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

@ -26,7 +26,8 @@
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')`.
- 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*

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

View File

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

View File

@ -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`',

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

View File

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

View File

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

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

View File

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

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]
&& 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()

View File

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

View File

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

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 {
suffix := match s {
'==' { '_eq' }
'!=' { '_ne' }
'<=' { '_le' }
'>=' { '_ge' }
else { '' }
}
return s[..s.len - 2] + suffix