diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index c86af5079f..84e4a3f7d5 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -278,8 +278,12 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { if node.params.len != 2 { c.error('operator methods should have exactly 1 argument', node.pos) } else { - receiver_sym := c.table.sym(node.receiver.typ) - param_sym := c.table.sym(node.params[1].typ) + receiver_type := node.receiver.typ + receiver_sym := c.table.sym(receiver_type) + + param_type := node.params[1].typ + param_sym := c.table.sym(param_type) + if param_sym.kind == .string && receiver_sym.kind == .string { // bypass check for strings // TODO there must be a better way to handle that @@ -302,6 +306,11 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } else if parent_sym.is_primitive() { c.error('cannot define operator methods on type alias for `${parent_sym.name}`', node.pos) + } else if receiver_type != param_type { + srtype := c.table.type_to_str(receiver_type) + sptype := c.table.type_to_str(param_type) + c.error('the receiver type `${srtype}` should be the same type as the operand `${sptype}`', + node.pos) } } } diff --git a/vlib/v/checker/tests/method_op_alias_err.out b/vlib/v/checker/tests/method_op_alias_err.out index 195ab562f6..a47bc130af 100644 --- a/vlib/v/checker/tests/method_op_alias_err.out +++ b/vlib/v/checker/tests/method_op_alias_err.out @@ -1,12 +1,12 @@ vlib/v/checker/tests/method_op_alias_err.vv:4:18: error: expected `Foo` not `Foo2` - both operands must be the same type for operator overloading 2 | type Foo2 = string - 3 | + 3 | 4 | fn (f Foo) + (f1 Foo2) Foo2 { | ~~~~ 5 | return Foo2(f + f1) 6 | } vlib/v/checker/tests/method_op_alias_err.vv:5:17: error: infix expr: cannot use `string` (right expression) as `string` - 3 | + 3 | 4 | fn (f Foo) + (f1 Foo2) Foo2 { 5 | return Foo2(f + f1) | ~~~~~~ @@ -14,7 +14,7 @@ vlib/v/checker/tests/method_op_alias_err.vv:5:17: error: infix expr: cannot use 7 | vlib/v/checker/tests/method_op_alias_err.vv:8:1: error: cannot define operator methods on type alias for `string` 6 | } - 7 | + 7 | 8 | fn (f Foo) * (f1 Foo) Foo { | ~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | return Foo(f + f1) @@ -31,12 +31,12 @@ vlib/v/checker/tests/method_op_alias_err.vv:15:9: error: cannot assign to `f`: e 14 | f += 'fg' 15 | f *= Foo2('2') | ~~~~~~~~~ - 16 | f -= Foo('fo') + 16 | f -= Foo('fo') 17 | println(f) vlib/v/checker/tests/method_op_alias_err.vv:16:6: error: cannot use operator methods on type alias for `string` 14 | f += 'fg' 15 | f *= Foo2('2') - 16 | f -= Foo('fo') + 16 | f -= Foo('fo') | ~~ 17 | println(f) 18 | } diff --git a/vlib/v/checker/tests/overload_operator_difference_in_operand_types.out b/vlib/v/checker/tests/overload_operator_difference_in_operand_types.out new file mode 100644 index 0000000000..835f8b0f3e --- /dev/null +++ b/vlib/v/checker/tests/overload_operator_difference_in_operand_types.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/overload_operator_difference_in_operand_types.vv:9:1: error: the receiver type `Resources` should be the same type as the operand `&Resources` + 7 | } + 8 | + 9 | fn (a Resources) < (b &Resources) bool { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 10 | return a.hru < b.hru && a.sru < b.sru && a.cru < b.cru && a.mru < b.mru + 11 | } +vlib/v/checker/tests/overload_operator_difference_in_operand_types.vv:13:1: error: the receiver type `&Resources` should be the same type as the operand `Resources` + 11 | } + 12 | + 13 | fn (a &Resources) == (b Resources) bool { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 14 | return a.hru == b.hru + 15 | } diff --git a/vlib/v/checker/tests/overload_operator_difference_in_operand_types.vv b/vlib/v/checker/tests/overload_operator_difference_in_operand_types.vv new file mode 100644 index 0000000000..c0f5b7f554 --- /dev/null +++ b/vlib/v/checker/tests/overload_operator_difference_in_operand_types.vv @@ -0,0 +1,26 @@ +struct Resources { +mut: + hru u64 + sru u64 + cru u64 + mru u64 +} + +fn (a Resources) < (b &Resources) bool { + return a.hru < b.hru && a.sru < b.sru && a.cru < b.cru && a.mru < b.mru +} + +fn (a &Resources) == (b Resources) bool { + return a.hru == b.hru +} + +fn test_struct_with_reference_operands_for_the_overloaded_operators_do_work() { + aa := Resources{} + bb := Resources{} + assert dump(aa < bb) == false + assert dump(aa == bb) == true + assert dump(aa != bb) == false + assert dump(aa > bb) == false + assert dump(aa <= bb) == true + assert dump(aa >= bb) == true +} diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index f69709c783..483ccbdf97 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -85,7 +85,13 @@ fn (mut g Gen) infix_expr_arrow_op(node ast.InfixExpr) { fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { left := g.unwrap(node.left_type) right := g.unwrap(node.right_type) - has_defined_eq_operator := g.table.has_method(left.sym, '==') + mut has_defined_eq_operator := false + mut eq_operator_expects_ptr := false + if m := g.table.find_method(left.sym, '==') { + has_defined_eq_operator = true + eq_operator_expects_ptr = m.receiver_type.is_ptr() + } + // TODO: investigate why the following is needed for vlib/v/tests/string_alias_test.v and vlib/v/tests/anon_fn_with_alias_args_test.v has_alias_eq_op_overload := left.sym.info is ast.Alias && left.sym.has_method('==') if g.pref.translated && !g.is_builtin_mod { g.gen_plain_infix_expr(node) @@ -113,9 +119,15 @@ fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { } g.write('__eq(') g.write('*'.repeat(left.typ.nr_muls())) + if eq_operator_expects_ptr { + g.write('&') + } g.expr(node.left) g.write(', ') g.write('*'.repeat(right.typ.nr_muls())) + if eq_operator_expects_ptr { + g.write('&') + } g.expr(node.right) g.write(')') } else if left.typ.idx() == right.typ.idx() @@ -294,7 +306,14 @@ fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { fn (mut g Gen) infix_expr_cmp_op(node ast.InfixExpr) { left := g.unwrap(node.left_type) right := g.unwrap(node.right_type) - has_operator_overloading := g.table.has_method(left.sym, '<') + + mut has_operator_overloading := false + mut operator_expects_ptr := false + if m := g.table.find_method(left.sym, '<') { + has_operator_overloading = true + operator_expects_ptr = m.receiver_type.is_ptr() + } + if g.pref.translated && !g.is_builtin_mod { g.gen_plain_infix_expr(node) return @@ -310,17 +329,29 @@ fn (mut g Gen) infix_expr_cmp_op(node ast.InfixExpr) { if node.op in [.lt, .ge] { g.write('(') g.write('*'.repeat(left.typ.nr_muls())) + if operator_expects_ptr { + g.write('&') + } g.expr(node.left) g.write(', ') g.write('*'.repeat(right.typ.nr_muls())) + if operator_expects_ptr { + g.write('&') + } g.expr(node.right) g.write(')') } else { g.write('(') g.write('*'.repeat(right.typ.nr_muls())) + if operator_expects_ptr { + g.write('&') + } g.expr(node.right) g.write(', ') g.write('*'.repeat(left.typ.nr_muls())) + if operator_expects_ptr { + g.write('&') + } g.expr(node.left) g.write(')') } @@ -333,17 +364,29 @@ fn (mut g Gen) infix_expr_cmp_op(node ast.InfixExpr) { if node.op in [.lt, .ge] { g.write('(') g.write('*'.repeat(left.typ.nr_muls())) + if operator_expects_ptr { + g.write('&') + } g.expr(node.left) g.write(', ') g.write('*'.repeat(right.typ.nr_muls())) + if operator_expects_ptr { + g.write('&') + } g.expr(node.right) g.write(')') } else { g.write('(') g.write('*'.repeat(right.typ.nr_muls())) + if operator_expects_ptr { + g.write('&') + } g.expr(node.right) g.write(', ') g.write('*'.repeat(left.typ.nr_muls())) + if operator_expects_ptr { + g.write('&') + } g.expr(node.left) g.write(')') } diff --git a/vlib/v/tests/operator_overloading_with_reference_operands_test.v b/vlib/v/tests/operator_overloading_with_reference_operands_test.v new file mode 100644 index 0000000000..6c5db2a872 --- /dev/null +++ b/vlib/v/tests/operator_overloading_with_reference_operands_test.v @@ -0,0 +1,47 @@ +struct Resources { +mut: + hru u64 + sru u64 + cru u64 + mru u64 +} + +fn (a &Resources) < (b &Resources) bool { + return a.hru < b.hru && a.sru < b.sru && a.cru < b.cru && a.mru < b.mru +} + +fn (a &Resources) == (b &Resources) bool { + return a.hru == b.hru +} + +fn test_struct_with_reference_operands_for_the_overloaded_operators_do_work() { + aa := Resources{} + bb := Resources{} + assert dump(aa < bb) == false + assert dump(aa == bb) == true + assert dump(aa != bb) == false + assert dump(aa > bb) == false + assert dump(aa <= bb) == true + assert dump(aa >= bb) == true +} + +// Issue: https://github.com/vlang/v/issues/15859 +struct Foo { + id u32 + x u32 + y u32 +} + +fn (f &Foo) == (o &Foo) bool { + return f.id == o.id +} + +fn test_eq_operator_with_reference_operands() { + a := Foo{1, 4, 5} + b := Foo{1, 9, 10} + if a == b { + assert true + return + } + assert false +}