cgen: implement argument operator overloading (#8067)

pull/8077/head
Swastik Baranwal 2021-01-13 08:01:14 +05:30 committed by GitHub
parent 0e490766df
commit 1e853b0efc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 118 additions and 29 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 // {} () []