all: only allow defining `==` and `<` and auto generate `!=`, `>`, `>=` and `<=` (#8520)
parent
9dcf673216
commit
7ec116d588
|
@ -27,6 +27,7 @@ from local variables.
|
|||
- `__offsetof` for low level needs (works like `offsetof` in C).
|
||||
- vfmt now preserves empty lines, like gofmt.
|
||||
- Support for compile time environment variables via `$env('ENV_VAR')`.
|
||||
- Allow method declaration of `==` and `<` operators and auto generate `!=`, `>`, `<=` and `>=`.
|
||||
|
||||
## V 0.2.1
|
||||
*30 Dec 2020*
|
||||
|
|
|
@ -3637,7 +3637,8 @@ To improve safety and maintainability, operator overloading is limited:
|
|||
- `==` and `!=` are self generated by the compiler but can be overriden.
|
||||
- Calling other functions inside operator functions is not allowed.
|
||||
- Operator functions can't modify their arguments.
|
||||
- When using `<`, `>`, `>=`, `<=`, `==` and `!=` operators, the return type must be `bool`.
|
||||
- When using `<` and `==` operators, the return type must be `bool`.
|
||||
- `!=`, `>`, `<=` and `>=` are auto generated when `==` and `<` are defined.
|
||||
- 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.
|
||||
|
|
|
@ -9,12 +9,6 @@ pub fn (t1 Time) == (t2 Time) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// operator `!=` returns true if provided time is not equal to time
|
||||
[inline]
|
||||
pub fn (t1 Time) != (t2 Time) bool {
|
||||
return !(t1 == t2)
|
||||
}
|
||||
|
||||
// operator `<` returns true if provided time is less than time
|
||||
[inline]
|
||||
pub fn (t1 Time) < (t2 Time) bool {
|
||||
|
@ -24,27 +18,6 @@ pub fn (t1 Time) < (t2 Time) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// operator `<=` returns true if provided time is less or equal to time
|
||||
[inline]
|
||||
pub fn (t1 Time) <= (t2 Time) bool {
|
||||
return t1 < t2 || t1 == t2
|
||||
}
|
||||
|
||||
// operator `>` returns true if provided time is greater than time
|
||||
[inline]
|
||||
pub fn (t1 Time) > (t2 Time) bool {
|
||||
if t1.unix > t2.unix || (t1.unix == t2.unix && t1.microsecond > t2.microsecond) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// operator `>=` returns true if provided time is greater or equal to time
|
||||
[inline]
|
||||
pub fn (t1 Time) >= (t2 Time) bool {
|
||||
return t1 > t2 || t1 == t2
|
||||
}
|
||||
|
||||
// Time subtract using operator overloading.
|
||||
[inline]
|
||||
pub fn (lhs Time) - (rhs Time) Duration {
|
||||
|
|
|
@ -882,7 +882,7 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
|
|||
.gt, .lt, .ge, .le {
|
||||
if left.kind in [.array, .array_fixed] && right.kind in [.array, .array_fixed] {
|
||||
c.error('only `==` and `!=` are defined on arrays', infix_expr.pos)
|
||||
} else if left.kind == .struct_ && right.kind == .struct_ {
|
||||
} else if left.kind == .struct_ && right.kind == .struct_ && infix_expr.op in [.eq, .lt] {
|
||||
if !(left.has_method(infix_expr.op.str()) && right.has_method(infix_expr.op.str())) {
|
||||
left_name := c.table.type_to_str(left_type)
|
||||
right_name := c.table.type_to_str(right_type)
|
||||
|
@ -894,6 +894,14 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
|
|||
}
|
||||
}
|
||||
}
|
||||
if left.kind == .struct_ && right.kind == .struct_ {
|
||||
if !left.has_method('<') && infix_expr.op in [.ge, .le] {
|
||||
c.error('cannot use `$infix_expr.op` as `<` operator method is not defined',
|
||||
infix_expr.pos)
|
||||
} else if !left.has_method('<') && infix_expr.op == .gt {
|
||||
c.error('cannot use `>` as `<=` operator method is not defined', infix_expr.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
.left_shift {
|
||||
if left.kind == .array {
|
||||
|
@ -5617,8 +5625,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
|||
c.error('.str() methods should have 0 arguments', node.pos)
|
||||
}
|
||||
}
|
||||
if node.language == .v && node.is_method
|
||||
&& node.name in ['+', '-', '*', '%', '/', '<', '>', '==', '!=', '>=', '<='] {
|
||||
if node.language == .v && node.is_method && node.name in ['+', '-', '*', '%', '/', '<', '=='] {
|
||||
if node.params.len != 2 {
|
||||
c.error('operator methods should have exactly 1 argument', node.pos)
|
||||
} else {
|
||||
|
@ -5636,8 +5643,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
|||
} else if node.receiver.typ != node.params[1].typ {
|
||||
c.error('expected `$receiver_sym.name` not `$param_sym.name` - both operands must be the same type for operator overloading',
|
||||
node.params[1].type_pos)
|
||||
} else if node.name in ['<', '>', '==', '!=', '>=', '<=']
|
||||
&& node.return_type != table.bool_type {
|
||||
} 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`',
|
||||
|
|
|
@ -12,65 +12,58 @@ vlib/v/checker/tests/method_op_err.vv:14:18: error: expected `User` not `Foo` -
|
|||
| ~~~
|
||||
15 | return User{u.a - f.a, u.b-f.a}
|
||||
16 | }
|
||||
vlib/v/checker/tests/method_op_err.vv:18:1: error: operator comparison methods should return `bool`
|
||||
vlib/v/checker/tests/method_op_err.vv:18:9: error: receiver cannot be `mut` for operator overloading
|
||||
16 | }
|
||||
17 |
|
||||
18 | fn (u User) > (u1 User) User {
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
18 | fn (mut u User) * (u1 User) User {
|
||||
| ~~~~~~
|
||||
19 | return User{}
|
||||
20 | }
|
||||
vlib/v/checker/tests/method_op_err.vv:22:9: error: receiver cannot be `mut` for operator overloading
|
||||
vlib/v/checker/tests/method_op_err.vv:22:1: error: argument cannot be `mut` for operator overloading
|
||||
20 | }
|
||||
21 |
|
||||
22 | fn (mut u User) * (u1 User) User {
|
||||
| ~~~~~~
|
||||
22 | fn (u User) / (mut u1 User) User {
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
23 | return User{}
|
||||
24 | }
|
||||
vlib/v/checker/tests/method_op_err.vv:26:1: error: argument cannot be `mut` for operator overloading
|
||||
24 | }
|
||||
25 |
|
||||
26 | fn (u User) / (mut u1 User) User {
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
27 | return User{}
|
||||
28 | }
|
||||
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})
|
||||
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})
|
||||
| ^
|
||||
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: undefined operation `User` < `User`
|
||||
35 | println(User{3, 4})
|
||||
36 | println(User{3, 4} - Foo{3, 3})
|
||||
37 | println(User{3, 2} < User{2, 4})
|
||||
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: undefined operation `User` < `User`
|
||||
31 | println(User{3, 4})
|
||||
32 | println(User{3, 4} - Foo{3, 3})
|
||||
33 | println(User{3, 2} < User{2, 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})
|
||||
34 | println(User{3, 4} < Foo{3, 4})
|
||||
35 | mut u := User{3, 4}
|
||||
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})
|
||||
| ^
|
||||
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
|
||||
35 | mut u := User{3, 4}
|
||||
36 | u += 12
|
||||
vlib/v/checker/tests/method_op_err.vv:36:10: error: cannot assign to `u`: expected `User`, not `int literal`
|
||||
34 | println(User{3, 4} < Foo{3, 4})
|
||||
35 | mut u := User{3, 4}
|
||||
36 | 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}
|
||||
37 | u %= User{1, 3}
|
||||
38 | u += User{2, 3}
|
||||
vlib/v/checker/tests/method_op_err.vv:37:5: error: operator %= not defined on left operand type `User`
|
||||
35 | mut u := User{3, 4}
|
||||
36 | u += 12
|
||||
37 | 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}
|
||||
38 | u += User{2, 3}
|
||||
39 | }
|
||||
vlib/v/checker/tests/method_op_err.vv:38:7: error: operator `+` must return `User` to be used as an assignment operator
|
||||
36 | u += 12
|
||||
37 | u %= User{1, 3}
|
||||
38 | u += User{2, 3}
|
||||
| ~~
|
||||
43 | }
|
||||
39 | }
|
||||
|
|
|
@ -15,10 +15,6 @@ fn (u User) - (f Foo) User {
|
|||
return User{u.a - f.a, u.b-f.a}
|
||||
}
|
||||
|
||||
fn (u User) > (u1 User) User {
|
||||
return User{}
|
||||
}
|
||||
|
||||
fn (mut u User) * (u1 User) User {
|
||||
return User{}
|
||||
}
|
||||
|
|
|
@ -251,8 +251,8 @@ fn (mut g Gen) gen_array_sort(node ast.CallExpr) {
|
|||
sym := g.table.get_type_symbol(typ)
|
||||
if !is_reverse && sym.has_method('<') && infix_expr.left.str().len == 1 {
|
||||
g.definitions.writeln('\tif (${styp}__lt(*a, *b)) { return -1; } else { return 1; }}')
|
||||
} else if is_reverse && sym.has_method('>') && infix_expr.left.str().len == 1 {
|
||||
g.definitions.writeln('\tif (${styp}__gt(*a, *b)) { return -1; } else { return 1; }}')
|
||||
} else if is_reverse && sym.has_method('<') && infix_expr.left.str().len == 1 {
|
||||
g.definitions.writeln('\tif (!${styp}__lt(*a, *b)) { return -1; } else { return 1; }}')
|
||||
} else {
|
||||
field_type := g.typ(infix_expr.left_type)
|
||||
mut left_expr_str := g.write_expr_to_string(infix_expr.left)
|
||||
|
|
|
@ -3019,7 +3019,6 @@ 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.info is table.Alias {
|
||||
right_sym.info.parent_type
|
||||
} else {
|
||||
|
@ -3174,16 +3173,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
|||
g.expr(node.right)
|
||||
g.write(')')
|
||||
} else if node.op in [.eq, .ne] && left_sym.kind == .struct_ && right_sym.kind == .struct_ {
|
||||
if has_eq_overloaded && !has_ne_overloaded {
|
||||
// Define `!=` as negation of Autogenerated `==`
|
||||
styp := g.typ(left_type)
|
||||
if node.op == .eq {
|
||||
g.write('!${styp}__ne(')
|
||||
} else if node.op == .ne {
|
||||
g.write('${styp}__ne(')
|
||||
}
|
||||
}
|
||||
if !has_eq_overloaded && has_ne_overloaded {
|
||||
if !has_eq_overloaded {
|
||||
// Define `==` as negation of Autogenerated `!=`
|
||||
styp := g.typ(left_type)
|
||||
if node.op == .ne {
|
||||
|
@ -3192,16 +3182,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
|||
g.write('${styp}__eq(')
|
||||
}
|
||||
}
|
||||
if !has_eq_overloaded && !has_ne_overloaded {
|
||||
// Overload both User defined `==` and `!=`
|
||||
styp := g.typ(left_type)
|
||||
if node.op == .eq {
|
||||
g.write('${styp}__eq(')
|
||||
} else if node.op == .ne {
|
||||
g.write('${styp}__ne(')
|
||||
}
|
||||
}
|
||||
if has_eq_overloaded && has_ne_overloaded {
|
||||
if has_eq_overloaded {
|
||||
// Auto generate both `==` and `!=`
|
||||
ptr_typ := g.gen_struct_equality_fn(left_type)
|
||||
if node.op == .eq {
|
||||
|
@ -3374,8 +3355,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
|||
&& g.typ((left_sym.info as table.Alias).parent_type).split('__').last()[0].is_capital()
|
||||
// Do not generate operator overloading with these `right_sym.kind`.
|
||||
e := right_sym.kind !in [.voidptr, .int_literal, .int]
|
||||
if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne, .le, .ge]
|
||||
&& ((a && b && e) || c|| d) {
|
||||
if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && ((a && b && e) || c || d) {
|
||||
// Overloaded operators
|
||||
g.write(g.typ(if !d { left_type } else { (left_sym.info as table.Alias).parent_type }))
|
||||
g.write('_')
|
||||
|
@ -3385,6 +3365,28 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
|||
g.write(', ')
|
||||
g.expr(node.right)
|
||||
g.write(')')
|
||||
} else if node.op in [.ne, .gt, .ge, .le] && ((a && b && e) || c || d) {
|
||||
typ := g.typ(if !d { left_type } else { (left_sym.info as table.Alias).parent_type })
|
||||
g.write('!$typ')
|
||||
g.write('_')
|
||||
if node.op == .ne {
|
||||
g.write('_eq')
|
||||
} else if node.op in [.ge, .le, .gt] {
|
||||
g.write('_lt')
|
||||
}
|
||||
if node.op == .le {
|
||||
g.write('(')
|
||||
g.expr(node.right)
|
||||
g.write(', ')
|
||||
g.expr(node.left)
|
||||
g.write(')')
|
||||
} else {
|
||||
g.write('(')
|
||||
g.expr(node.left)
|
||||
g.write(', ')
|
||||
g.expr(node.right)
|
||||
g.write(')')
|
||||
}
|
||||
} else {
|
||||
need_par := node.op in [.amp, .pipe, .xor] // `x & y == 0` => `(x & y) == 0` in C
|
||||
if need_par {
|
||||
|
|
|
@ -56,7 +56,7 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) {
|
|||
}
|
||||
//
|
||||
mut name := node.name
|
||||
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '<=', '>='] {
|
||||
if name in ['+', '-', '*', '/', '%', '<', '=='] {
|
||||
name = util.replace_op(name)
|
||||
}
|
||||
if node.is_method {
|
||||
|
|
|
@ -275,14 +275,16 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
|||
}
|
||||
}
|
||||
}
|
||||
if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt, .eq, .ne, .le, .ge]
|
||||
&& p.peek_tok.kind == .lpar {
|
||||
if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && p.peek_tok.kind == .lpar {
|
||||
name = p.tok.kind.str() // op_to_fn_name()
|
||||
if rec_type == table.void_type {
|
||||
p.error_with_pos('cannot use operator overloading with normal functions',
|
||||
p.tok.position())
|
||||
}
|
||||
p.next()
|
||||
} else if p.tok.kind in [.ne, .gt, .ge, .le] && p.peek_tok.kind == .lpar {
|
||||
p.error_with_pos('cannot overload `!=`, `>`, `<=` and `>=` as they are auto generated with `==` and`<`',
|
||||
p.tok.position())
|
||||
}
|
||||
// <T>
|
||||
generic_params := p.parse_generic_params()
|
||||
|
|
|
@ -27,10 +27,6 @@ fn (a Vec) % (b Vec) Vec {
|
|||
return Vec{a.x % b.x, a.y % b.y}
|
||||
}
|
||||
|
||||
fn (a Vec) > (b Vec) bool {
|
||||
return a.x > b.x && a.y > b.y
|
||||
}
|
||||
|
||||
fn (a Vec) < (b Vec) bool {
|
||||
return a.x < b.x && a.y < b.y
|
||||
}
|
||||
|
@ -39,18 +35,6 @@ fn (a Vec) == (b Vec) bool {
|
|||
return a.x == b.y && a.y == b.x
|
||||
}
|
||||
|
||||
fn (a Vec) != (b Vec) bool {
|
||||
return !(a == b)
|
||||
}
|
||||
|
||||
fn (a Vec) >= (b Vec) bool {
|
||||
return a > b || a == b
|
||||
}
|
||||
|
||||
fn (a Vec) <= (b Vec) bool {
|
||||
return a < b || a == b
|
||||
}
|
||||
|
||||
fn test_operator_overloading_with_string_interpolation() {
|
||||
a := Vec{2, 3}
|
||||
b := Vec{4, 5}
|
||||
|
|
|
@ -11,10 +11,6 @@ fn (p Parent) < (p1 Parent) bool {
|
|||
return p.name < p1.name
|
||||
}
|
||||
|
||||
fn (p Parent) > (p1 Parent) bool {
|
||||
return p.name > p1.name
|
||||
}
|
||||
|
||||
fn test_sorting_by_different_criteria_in_same_function() {
|
||||
mut arr := [
|
||||
Parent{Child{0.2}, 'def'},
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
struct User {
|
||||
name string
|
||||
num int
|
||||
}
|
||||
|
||||
fn (u User) != (u1 User) bool {
|
||||
return u.num != u1.num
|
||||
}
|
||||
|
||||
fn test_eq_op() {
|
||||
u1 := User{'Joe', 23}
|
||||
u2 := User{'Joe', 24}
|
||||
assert u1 != u2
|
||||
assert (u1 == u2) == false
|
||||
}
|
|
@ -382,9 +382,6 @@ pub fn replace_op(s string) string {
|
|||
} else {
|
||||
suffix := match s {
|
||||
'==' { '_eq' }
|
||||
'!=' { '_ne' }
|
||||
'<=' { '_le' }
|
||||
'>=' { '_ge' }
|
||||
else { '' }
|
||||
}
|
||||
return s[..s.len - 2] + suffix
|
||||
|
|
Loading…
Reference in New Issue