From 29056191084b40c8cead648167e89722724028f3 Mon Sep 17 00:00:00 2001 From: Delta456 Date: Sun, 3 Jan 2021 16:56:50 +0530 Subject: [PATCH 1/6] cgen: implement overriding of `==` and `!=` for operator overloading --- vlib/v/checker/checker.v | 4 ++-- vlib/v/gen/cgen.v | 7 +++++-- vlib/v/gen/fn.v | 2 +- vlib/v/parser/fn.v | 3 ++- vlib/v/util/util.v | 33 +++++++++++++++++++++------------ 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 6b7f5b87559593..9a64aa04e7ca30 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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 { @@ -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) } } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 8d210c5b2977a4..464936b27af995 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -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('==') + 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 { @@ -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(') @@ -3160,7 +3163,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { // 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) { + if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne] && ((a && b) || c || d) { // Overloaded operators g.write(g.typ(if !d { left_type diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index f1b5c3ff6dbf4c..c6cdfee321fd5f 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -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 { diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 453abcb702b3de..c8697f69c6e628 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -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', diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index d93a057f07abde..c1af1b4e31119a 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -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 { From 2a08adae1c2ae66a0f0c3636249c2f00e3397bf7 Mon Sep 17 00:00:00 2001 From: Delta456 Date: Sun, 3 Jan 2021 17:43:52 +0530 Subject: [PATCH 2/6] more work --- doc/docs.md | 6 +++--- vlib/v/gen/cgen.v | 8 +++++++- ...erator_overloading_with_string_interpolation_test.v | 10 ++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index 2ed6eec55164c2..4e4ffbd46ae03c 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -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 diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 464936b27af995..efaf6388c65c44 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -3163,7 +3163,13 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { // 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, .eq, .ne] && ((a && b) || c || d) { + // Do not generate operator overloading with these `right_sym.kind`. + e := right_sym.kind !in [.voidptr, .any_int, .int] + // If it's an alias then check the parent type + f := left_sym.kind == .alias && + g.table.get_type_symbol((left_sym.info as table.Alias).parent_type).kind !in [.function, .interface_, .sum_type] + if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne] && + ((a && b && e && f) || c || d) { // Overloaded operators g.write(g.typ(if !d { left_type diff --git a/vlib/v/tests/operator_overloading_with_string_interpolation_test.v b/vlib/v/tests/operator_overloading_with_string_interpolation_test.v index 34533ec29a7d6d..91db05897eaa6f 100644 --- a/vlib/v/tests/operator_overloading_with_string_interpolation_test.v +++ b/vlib/v/tests/operator_overloading_with_string_interpolation_test.v @@ -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} @@ -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}' From 13c22f92425b5a336f72af22d32781d519624898 Mon Sep 17 00:00:00 2001 From: Delta456 Date: Sun, 3 Jan 2021 18:44:38 +0530 Subject: [PATCH 3/6] fix --- vlib/v/gen/cgen.v | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index efaf6388c65c44..b976acaf2f9abe 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -3157,7 +3157,7 @@ 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 @@ -3165,11 +3165,8 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { g.typ((left_sym.info as table.Alias).parent_type).split('__').last()[0].is_capital() // Do not generate operator overloading with these `right_sym.kind`. e := right_sym.kind !in [.voidptr, .any_int, .int] - // If it's an alias then check the parent type - f := left_sym.kind == .alias && - g.table.get_type_symbol((left_sym.info as table.Alias).parent_type).kind !in [.function, .interface_, .sum_type] if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne] && - ((a && b && e && f) || c || d) { + ((a && b && e) || c || d) { // Overloaded operators g.write(g.typ(if !d { left_type From 76fe5d739407463bbbac3e0c03b8d17b383a5d35 Mon Sep 17 00:00:00 2001 From: Delta456 Date: Sun, 3 Jan 2021 18:55:36 +0530 Subject: [PATCH 4/6] add ops for fmt --- vlib/v/ast/str.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index b1024ce4c86d71..d85bf79a349274 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -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 { From 9029d0e4b98ad3bfee4ee888548dc81a289e3891 Mon Sep 17 00:00:00 2001 From: Delta456 Date: Sun, 3 Jan 2021 20:22:48 +0530 Subject: [PATCH 5/6] optimize --- vlib/v/gen/cgen.v | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index b976acaf2f9abe..19ac78c5529004 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2868,8 +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('==') - has_ne_overloaded := !right_sym.has_method('!=') && !left_sym.has_method('!=') + 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 { From 3a159e4c08e2838a171467b9d14b97534926526b Mon Sep 17 00:00:00 2001 From: Delta456 Date: Sun, 3 Jan 2021 20:37:05 +0530 Subject: [PATCH 6/6] add feature to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb34a32290f17f..66a8f4fb02da79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.