diff --git a/doc/docs.md b/doc/docs.md index 841fde5632..43ebc3d8cb 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -3148,9 +3148,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. - 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`. - 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 9f6a022f59..b1024ce4c8 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 126f574f2a..9b92b25aaa 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4949,7 +4949,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 { @@ -4961,6 +4961,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 { + 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 f63d6cbc40..bb322f7d33 100644 --- a/vlib/v/checker/tests/method_op_err.out +++ b/vlib/v/checker/tests/method_op_err.out @@ -12,9 +12,16 @@ vlib/v/checker/tests/method_op_err.vv:14:1: error: both sides of an operator mus | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | return User{u.a - f.a, u.b-f.a} 16 | } -vlib/v/checker/tests/method_op_err.vv:20:24: error: infix expr: cannot use `Foo` (right expression) as `User` - 18 | fn main() { - 19 | println(User{3, 4}) - 20 | println(User{3, 4} - Foo{3, 3}) +vlib/v/checker/tests/method_op_err.vv:18:1: error: operator comparison methods should return `bool` + 16 | } + 17 | + 18 | fn (u User) > (u1 User) User { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 19 | return User{} + 20 | } +vlib/v/checker/tests/method_op_err.vv:24:24: error: infix expr: cannot use `Foo` (right expression) as `User` + 22 | fn main() { + 23 | println(User{3, 4}) + 24 | println(User{3, 4} - Foo{3, 3}) | ^ - 21 | } + 25 | } diff --git a/vlib/v/checker/tests/method_op_err.vv b/vlib/v/checker/tests/method_op_err.vv index 4e97ce4e4b..c4e1dabd72 100644 --- a/vlib/v/checker/tests/method_op_err.vv +++ b/vlib/v/checker/tests/method_op_err.vv @@ -15,6 +15,10 @@ 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 main() { println(User{3, 4}) println(User{3, 4} - Foo{3, 3}) diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index b42067a47e..b67b9c5a45 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -3153,13 +3153,14 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { g.expr(node.left) g.write(')') } else { - a := left_sym.name[0].is_capital() || left_sym.name.contains('.') + a := (left_sym.name[0].is_capital() || left_sym.name.contains('.')) && + left_sym.kind != .enum_ 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] && ((a && b) || c || d) { + if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt] && ((a && b) || c || d) { // Overloaded operators g.write(g.typ(if !d { left_type @@ -5006,6 +5007,8 @@ fn op_to_fn_name(name string) string { '*' { '_op_mul' } '/' { '_op_div' } '%' { '_op_mod' } + '<' { '_op_lt' } + '>' { '_op_gt' } else { 'bad op $name' } } } diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index bdf7a87daa..38496b1ad0 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[0] 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 78e6992648..832f89494a 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -270,7 +270,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { } } } - if p.tok.kind in [.plus, .minus, .mul, .div, .mod] { + if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt] && 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/tests/operator_overloading_with_string_interpolation_test.v b/vlib/v/tests/operator_overloading_with_string_interpolation_test.v index 30ee476b24..34533ec29a 100644 --- a/vlib/v/tests/operator_overloading_with_string_interpolation_test.v +++ b/vlib/v/tests/operator_overloading_with_string_interpolation_test.v @@ -7,50 +7,60 @@ pub fn (a Vec) str() string { return '{$a.x, $a.y}' } -fn (a Vec) +(b Vec) Vec { +fn (a Vec) + (b Vec) Vec { return Vec{a.x + b.x, a.y + b.y} } -fn (a Vec) -(b Vec) Vec { +fn (a Vec) - (b Vec) Vec { return Vec{a.x - b.x, a.y - b.y} } -fn (a Vec) *(b Vec) Vec { +fn (a Vec) * (b Vec) Vec { return Vec{a.x * b.x, a.y * b.y} } -fn (a Vec) /(b Vec) Vec { +fn (a Vec) / (b Vec) Vec { return Vec{a.x / b.x, a.y / b.y} } -fn (a Vec) %(b Vec) Vec { +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 +} + fn test_operator_overloading_with_string_interpolation() { a := Vec{2, 3} b := Vec{4, 5} - c := a + b assert a.x + b.x == c.x assert a.y + b.y == c.y - + ////// ///// d := a - b assert a.x - b.x == d.x assert a.y - b.y == d.y - + ////// ///// e := a * b assert a.x * b.x == e.x assert a.y * b.y == e.y - + ////// ///// f := a / b assert a.x / b.x == f.x assert a.y / b.y == f.y - + ////// ///// g := a % b assert a.x % b.x == g.x assert a.y % b.y == g.y - + ////// ///// + assert b > a == true + assert a < b == true + ////// ///// assert c.str() == '{6, 8}' assert d.str() == '{-2, -2}' assert e.str() == '{8, 15}' diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index 1ed32bacfa..9c42de5e0b 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -285,6 +285,8 @@ pub fn replace_op(s string) string { `*` { '_mult' } `/` { '_div' } `%` { '_mod' } + `<` { '_lt' } + `>` { '_gt' } else { '' } } return s[..s.len - 1] + suffix