diff --git a/CHANGELOG.md b/CHANGELOG.md index 4463e71614..eea27fdd08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ from local variables. - `__offsetof` for low level needs (works like `offsetof` in C). - vfmt now preserves empty lines, like gofmt. -- Support for compile time environment variables via `$env('ENV_VAR')`. +- Support for compile time environment variables via `$env('ENV_VAR')`. +- Allow method declaration of `==` and `<` operators and auto generate `!=`, `>`, `<=` and `>=`. ## V 0.2.1 *30 Dec 2020* diff --git a/doc/docs.md b/doc/docs.md index 738164a25e..7187fdfc7c 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -3637,7 +3637,8 @@ To improve safety and maintainability, operator overloading is limited: - `==` 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 `!=` operators, the return type must be `bool`. +- When using `<` and `==` operators, the return type must be `bool`. +- `!=`, `>`, `<=` and `>=` are auto generated when `==` and `<` are defined. - Both arguments must have the same type (just like with all operators in V). - Assignment operators (`*=`, `+=`, `/=`, etc) are auto generated when the operators are defined though they must return the same type. diff --git a/vlib/time/operator.v b/vlib/time/operator.v index 4de7d8de5b..d4f13a82cb 100644 --- a/vlib/time/operator.v +++ b/vlib/time/operator.v @@ -9,12 +9,6 @@ pub fn (t1 Time) == (t2 Time) bool { return false } -// operator `!=` returns true if provided time is not equal to time -[inline] -pub fn (t1 Time) != (t2 Time) bool { - return !(t1 == t2) -} - // operator `<` returns true if provided time is less than time [inline] pub fn (t1 Time) < (t2 Time) bool { @@ -24,27 +18,6 @@ pub fn (t1 Time) < (t2 Time) bool { return false } -// operator `<=` returns true if provided time is less or equal to time -[inline] -pub fn (t1 Time) <= (t2 Time) bool { - return t1 < t2 || t1 == t2 -} - -// operator `>` returns true if provided time is greater than time -[inline] -pub fn (t1 Time) > (t2 Time) bool { - if t1.unix > t2.unix || (t1.unix == t2.unix && t1.microsecond > t2.microsecond) { - return true - } - return false -} - -// operator `>=` returns true if provided time is greater or equal to time -[inline] -pub fn (t1 Time) >= (t2 Time) bool { - return t1 > t2 || t1 == t2 -} - // Time subtract using operator overloading. [inline] pub fn (lhs Time) - (rhs Time) Duration { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 33960ffd75..f4f16b6bc7 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -882,7 +882,7 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type { .gt, .lt, .ge, .le { if left.kind in [.array, .array_fixed] && right.kind in [.array, .array_fixed] { c.error('only `==` and `!=` are defined on arrays', infix_expr.pos) - } else if left.kind == .struct_ && right.kind == .struct_ { + } else if left.kind == .struct_ && right.kind == .struct_ && infix_expr.op in [.eq, .lt] { if !(left.has_method(infix_expr.op.str()) && right.has_method(infix_expr.op.str())) { left_name := c.table.type_to_str(left_type) right_name := c.table.type_to_str(right_type) @@ -894,6 +894,14 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type { } } } + if left.kind == .struct_ && right.kind == .struct_ { + if !left.has_method('<') && infix_expr.op in [.ge, .le] { + c.error('cannot use `$infix_expr.op` as `<` operator method is not defined', + infix_expr.pos) + } else if !left.has_method('<') && infix_expr.op == .gt { + c.error('cannot use `>` as `<=` operator method is not defined', infix_expr.pos) + } + } } .left_shift { if left.kind == .array { @@ -5617,8 +5625,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 { @@ -5636,8 +5643,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } else if node.receiver.typ != node.params[1].typ { c.error('expected `$receiver_sym.name` not `$param_sym.name` - both operands must be the same type for operator overloading', node.params[1].type_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) } else if parent_sym.is_primitive() { c.error('cannot define operator methods on type alias for `$parent_sym.name`', diff --git a/vlib/v/checker/tests/method_op_err.out b/vlib/v/checker/tests/method_op_err.out index e32d5e9913..c24f8e5863 100644 --- a/vlib/v/checker/tests/method_op_err.out +++ b/vlib/v/checker/tests/method_op_err.out @@ -12,65 +12,58 @@ vlib/v/checker/tests/method_op_err.vv:14:18: error: expected `User` not `Foo` - | ~~~ 15 | return User{u.a - f.a, u.b-f.a} 16 | } -vlib/v/checker/tests/method_op_err.vv:18:1: error: operator comparison methods should return `bool` +vlib/v/checker/tests/method_op_err.vv:18:9: error: receiver cannot be `mut` for operator overloading 16 | } 17 | - 18 | fn (u User) > (u1 User) User { - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 18 | fn (mut u User) * (u1 User) User { + | ~~~~~~ 19 | return User{} 20 | } -vlib/v/checker/tests/method_op_err.vv:22:9: error: receiver cannot be `mut` for operator overloading +vlib/v/checker/tests/method_op_err.vv:22:1: error: argument cannot be `mut` for operator overloading 20 | } 21 | - 22 | fn (mut u User) * (u1 User) User { - | ~~~~~~ + 22 | fn (u User) / (mut u1 User) User { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | return User{} 24 | } -vlib/v/checker/tests/method_op_err.vv:26:1: error: argument cannot be `mut` for operator overloading - 24 | } - 25 | - 26 | fn (u User) / (mut u1 User) User { - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 27 | return User{} - 28 | } -vlib/v/checker/tests/method_op_err.vv:36:24: error: infix expr: cannot use `Foo` (right expression) as `User` - 34 | fn main() { - 35 | println(User{3, 4}) - 36 | println(User{3, 4} - Foo{3, 3}) +vlib/v/checker/tests/method_op_err.vv:32:24: error: infix expr: cannot use `Foo` (right expression) as `User` + 30 | fn main() { + 31 | println(User{3, 4}) + 32 | println(User{3, 4} - Foo{3, 3}) | ^ - 37 | println(User{3, 2} < User{2, 4}) - 38 | println(User{3, 4} < Foo{3, 4}) -vlib/v/checker/tests/method_op_err.vv:37:24: error: undefined operation `User` < `User` - 35 | println(User{3, 4}) - 36 | println(User{3, 4} - Foo{3, 3}) - 37 | println(User{3, 2} < User{2, 4}) + 33 | println(User{3, 2} < User{2, 4}) + 34 | println(User{3, 4} < Foo{3, 4}) +vlib/v/checker/tests/method_op_err.vv:33:24: error: undefined operation `User` < `User` + 31 | println(User{3, 4}) + 32 | println(User{3, 4} - Foo{3, 3}) + 33 | println(User{3, 2} < User{2, 4}) | ^ - 38 | println(User{3, 4} < Foo{3, 4}) - 39 | mut u := User{3, 4} -vlib/v/checker/tests/method_op_err.vv:38:24: error: mismatched types `User` and `Foo` - 36 | println(User{3, 4} - Foo{3, 3}) - 37 | println(User{3, 2} < User{2, 4}) - 38 | println(User{3, 4} < Foo{3, 4}) + 34 | println(User{3, 4} < Foo{3, 4}) + 35 | mut u := User{3, 4} +vlib/v/checker/tests/method_op_err.vv:34:24: error: mismatched types `User` and `Foo` + 32 | println(User{3, 4} - Foo{3, 3}) + 33 | println(User{3, 2} < User{2, 4}) + 34 | println(User{3, 4} < Foo{3, 4}) | ^ - 39 | mut u := User{3, 4} - 40 | u += 12 -vlib/v/checker/tests/method_op_err.vv:40:10: error: cannot assign to `u`: expected `User`, not `int literal` - 38 | println(User{3, 4} < Foo{3, 4}) - 39 | mut u := User{3, 4} - 40 | u += 12 + 35 | mut u := User{3, 4} + 36 | u += 12 +vlib/v/checker/tests/method_op_err.vv:36:10: error: cannot assign to `u`: expected `User`, not `int literal` + 34 | println(User{3, 4} < Foo{3, 4}) + 35 | mut u := User{3, 4} + 36 | u += 12 | ~~ - 41 | u %= User{1, 3} - 42 | u += User{2, 3} -vlib/v/checker/tests/method_op_err.vv:41:5: error: operator %= not defined on left operand type `User` - 39 | mut u := User{3, 4} - 40 | u += 12 - 41 | u %= User{1, 3} + 37 | u %= User{1, 3} + 38 | u += User{2, 3} +vlib/v/checker/tests/method_op_err.vv:37:5: error: operator %= not defined on left operand type `User` + 35 | mut u := User{3, 4} + 36 | u += 12 + 37 | u %= User{1, 3} | ^ - 42 | u += User{2, 3} - 43 | } -vlib/v/checker/tests/method_op_err.vv:42:7: error: operator `+` must return `User` to be used as an assignment operator - 40 | u += 12 - 41 | u %= User{1, 3} - 42 | u += User{2, 3} + 38 | u += User{2, 3} + 39 | } +vlib/v/checker/tests/method_op_err.vv:38:7: error: operator `+` must return `User` to be used as an assignment operator + 36 | u += 12 + 37 | u %= User{1, 3} + 38 | u += User{2, 3} | ~~ - 43 | } + 39 | } diff --git a/vlib/v/checker/tests/method_op_err.vv b/vlib/v/checker/tests/method_op_err.vv index 40b1b5b44e..94239b90f7 100644 --- a/vlib/v/checker/tests/method_op_err.vv +++ b/vlib/v/checker/tests/method_op_err.vv @@ -15,10 +15,6 @@ fn (u User) - (f Foo) User { return User{u.a - f.a, u.b-f.a} } -fn (u User) > (u1 User) User { - return User{} -} - fn (mut u User) * (u1 User) User { return User{} } diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index 874b9eed33..96c26bc102 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -251,8 +251,8 @@ fn (mut g Gen) gen_array_sort(node ast.CallExpr) { sym := g.table.get_type_symbol(typ) if !is_reverse && sym.has_method('<') && infix_expr.left.str().len == 1 { g.definitions.writeln('\tif (${styp}__lt(*a, *b)) { return -1; } else { return 1; }}') - } else if is_reverse && sym.has_method('>') && infix_expr.left.str().len == 1 { - g.definitions.writeln('\tif (${styp}__gt(*a, *b)) { return -1; } else { return 1; }}') + } else if is_reverse && sym.has_method('<') && infix_expr.left.str().len == 1 { + g.definitions.writeln('\tif (!${styp}__lt(*a, *b)) { return -1; } else { return 1; }}') } else { field_type := g.typ(infix_expr.left_type) mut left_expr_str := g.write_expr_to_string(infix_expr.left) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index a14eabb2f3..d64a010926 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3019,7 +3019,6 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { } 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.info is table.Alias { right_sym.info.parent_type } else { @@ -3174,16 +3173,7 @@ 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_ { - if has_eq_overloaded && !has_ne_overloaded { - // Define `!=` as negation of Autogenerated `==` - styp := g.typ(left_type) - if node.op == .eq { - g.write('!${styp}__ne(') - } else if node.op == .ne { - g.write('${styp}__ne(') - } - } - if !has_eq_overloaded && has_ne_overloaded { + if !has_eq_overloaded { // Define `==` as negation of Autogenerated `!=` styp := g.typ(left_type) if node.op == .ne { @@ -3192,16 +3182,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { g.write('${styp}__eq(') } } - if !has_eq_overloaded && !has_ne_overloaded { - // Overload both User defined `==` and `!=` - styp := g.typ(left_type) - if node.op == .eq { - g.write('${styp}__eq(') - } else if node.op == .ne { - g.write('${styp}__ne(') - } - } - if has_eq_overloaded && has_ne_overloaded { + if has_eq_overloaded { // Auto generate both `==` and `!=` ptr_typ := g.gen_struct_equality_fn(left_type) if node.op == .eq { @@ -3374,8 +3355,7 @@ 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, .int_literal, .int] - if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne, .le, .ge] - && ((a && b && e) || c|| d) { + if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && ((a && b && e) || c || d) { // Overloaded operators g.write(g.typ(if !d { left_type } else { (left_sym.info as table.Alias).parent_type })) g.write('_') @@ -3385,6 +3365,28 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { g.write(', ') g.expr(node.right) g.write(')') + } else if node.op in [.ne, .gt, .ge, .le] && ((a && b && e) || c || d) { + typ := g.typ(if !d { left_type } else { (left_sym.info as table.Alias).parent_type }) + g.write('!$typ') + g.write('_') + if node.op == .ne { + g.write('_eq') + } else if node.op in [.ge, .le, .gt] { + g.write('_lt') + } + if node.op == .le { + g.write('(') + g.expr(node.right) + g.write(', ') + g.expr(node.left) + g.write(')') + } else { + g.write('(') + g.expr(node.left) + g.write(', ') + g.expr(node.right) + g.write(')') + } } else { need_par := node.op in [.amp, .pipe, .xor] // `x & y == 0` => `(x & y) == 0` in C if need_par { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index ef1e3bd2b3..be6910b4c7 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -56,7 +56,7 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) { } // mut name := node.name - if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '<=', '>='] { + if name in ['+', '-', '*', '/', '%', '<', '=='] { name = util.replace_op(name) } if node.is_method { diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 197f929d82..2a0927099a 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -275,14 +275,16 @@ fn (mut p Parser) fn_decl() ast.FnDecl { } } } - if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt, .eq, .ne, .le, .ge] - && p.peek_tok.kind == .lpar { + if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && 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', p.tok.position()) } p.next() + } else if p.tok.kind in [.ne, .gt, .ge, .le] && p.peek_tok.kind == .lpar { + p.error_with_pos('cannot overload `!=`, `>`, `<=` and `>=` as they are auto generated with `==` and`<`', + p.tok.position()) } // generic_params := p.parse_generic_params() 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 c66a5d6804..ccd412bb53 100644 --- a/vlib/v/tests/operator_overloading_with_string_interpolation_test.v +++ b/vlib/v/tests/operator_overloading_with_string_interpolation_test.v @@ -27,10 +27,6 @@ fn (a Vec) % (b Vec) Vec { return Vec{a.x % b.x, a.y % b.y} } -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.x && a.y < b.y } @@ -39,18 +35,6 @@ 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 (a Vec) >= (b Vec) bool { - return a > b || a == b -} - -fn (a Vec) <= (b Vec) bool { - return a < b || a == b -} - fn test_operator_overloading_with_string_interpolation() { a := Vec{2, 3} b := Vec{4, 5} diff --git a/vlib/v/tests/sorting_by_different_criteria_test.v b/vlib/v/tests/sorting_by_different_criteria_test.v index 8b6e679dc1..c54d1b1a7c 100644 --- a/vlib/v/tests/sorting_by_different_criteria_test.v +++ b/vlib/v/tests/sorting_by_different_criteria_test.v @@ -11,10 +11,6 @@ fn (p Parent) < (p1 Parent) bool { return p.name < p1.name } -fn (p Parent) > (p1 Parent) bool { - return p.name > p1.name -} - fn test_sorting_by_different_criteria_in_same_function() { mut arr := [ Parent{Child{0.2}, 'def'}, diff --git a/vlib/v/tests/sturct_ne_op_only_test.v b/vlib/v/tests/sturct_ne_op_only_test.v deleted file mode 100644 index 5e35682366..0000000000 --- a/vlib/v/tests/sturct_ne_op_only_test.v +++ /dev/null @@ -1,15 +0,0 @@ -struct User { - name string - num int -} - -fn (u User) != (u1 User) bool { - return u.num != u1.num -} - -fn test_eq_op() { - u1 := User{'Joe', 23} - u2 := User{'Joe', 24} - assert u1 != u2 - assert (u1 == u2) == false -} diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index cb99d18862..32feb2c5ce 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -382,9 +382,6 @@ pub fn replace_op(s string) string { } else { suffix := match s { '==' { '_eq' } - '!=' { '_ne' } - '<=' { '_le' } - '>=' { '_ge' } else { '' } } return s[..s.len - 2] + suffix