cgen: allow assignment operators for type aliases (#8086)

pull/8117/head
Swastik Baranwal 2021-01-15 06:57:19 +05:30 committed by GitHub
parent aeddd5b559
commit 0ca36aafe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 18 deletions

View File

@ -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() {

View File

@ -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)
}
}
}

View File

@ -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 | }

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {