diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbf788884..4fc9d66e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Treating `enum` as `int` and operations on `enum` except `==` and `!=` are removed for strict type checking. - Support `[manualfree] fn f1(){}` and `[manualfree] module m1`, for functions doing their own memory management. - Allow usage of `<` and `>` operators for struct in `.sort` method for arrays, i.e. `arr.sort(a < b)`. +- Auto generate assignment operators like `+=`, `-=`, `*=`, `/=` and `%=` if the operators are defined. ## V 0.2.1 *30 Dec 2020* diff --git a/doc/docs.md b/doc/docs.md index db285aa1de..8d06a25157 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -3216,8 +3216,11 @@ fn (a Vec) - (b Vec) Vec { fn main() { a := Vec{2, 3} b := Vec{4, 5} + mut c := Vec{1, 2} println(a + b) // "{6, 8}" println(a - b) // "{-2, -2}" + c += a + println(c) // "{3, 5}" } ``` @@ -3235,6 +3238,8 @@ To improve safety and maintainability, operator overloading is limited: - Operator functions can't modify their arguments. - When using `<`, `>`, `>=`, `<=`, `==` and `!=` operators, the return type must be `bool`. - 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. ## Inline assembly diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 6b9a839cfa..e23c72f563 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2494,10 +2494,10 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { c.error('invalid right operand: $left_sym.name $assign_stmt.op $right_sym.name', right.position()) } - } else if !left_sym.is_number() && left_sym.kind !in [.byteptr, .charptr] { + } else if !left_sym.is_number() && left_sym.kind !in [.byteptr, .charptr, .struct_] { c.error('operator `$assign_stmt.op` not defined on left operand type `$left_sym.name`', left.position()) - } else if !right_sym.is_number() && left_sym.kind !in [.byteptr, .charptr] { + } else if !right_sym.is_number() && left_sym.kind !in [.byteptr, .charptr, .struct_] { c.error('invalid right operand: $left_sym.name $assign_stmt.op $right_sym.name', right.position()) } else if right is ast.IntegerLiteral { @@ -2513,11 +2513,11 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { } .mult_assign, .div_assign { if !left_sym.is_number() && - !c.table.get_final_type_symbol(left_type_unwrapped).is_int() { + !c.table.get_final_type_symbol(left_type_unwrapped).is_int() && left_sym.kind != .struct_ { c.error('operator $assign_stmt.op.str() not defined on left operand type `$left_sym.name`', left.position()) } else if !right_sym.is_number() && - !c.table.get_final_type_symbol(left_type_unwrapped).is_int() { + !c.table.get_final_type_symbol(left_type_unwrapped).is_int() && left_sym.kind != .struct_ { c.error('operator $assign_stmt.op.str() not defined on right operand type `$right_sym.name`', right.position()) } @@ -2535,6 +2535,33 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { } else {} } + if assign_stmt.op in + [.plus_assign, .minus_assign, .mod_assign, .mult_assign, .div_assign] && + left_sym.kind == .struct_ && right_sym.kind == .struct_ { + extracted_op := match assign_stmt.op { + .plus_assign { '+' } + .minus_assign { '-' } + .div_assign { '/' } + .mod_assign { '%' } + .mult_assign { '*' } + else { 'unknown op' } + } + left_name := c.table.type_to_str(left_type) + if method := left_sym.find_method(extracted_op) { + if method.return_type != left_type { + c.error('operator `$extracted_op` must return `$left_name` to be used as an assignment operator', + assign_stmt.pos) + } + } else { + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('operation `$left_name` $extracted_op `$right_name` does not exist, please define it', + assign_stmt.pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', assign_stmt.pos) + } + } + } if !is_blank_ident && right_sym.kind != .placeholder && left_sym.kind != .interface_ { // Dual sides check (compatibility check) c.check_expected(right_type_unwrapped, left_type_unwrapped) or { diff --git a/vlib/v/checker/tests/method_op_err.out b/vlib/v/checker/tests/method_op_err.out index 0c77048108..db1271b02b 100644 --- a/vlib/v/checker/tests/method_op_err.out +++ b/vlib/v/checker/tests/method_op_err.out @@ -33,23 +33,44 @@ vlib/v/checker/tests/method_op_err.vv:26:1: error: argument cannot be `mut` for | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 27 | return User{} 28 | } -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}) +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}) | ^ - 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: operation `User` < `User` does not exist, please define it - 31 | println(User{3, 4}) - 32 | println(User{3, 4} - Foo{3, 3}) - 33 | println(User{3, 2} < User{2, 4}) + 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: operation `User` < `User` does not exist, please define it + 35 | println(User{3, 4}) + 36 | println(User{3, 4} - Foo{3, 3}) + 37 | println(User{3, 2} < User{2, 4}) | ^ - 34 | println(User{3, 4} < Foo{3, 4}) - 35 | } -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}) + 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}) | ^ - 35 | } + 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 + | ~~ + 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} + | ^ + 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} + | ~~ + 43 | } diff --git a/vlib/v/checker/tests/method_op_err.vv b/vlib/v/checker/tests/method_op_err.vv index a3cb8ff402..40b1b5b44e 100644 --- a/vlib/v/checker/tests/method_op_err.vv +++ b/vlib/v/checker/tests/method_op_err.vv @@ -27,9 +27,17 @@ fn (u User) / (mut u1 User) User { return User{} } +fn (u User) + (u1 User) Foo { + return Foo{a: u.a + u1.a, b: u.b + u1.b} +} + 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}) + mut u := User{3, 4} + u += 12 + u %= User{1, 3} + u += User{2, 3} } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index e9be899d7d..f943209dc5 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -1919,6 +1919,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { '' } mut str_add := false + mut op_overloaded := false if var_type == table.string_type_idx && assign_stmt.op == .plus_assign { if left is ast.IndexExpr { // a[0] += str => `array_set(&a, 0, &(string[]) {string_add(...))})` @@ -1933,6 +1934,22 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { g.is_assign_rhs = true str_add = true } + // Assignment Operator Overloading + if left_sym.kind == .struct_ && + right_sym.kind == .struct_ && assign_stmt.op in + [.plus_assign, .minus_assign, .div_assign, .mult_assign, .mod_assign] { + g.expr(left) + extracted_op := match assign_stmt.op { + .plus_assign { '+' } + .minus_assign { '-' } + .div_assign { '/' } + .mod_assign { '%' } + .mult_assign { '*' } + else { 'unknown op' } + } + g.write(' = ${styp}_${util.replace_op(extracted_op)}(') + op_overloaded = true + } if right_sym.kind == .function && is_decl { if is_inside_ternary && is_decl { g.out.write(tabs[g.indent - g.inside_ternary]) @@ -1969,9 +1986,9 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { if is_decl { g.writeln(';') } - } else if !g.is_array_set && !str_add { + } else if !g.is_array_set && !str_add && !op_overloaded { g.write(' $op ') - } else if str_add { + } else if str_add || op_overloaded { g.write(', ') } mut cloned := false @@ -2049,7 +2066,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { g.write('.data') } } - if str_add { + if str_add || op_overloaded { g.write(')') } if g.is_array_set { 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 38e7e2d222..c66a5d6804 100644 --- a/vlib/v/tests/operator_overloading_with_string_interpolation_test.v +++ b/vlib/v/tests/operator_overloading_with_string_interpolation_test.v @@ -86,4 +86,14 @@ fn test_operator_overloading_with_string_interpolation() { assert e.str() == '{8, 15}' assert f.str() == '{0, 0}' assert g.str() == '{2, 3}' + ///// /// // + mut ad := Vec{2, 4} + ad += Vec{3, 6} + assert ad.str() == '{5, 10}' + ad -= Vec{1, 1} + assert ad.str() == '{4, 9}' + ad *= Vec{2, 2} + assert ad.str() == '{8, 18}' + ad /= Vec{2, 2} + assert ad.str() == '{4, 9}' } diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index 45c5888832..dd9bb71769 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -53,11 +53,11 @@ pub enum Kind { decl_assign // := plus_assign // += minus_assign // -= - div_assign - mult_assign - xor_assign - mod_assign - or_assign + div_assign // /= + mult_assign // *= + xor_assign // ^= + mod_assign // %= + or_assign // |= and_assign right_shift_assign left_shift_assign // {} () []