Skip to content

Commit

Permalink
cgen: implement overriding of != and == (#7837)
Browse files Browse the repository at this point in the history
  • Loading branch information
Delta456 authored Jan 3, 2021
1 parent b7f83e2 commit 9033099
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*Not yet released*
- `vweb` now uses struct embedding: `app.vweb.text('hello') => app.text('hello')`.
- Consts can now be declared outside of `const()` blocks: `const x = 0`.
- Allow overloading of `>`, `<`, `!=` and `==` operators.

## V 0.2.1
- Hashmap bootstrapping fixes.
Expand Down
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 @@ -4980,7 +4980,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 @@ -4992,7 +4992,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 := !left_sym.has_method('==')
has_ne_overloaded := !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

0 comments on commit 9033099

Please sign in to comment.