From 0ca36aafe289c811162fbd7bd6ed68927f58135c Mon Sep 17 00:00:00 2001 From: Swastik Baranwal Date: Fri, 15 Jan 2021 06:57:19 +0530 Subject: [PATCH] cgen: allow assignment operators for type aliases (#8086) --- vlib/math/big/big_test.v | 16 ++++++-- vlib/v/checker/checker.v | 26 ++++++++---- vlib/v/checker/tests/method_op_alias_err.out | 42 ++++++++++++++++++++ vlib/v/checker/tests/method_op_alias_err.vv | 18 +++++++++ vlib/v/gen/cgen.v | 17 ++++---- vlib/v/table/types.v | 10 +++++ 6 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 vlib/v/checker/tests/method_op_alias_err.out create mode 100644 vlib/v/checker/tests/method_op_alias_err.vv diff --git a/vlib/math/big/big_test.v b/vlib/math/big/big_test.v index 9126d73e1f..005b67d1b2 100644 --- a/vlib/math/big/big_test.v +++ b/vlib/math/big/big_test.v @@ -24,26 +24,30 @@ fn test_from_u64() { } fn test_plus() { - a := big.from_u64(2) + mut a := big.from_u64(2) b := big.from_u64(3) c := a + b assert c.hexstr() == '5' assert (big.from_u64(1024) + big.from_u64(1024)).hexstr() == '800' + a += b + assert a.hexstr() == '5' } fn test_minus() { a := big.from_u64(2) - b := big.from_u64(3) + mut b := big.from_u64(3) c := b - a assert c.hexstr() == '1' e := big.from_u64(1024) ee := e - e assert ee.hexstr() == '0' + b -= a + assert b.hexstr() == '1' } fn test_divide() { a := big.from_u64(2) - b := big.from_u64(3) + mut b := big.from_u64(3) c := b / a assert c.hexstr() == '1' assert (b % a).hexstr() == '1' @@ -52,10 +56,12 @@ fn test_divide() { assert ee.hexstr() == '1' assert (e / a).hexstr() == '200' assert (e / (a * a)).hexstr() == '100' + b /= a + assert b.hexstr() == '1' } fn test_multiply() { - a := big.from_u64(2) + mut a := big.from_u64(2) b := big.from_u64(3) c := b * a assert c.hexstr() == '6' @@ -69,6 +75,8 @@ fn test_multiply() { assert e8.hexstr() == '100000000000000000000' assert e9.hexstr() == '100000000000000000001' assert d.hexstr() == '60000000000000000000c00000000000000000018' + a *= b + assert a.hexstr() == '6' } fn test_mod() { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index a5e47dadf9..7bcfdee534 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2496,10 +2496,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, .struct_] { + } else if !left_sym.is_number() && left_sym.kind !in [.byteptr, .charptr, .struct_, .alias] { 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, .struct_] { + } else if !right_sym.is_number() && left_sym.kind !in [.byteptr, .charptr, .struct_, .alias] { c.error('invalid right operand: $left_sym.name $assign_stmt.op $right_sym.name', right.position()) } else if right is ast.IntegerLiteral { @@ -2515,11 +2515,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() && left_sym.kind != .struct_ { + !c.table.get_final_type_symbol(left_type_unwrapped).is_int() && left_sym.kind !in [.struct_, .alias] { 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() && left_sym.kind != .struct_ { + !c.table.get_final_type_symbol(left_type_unwrapped).is_int() && left_sym.kind !in [.struct_, .alias] { c.error('operator $assign_stmt.op.str() not defined on right operand type `$right_sym.name`', right.position()) } @@ -2539,7 +2539,13 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { } if assign_stmt.op in [.plus_assign, .minus_assign, .mod_assign, .mult_assign, .div_assign] && - left_sym.kind == .struct_ && right_sym.kind == .struct_ { + ((left_sym.kind == .struct_ && right_sym.kind == .struct_) || left_sym.kind == .alias) { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + parent_sym := c.table.get_final_type_symbol(left_type) + if left_sym.kind == .alias && right_sym.kind != .alias { + c.error('mismatched types `$left_name` and `$right_name`', assign_stmt.pos) + } extracted_op := match assign_stmt.op { .plus_assign { '+' } .minus_assign { '-' } @@ -2548,14 +2554,16 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { .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 parent_sym.is_primitive() { + c.error('cannot use operator methods on type alias for `$parent_sym.name`', + assign_stmt.pos) + } if left_name == right_name { c.error('operation `$left_name` $extracted_op `$right_name` does not exist, please define it', assign_stmt.pos) @@ -5166,6 +5174,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { c.error('operator methods are only allowed for struct and type alias', node.pos) } else { + parent_sym := c.table.get_final_type_symbol(node.receiver.typ) if node.rec_mut { c.error('receiver cannot be `mut` for operator overloading', node.receiver_pos) } else if node.params[1].is_mut { @@ -5175,6 +5184,9 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } 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`', + node.pos) } } } diff --git a/vlib/v/checker/tests/method_op_alias_err.out b/vlib/v/checker/tests/method_op_alias_err.out new file mode 100644 index 0000000000..1b800807d5 --- /dev/null +++ b/vlib/v/checker/tests/method_op_alias_err.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/method_op_alias_err.vv:4:1: error: both sides of an operator must be the same type + 2 | type Foo2 = string + 3 | + 4 | fn (f Foo) + (f1 Foo2) Foo2 { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 5 | return Foo2(f + f1) + 6 | } +vlib/v/checker/tests/method_op_alias_err.vv:5:19: error: infix expr: cannot use `string` (right expression) as `string` + 3 | + 4 | fn (f Foo) + (f1 Foo2) Foo2 { + 5 | return Foo2(f + f1) + | ^ + 6 | } + 7 | +vlib/v/checker/tests/method_op_alias_err.vv:8:1: error: cannot define operator methods on type alias for `string` + 6 | } + 7 | + 8 | fn (f Foo) * (f1 Foo) Foo { + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + 9 | return Foo(f + f1) + 10 | } +vlib/v/checker/tests/method_op_alias_err.vv:14:6: error: mismatched types `Foo` and `string` + 12 | fn main() { + 13 | mut f := Foo('fg') + 14 | f += 'fg' + | ~~ + 15 | f *= Foo2('2') + 16 | f -= Foo('fo') +vlib/v/checker/tests/method_op_alias_err.vv:15:9: error: cannot assign to `f`: expected `Foo`, not `Foo2` + 13 | mut f := Foo('fg') + 14 | f += 'fg' + 15 | f *= Foo2('2') + | ~~~~~~~~~ + 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') + | ~~ + 17 | println(f) + 18 | } diff --git a/vlib/v/checker/tests/method_op_alias_err.vv b/vlib/v/checker/tests/method_op_alias_err.vv new file mode 100644 index 0000000000..d5b4ecd261 --- /dev/null +++ b/vlib/v/checker/tests/method_op_alias_err.vv @@ -0,0 +1,18 @@ +type Foo = string +type Foo2 = string + +fn (f Foo) + (f1 Foo2) Foo2 { + return Foo2(f + f1) +} + +fn (f Foo) * (f1 Foo) Foo { + return Foo(f + f1) +} + +fn main() { + mut f := Foo('fg') + f += 'fg' + f *= Foo2('2') + f -= Foo('fo') + println(f) +} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index a5c5a4af76..69f25a7352 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -1953,10 +1953,10 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { 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) + if ((left_sym.kind == .struct_ && + right_sym.kind == .struct_) || (left_sym.kind == .alias && + right_sym.kind == .alias)) && + assign_stmt.op in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .mod_assign] { extracted_op := match assign_stmt.op { .plus_assign { '+' } .minus_assign { '-' } @@ -1965,6 +1965,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { .mult_assign { '*' } else { 'unknown op' } } + g.expr(left) g.write(' = ${styp}_${util.replace_op(extracted_op)}(') op_overloaded = true } @@ -2514,7 +2515,9 @@ fn (mut g Gen) expr(node ast.Expr) { } else { styp := g.typ(node.typ) mut cast_label := '' - if sym.kind != .alias || (sym.info as table.Alias).parent_type != node.expr_type { + // `table.string_type` is done for MSVC's bug + if sym.kind != .alias || + (sym.info as table.Alias).parent_type !in [node.expr_type, table.string_type] { cast_label = '($styp)' } g.write('(${cast_label}(') @@ -2943,8 +2946,8 @@ 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.kind == .alias { - (right_sym.info as table.Alias).parent_type + unaliased_right := if right_sym.info is table.Alias { + right_sym.info.parent_type } else { node.right_type } diff --git a/vlib/v/table/types.v b/vlib/v/table/types.v index 864cb2ad8b..fa2a64695f 100644 --- a/vlib/v/table/types.v +++ b/vlib/v/table/types.v @@ -546,11 +546,21 @@ pub fn (t &TypeSymbol) is_float() bool { return t.kind in [.f32, .f64, .float_literal] } +[inline] +pub fn (t &TypeSymbol) is_string() bool { + return t.kind in [.string, .ustring] +} + [inline] pub fn (t &TypeSymbol) is_number() bool { return t.is_int() || t.is_float() } +[inline] +pub fn (t &TypeSymbol) is_primitive() bool { + return t.is_number() || t.is_pointer() || t.is_string() +} + // for debugging/errors only, perf is not an issue pub fn (k Kind) str() string { k_str := match k {