diff --git a/CHANGELOG.md b/CHANGELOG.md index 7966072dee..16f6f5e69e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +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`. -- Overloading of `>`, `<`, `!=`, and `==` operators. +- Overloading of `>`, `<`, `!=`, `==`, `<=` and `>=` operators. - New struct updating syntax: `User{ ...u, name: 'new' }` to replace `{ u | name: 'new' }`. - `byte.str()` has been fixed and works like with all other numbers. `byte.ascii_str()` has been added. - Smart cast in for loops: `for mut x is string {}`. diff --git a/doc/docs.md b/doc/docs.md index 6912c4d5bd..72c0dfec98 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -3217,11 +3217,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. +- 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 `!=` operators, 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/ast/str.v b/vlib/v/ast/str.v index c596c6934f..36badc0505 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 { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index ddaa6807f9..fd4c427825 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -840,6 +840,17 @@ 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_ { + 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) + if left_name == right_name { + c.error('operation `$left_name` $infix_expr.op.str() `$right_name` does not exist, please define it', + infix_expr.pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', infix_expr.pos) + } + } } } .left_shift { @@ -5103,7 +5114,8 @@ 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/checker/tests/method_op_err.out b/vlib/v/checker/tests/method_op_err.out index bb322f7d33..cbee61bf66 100644 --- a/vlib/v/checker/tests/method_op_err.out +++ b/vlib/v/checker/tests/method_op_err.out @@ -24,4 +24,18 @@ vlib/v/checker/tests/method_op_err.vv:24:24: error: infix expr: cannot use `Foo` 23 | println(User{3, 4}) 24 | println(User{3, 4} - Foo{3, 3}) | ^ - 25 | } + 25 | println(User{3, 2} < User{2, 4}) + 26 | println(User{3, 4} < Foo{3, 4}) +vlib/v/checker/tests/method_op_err.vv:25:24: error: operation `User` < `User` does not exist, please define it + 23 | println(User{3, 4}) + 24 | println(User{3, 4} - Foo{3, 3}) + 25 | println(User{3, 2} < User{2, 4}) + | ^ + 26 | println(User{3, 4} < Foo{3, 4}) + 27 | } +vlib/v/checker/tests/method_op_err.vv:26:24: error: mismatched types `User` and `Foo` + 24 | println(User{3, 4} - Foo{3, 3}) + 25 | println(User{3, 2} < User{2, 4}) + 26 | println(User{3, 4} < Foo{3, 4}) + | ^ + 27 | } diff --git a/vlib/v/checker/tests/method_op_err.vv b/vlib/v/checker/tests/method_op_err.vv index c4e1dabd72..e15fe4e624 100644 --- a/vlib/v/checker/tests/method_op_err.vv +++ b/vlib/v/checker/tests/method_op_err.vv @@ -22,4 +22,6 @@ fn (u User) > (u1 User) User { fn main() { println(User{3, 4}) println(User{3, 4} - Foo{3, 3}) + println(User{3, 2} < User{2, 4}) + println(User{3, 4} < Foo{3, 4}) } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index f09e2d901b..ce6766eb0f 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -3230,7 +3230,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, .any_int, .int] - if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne] && + if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne, .le, .ge] && ((a && b && e) || c || d) { // Overloaded operators g.write(g.typ(if !d { diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 1ea64d4ebf..8a5aff08a5 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -56,7 +56,7 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl, skip bool) { } // mut name := it.name - if name 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 8032327809..09a2314dae 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -277,7 +277,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { } } } - if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt, .eq, .ne] && + if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt, .eq, .ne, .le, .ge] && p.peek_tok.kind == .lpar { name = p.tok.kind.str() // op_to_fn_name() if rec_type == table.void_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 91db05897e..38e7e2d222 100644 --- a/vlib/v/tests/operator_overloading_with_string_interpolation_test.v +++ b/vlib/v/tests/operator_overloading_with_string_interpolation_test.v @@ -43,6 +43,14 @@ 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} @@ -68,6 +76,8 @@ fn test_operator_overloading_with_string_interpolation() { ////// ///// assert b > a == true assert a < b == true + assert b >= a == true + assert a <= b == true assert (Vec{2, 3} == Vec{3, 2}) == true assert (Vec{2, 3} != Vec{3, 2}) == false ////// ///// diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index 7e430fb0e2..ad30263fc3 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -316,6 +316,8 @@ pub fn replace_op(s string) string { suffix := match s { '==' { '_eq' } '!=' { '_ne' } + '<=' { '_le' } + '>=' { '_ge' } else { '' } } return s[..s.len - 2] + suffix