all: only allow defining `==` and `<` and auto generate `!=`, `>`, `>=` and `<=` (#8520)

pull/8536/head
Swastik Baranwal 2021-02-03 19:48:38 +05:30 committed by GitHub
parent 9dcf673216
commit 7ec116d588
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 88 additions and 152 deletions

View File

@ -27,6 +27,7 @@ from local variables.
- `__offsetof` for low level needs (works like `offsetof` in C). - `__offsetof` for low level needs (works like `offsetof` in C).
- vfmt now preserves empty lines, like gofmt. - vfmt now preserves empty lines, like gofmt.
- Support for compile time environment variables via `$env('ENV_VAR')`. - Support for compile time environment variables via `$env('ENV_VAR')`.
- Allow method declaration of `==` and `<` operators and auto generate `!=`, `>`, `<=` and `>=`.
## V 0.2.1 ## V 0.2.1
*30 Dec 2020* *30 Dec 2020*

View File

@ -3637,7 +3637,8 @@ To improve safety and maintainability, operator overloading is limited:
- `==` and `!=` are self generated by the compiler but can be overriden. - `==` and `!=` are self generated by the compiler but can be overriden.
- Calling other functions inside operator functions is not allowed. - Calling other functions inside operator functions is not allowed.
- Operator functions can't modify their arguments. - 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). - Both arguments must have the same type (just like with all operators in V).
- Assignment operators (`*=`, `+=`, `/=`, etc) - Assignment operators (`*=`, `+=`, `/=`, etc)
are auto generated when the operators are defined though they must return the same type. are auto generated when the operators are defined though they must return the same type.

View File

@ -9,12 +9,6 @@ pub fn (t1 Time) == (t2 Time) bool {
return false 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 // operator `<` returns true if provided time is less than time
[inline] [inline]
pub fn (t1 Time) < (t2 Time) bool { pub fn (t1 Time) < (t2 Time) bool {
@ -24,27 +18,6 @@ pub fn (t1 Time) < (t2 Time) bool {
return false 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. // Time subtract using operator overloading.
[inline] [inline]
pub fn (lhs Time) - (rhs Time) Duration { pub fn (lhs Time) - (rhs Time) Duration {

View File

@ -882,7 +882,7 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
.gt, .lt, .ge, .le { .gt, .lt, .ge, .le {
if left.kind in [.array, .array_fixed] && right.kind in [.array, .array_fixed] { if left.kind in [.array, .array_fixed] && right.kind in [.array, .array_fixed] {
c.error('only `==` and `!=` are defined on arrays', infix_expr.pos) 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())) { if !(left.has_method(infix_expr.op.str()) && right.has_method(infix_expr.op.str())) {
left_name := c.table.type_to_str(left_type) left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_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 { .left_shift {
if left.kind == .array { 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) c.error('.str() methods should have 0 arguments', node.pos)
} }
} }
if node.language == .v && node.is_method if node.language == .v && node.is_method && node.name in ['+', '-', '*', '%', '/', '<', '=='] {
&& node.name in ['+', '-', '*', '%', '/', '<', '>', '==', '!=', '>=', '<='] {
if node.params.len != 2 { if node.params.len != 2 {
c.error('operator methods should have exactly 1 argument', node.pos) c.error('operator methods should have exactly 1 argument', node.pos)
} else { } else {
@ -5636,8 +5643,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
} else if node.receiver.typ != node.params[1].typ { } 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', 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) node.params[1].type_pos)
} else if node.name in ['<', '>', '==', '!=', '>=', '<='] } else if node.name in ['<', '=='] && node.return_type != table.bool_type {
&& node.return_type != table.bool_type {
c.error('operator comparison methods should return `bool`', node.pos) c.error('operator comparison methods should return `bool`', node.pos)
} else if parent_sym.is_primitive() { } else if parent_sym.is_primitive() {
c.error('cannot define operator methods on type alias for `$parent_sym.name`', c.error('cannot define operator methods on type alias for `$parent_sym.name`',

View File

@ -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} 15 | return User{u.a - f.a, u.b-f.a}
16 | } 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 | } 16 | }
17 | 17 |
18 | fn (u User) > (u1 User) User { 18 | fn (mut u User) * (u1 User) User {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~
19 | return User{} 19 | return User{}
20 | } 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 | } 20 | }
21 | 21 |
22 | fn (mut u User) * (u1 User) User { 22 | fn (u User) / (mut u1 User) User {
| ~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 | return User{} 23 | return User{}
24 | } 24 | }
vlib/v/checker/tests/method_op_err.vv:26:1: error: argument cannot be `mut` for operator overloading vlib/v/checker/tests/method_op_err.vv:32:24: error: infix expr: cannot use `Foo` (right expression) as `User`
24 | } 30 | fn main() {
25 | 31 | println(User{3, 4})
26 | fn (u User) / (mut u1 User) User { 32 | println(User{3, 4} - Foo{3, 3})
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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})
| ^ | ^
37 | println(User{3, 2} < User{2, 4}) 33 | println(User{3, 2} < User{2, 4})
38 | println(User{3, 4} < Foo{3, 4}) 34 | println(User{3, 4} < Foo{3, 4})
vlib/v/checker/tests/method_op_err.vv:37:24: error: undefined operation `User` < `User` vlib/v/checker/tests/method_op_err.vv:33:24: error: undefined operation `User` < `User`
35 | println(User{3, 4}) 31 | println(User{3, 4})
36 | println(User{3, 4} - Foo{3, 3}) 32 | println(User{3, 4} - Foo{3, 3})
37 | println(User{3, 2} < User{2, 4}) 33 | println(User{3, 2} < User{2, 4})
| ^ | ^
38 | println(User{3, 4} < Foo{3, 4}) 34 | println(User{3, 4} < Foo{3, 4})
39 | mut u := User{3, 4} 35 | mut u := User{3, 4}
vlib/v/checker/tests/method_op_err.vv:38:24: error: mismatched types `User` and `Foo` vlib/v/checker/tests/method_op_err.vv:34:24: error: mismatched types `User` and `Foo`
36 | println(User{3, 4} - Foo{3, 3}) 32 | println(User{3, 4} - Foo{3, 3})
37 | println(User{3, 2} < User{2, 4}) 33 | println(User{3, 2} < User{2, 4})
38 | println(User{3, 4} < Foo{3, 4}) 34 | println(User{3, 4} < Foo{3, 4})
| ^ | ^
39 | mut u := User{3, 4} 35 | mut u := User{3, 4}
40 | u += 12 36 | u += 12
vlib/v/checker/tests/method_op_err.vv:40:10: error: cannot assign to `u`: expected `User`, not `int literal` vlib/v/checker/tests/method_op_err.vv:36:10: error: cannot assign to `u`: expected `User`, not `int literal`
38 | println(User{3, 4} < Foo{3, 4}) 34 | println(User{3, 4} < Foo{3, 4})
39 | mut u := User{3, 4} 35 | mut u := User{3, 4}
40 | u += 12 36 | u += 12
| ~~ | ~~
41 | u %= User{1, 3} 37 | u %= User{1, 3}
42 | u += User{2, 3} 38 | u += User{2, 3}
vlib/v/checker/tests/method_op_err.vv:41:5: error: operator %= not defined on left operand type `User` vlib/v/checker/tests/method_op_err.vv:37:5: error: operator %= not defined on left operand type `User`
39 | mut u := User{3, 4} 35 | mut u := User{3, 4}
40 | u += 12 36 | u += 12
41 | u %= User{1, 3} 37 | u %= User{1, 3}
| ^ | ^
42 | u += User{2, 3} 38 | u += User{2, 3}
43 | } 39 | }
vlib/v/checker/tests/method_op_err.vv:42:7: error: operator `+` must return `User` to be used as an assignment operator vlib/v/checker/tests/method_op_err.vv:38:7: error: operator `+` must return `User` to be used as an assignment operator
40 | u += 12 36 | u += 12
41 | u %= User{1, 3} 37 | u %= User{1, 3}
42 | u += User{2, 3} 38 | u += User{2, 3}
| ~~ | ~~
43 | } 39 | }

View File

@ -15,10 +15,6 @@ fn (u User) - (f Foo) User {
return User{u.a - f.a, u.b-f.a} 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 { fn (mut u User) * (u1 User) User {
return User{} return User{}
} }

View File

@ -251,8 +251,8 @@ fn (mut g Gen) gen_array_sort(node ast.CallExpr) {
sym := g.table.get_type_symbol(typ) sym := g.table.get_type_symbol(typ)
if !is_reverse && sym.has_method('<') && infix_expr.left.str().len == 1 { 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; }}') 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 { } 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; }}') g.definitions.writeln('\tif (!${styp}__lt(*a, *b)) { return -1; } else { return 1; }}')
} else { } else {
field_type := g.typ(infix_expr.left_type) field_type := g.typ(infix_expr.left_type)
mut left_expr_str := g.write_expr_to_string(infix_expr.left) mut left_expr_str := g.write_expr_to_string(infix_expr.left)

View File

@ -3019,7 +3019,6 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
} }
right_sym := g.table.get_type_symbol(node.right_type) right_sym := g.table.get_type_symbol(node.right_type)
has_eq_overloaded := !left_sym.has_method('==') has_eq_overloaded := !left_sym.has_method('==')
has_ne_overloaded := !left_sym.has_method('!=')
unaliased_right := if right_sym.info is table.Alias { unaliased_right := if right_sym.info is table.Alias {
right_sym.info.parent_type right_sym.info.parent_type
} else { } else {
@ -3174,16 +3173,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
g.expr(node.right) g.expr(node.right)
g.write(')') g.write(')')
} else if node.op in [.eq, .ne] && left_sym.kind == .struct_ && right_sym.kind == .struct_ { } else if node.op in [.eq, .ne] && left_sym.kind == .struct_ && right_sym.kind == .struct_ {
if has_eq_overloaded && !has_ne_overloaded { if !has_eq_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 {
// Define `==` as negation of Autogenerated `!=` // Define `==` as negation of Autogenerated `!=`
styp := g.typ(left_type) styp := g.typ(left_type)
if node.op == .ne { if node.op == .ne {
@ -3192,16 +3182,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
g.write('${styp}__eq(') g.write('${styp}__eq(')
} }
} }
if !has_eq_overloaded && !has_ne_overloaded { if has_eq_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 {
// Auto generate both `==` and `!=` // Auto generate both `==` and `!=`
ptr_typ := g.gen_struct_equality_fn(left_type) ptr_typ := g.gen_struct_equality_fn(left_type)
if node.op == .eq { 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() && 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`. // Do not generate operator overloading with these `right_sym.kind`.
e := right_sym.kind !in [.voidptr, .int_literal, .int] e := right_sym.kind !in [.voidptr, .int_literal, .int]
if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne, .le, .ge] if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && ((a && b && e) || c || d) {
&& ((a && b && e) || c|| d) {
// Overloaded operators // Overloaded operators
g.write(g.typ(if !d { left_type } else { (left_sym.info as table.Alias).parent_type })) g.write(g.typ(if !d { left_type } else { (left_sym.info as table.Alias).parent_type }))
g.write('_') g.write('_')
@ -3385,6 +3365,28 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
g.write(', ') g.write(', ')
g.expr(node.right) g.expr(node.right)
g.write(')') 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 { } else {
need_par := node.op in [.amp, .pipe, .xor] // `x & y == 0` => `(x & y) == 0` in C need_par := node.op in [.amp, .pipe, .xor] // `x & y == 0` => `(x & y) == 0` in C
if need_par { if need_par {

View File

@ -56,7 +56,7 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) {
} }
// //
mut name := node.name mut name := node.name
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '<=', '>='] { if name in ['+', '-', '*', '/', '%', '<', '=='] {
name = util.replace_op(name) name = util.replace_op(name)
} }
if node.is_method { if node.is_method {

View File

@ -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] if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && p.peek_tok.kind == .lpar {
&& p.peek_tok.kind == .lpar {
name = p.tok.kind.str() // op_to_fn_name() name = p.tok.kind.str() // op_to_fn_name()
if rec_type == table.void_type { if rec_type == table.void_type {
p.error_with_pos('cannot use operator overloading with normal functions', p.error_with_pos('cannot use operator overloading with normal functions',
p.tok.position()) p.tok.position())
} }
p.next() 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> // <T>
generic_params := p.parse_generic_params() generic_params := p.parse_generic_params()

View File

@ -27,10 +27,6 @@ fn (a Vec) % (b Vec) Vec {
return Vec{a.x % b.x, a.y % b.y} 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 { fn (a Vec) < (b Vec) bool {
return a.x < b.x && a.y < b.y 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 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() { fn test_operator_overloading_with_string_interpolation() {
a := Vec{2, 3} a := Vec{2, 3}
b := Vec{4, 5} b := Vec{4, 5}

View File

@ -11,10 +11,6 @@ fn (p Parent) < (p1 Parent) bool {
return p.name < p1.name 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() { fn test_sorting_by_different_criteria_in_same_function() {
mut arr := [ mut arr := [
Parent{Child{0.2}, 'def'}, Parent{Child{0.2}, 'def'},

View File

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

View File

@ -382,9 +382,6 @@ pub fn replace_op(s string) string {
} else { } else {
suffix := match s { suffix := match s {
'==' { '_eq' } '==' { '_eq' }
'!=' { '_ne' }
'<=' { '_le' }
'>=' { '_ge' }
else { '' } else { '' }
} }
return s[..s.len - 2] + suffix return s[..s.len - 2] + suffix