Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cgen: implement overriding of != and == #7837

Merged
merged 6 commits into from
Jan 3, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions doc/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -3166,11 +3166,11 @@ operator overloading is an important feature to have in order to improve readabi

To improve safety and maintainability, operator overloading is limited:

- It's only possible to overload `+, -, *, /, %, <, >` operators.
- `==` and `!=` are self generated by the compiler.
- It's only possible to overload `+, -, *, /, %, <, >, ==, !=` operators.
- `==` and `!=` are self generated by the compiler but can be overriden.
- Calling other functions inside operator functions is not allowed.
- Operator functions can't modify their arguments.
- When using `<` and `>`, the return type must be `bool`.
- When using `<`, `>`, `==` and `!=` operators, the return type must be `bool`.
- Both arguments must have the same type (just like with all operators in V).

## Inline assembly
Expand Down
2 changes: 1 addition & 1 deletion vlib/v/ast/str.v
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn (node &FnDecl) stringify(t &table.Table, cur_mod string, m2a map[string]s
}
}
f.write('fn $receiver$name')
if name in ['+', '-', '*', '/', '%', '<', '>'] {
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!='] {
f.write(' ')
}
if node.is_generic {
Expand Down
4 changes: 2 additions & 2 deletions vlib/v/checker/checker.v
Original file line number Diff line number Diff line change
Expand Up @@ -4964,7 +4964,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
c.error('.str() methods should have 0 arguments', node.pos)
}
}
if node.language == .v && node.is_method && node.name in ['+', '-', '*', '%', '/', '<', '>'] {
if node.language == .v && node.is_method && node.name in ['+', '-', '*', '%', '/', '<', '>', '==', '!='] {
if node.params.len != 2 {
c.error('operator methods should have exactly 1 argument', node.pos)
} else {
Expand All @@ -4976,7 +4976,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
} else {
if node.receiver.typ != node.params[1].typ {
c.error('both sides of an operator must be the same type', node.pos)
} else if node.name in ['<', '>'] && node.return_type != table.bool_type {
} else if node.name in ['<', '>', '==', '!='] && node.return_type != table.bool_type {
c.error('operator comparison methods should return `bool`', node.pos)
}
}
Expand Down
12 changes: 9 additions & 3 deletions vlib/v/gen/cgen.v
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,8 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
return
}
right_sym := g.table.get_type_symbol(node.right_type)
has_eq_overloaded := !right_sym.has_method('==') && !left_sym.has_method('==')
Delta456 marked this conversation as resolved.
Show resolved Hide resolved
has_ne_overloaded := !right_sym.has_method('!=') && !left_sym.has_method('!=')
unaliased_right := if right_sym.kind == .alias {
(right_sym.info as table.Alias).parent_type
} else {
Expand Down Expand Up @@ -3006,7 +3008,8 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
}
g.expr(node.right)
g.write(')')
} else if node.op in [.eq, .ne] && left_sym.kind == .struct_ && right_sym.kind == .struct_ {
} else if node.op in [.eq, .ne] &&
left_sym.kind == .struct_ && right_sym.kind == .struct_ && has_eq_overloaded && has_ne_overloaded {
ptr_typ := g.gen_struct_equality_fn(left_type)
if node.op == .eq {
g.write('${ptr_typ}_struct_eq(')
Expand Down Expand Up @@ -3154,13 +3157,16 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
g.write(')')
} else {
a := (left_sym.name[0].is_capital() || left_sym.name.contains('.')) &&
left_sym.kind != .enum_
left_sym.kind !in [.enum_, .function, .interface_, .sum_type]
b := left_sym.kind != .alias
c := left_sym.kind == .alias && (left_sym.info as table.Alias).language == .c
// Check if aliased type is a struct
d := !b &&
g.typ((left_sym.info as table.Alias).parent_type).split('__').last()[0].is_capital()
if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt] && ((a && b) || c || d) {
// Do not generate operator overloading with these `right_sym.kind`.
e := right_sym.kind !in [.voidptr, .any_int, .int]
if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne] &&
((a && b && e) || c || d) {
// Overloaded operators
g.write(g.typ(if !d {
left_type
Expand Down
2 changes: 1 addition & 1 deletion vlib/v/gen/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl, skip bool) {
}
//
mut name := it.name
if name[0] in [`+`, `-`, `*`, `/`, `%`, `<`, `>`] {
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!='] {
name = util.replace_op(name)
}
if it.is_method {
Expand Down
3 changes: 2 additions & 1 deletion vlib/v/parser/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
}
}
}
if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt] && p.peek_tok.kind == .lpar {
if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt, .eq, .ne] &&
p.peek_tok.kind == .lpar {
name = p.tok.kind.str() // op_to_fn_name()
if rec_type == table.void_type {
p.error_with_pos('cannot use operator overloading with normal functions',
Expand Down
10 changes: 10 additions & 0 deletions vlib/v/tests/operator_overloading_with_string_interpolation_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ fn (a Vec) < (b Vec) bool {
return a.x < b.x && a.y < b.y
}

fn (a Vec) == (b Vec) bool {
return a.x == b.y && a.y == b.x
}

fn (a Vec) != (b Vec) bool {
return !(a == b)
}

fn test_operator_overloading_with_string_interpolation() {
a := Vec{2, 3}
b := Vec{4, 5}
Expand All @@ -60,6 +68,8 @@ fn test_operator_overloading_with_string_interpolation() {
////// /////
assert b > a == true
assert a < b == true
assert (Vec{2, 3} == Vec{3, 2}) == true
assert (Vec{2, 3} != Vec{3, 2}) == false
////// /////
assert c.str() == '{6, 8}'
assert d.str() == '{-2, -2}'
Expand Down
33 changes: 21 additions & 12 deletions vlib/v/util/util.v
Original file line number Diff line number Diff line change
Expand Up @@ -278,18 +278,27 @@ pub fn imax(a int, b int) int {
}

pub fn replace_op(s string) string {
last_char := s[s.len - 1]
suffix := match last_char {
`+` { '_plus' }
`-` { '_minus' }
`*` { '_mult' }
`/` { '_div' }
`%` { '_mod' }
`<` { '_lt' }
`>` { '_gt' }
else { '' }
}
return s[..s.len - 1] + suffix
if s.len == 1 {
last_char := s[s.len - 1]
suffix := match last_char {
`+` { '_plus' }
`-` { '_minus' }
`*` { '_mult' }
`/` { '_div' }
`%` { '_mod' }
`<` { '_lt' }
`>` { '_gt' }
else { '' }
}
return s[..s.len - 1] + suffix
} else {
suffix := match s {
'==' { '_eq' }
'!=' { '_ne' }
else { '' }
}
return s[..s.len - 2] + suffix
}
}

pub fn join_env_vflags_and_os_args() []string {
Expand Down