cgen: allow assignment operators for type aliases (#8086)
							parent
							
								
									aeddd5b559
								
							
						
					
					
						commit
						0ca36aafe2
					
				| 
						 | 
				
			
			@ -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() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 | }
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue