cgen: implement overriding of `!=` and `==` (#7837)

pull/7844/head
Swastik Baranwal 2021-01-03 20:49:02 +05:30 committed by GitHub
parent b7f83e2f50
commit 9033099676
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 49 additions and 22 deletions

View File

@ -2,6 +2,7 @@
*Not yet released*
- `vweb` now uses struct embedding: `app.vweb.text('hello') => app.text('hello')`.
- Consts can now be declared outside of `const()` blocks: `const x = 0`.
- Allow overloading of `>`, `<`, `!=` and `==` operators.
## V 0.2.1
- Hashmap bootstrapping fixes.

View File

@ -3166,11 +3166,11 @@ operator overloading is an important feature to have in order to improve readabi
To improve safety and maintainability, operator overloading is limited:
- It's only possible to overload `+, -, *, /, %, <, >` operators.
- `==` and `!=` are self generated by the compiler.
- It's only possible to overload `+, -, *, /, %, <, >, ==, !=` operators.
- `==` 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 `>`, the return type must be `bool`.
- When using `<`, `>`, `==` and `!=` operators, the return type must be `bool`.
- Both arguments must have the same type (just like with all operators in V).
## Inline assembly

View File

@ -52,7 +52,7 @@ pub fn (node &FnDecl) stringify(t &table.Table, cur_mod string, m2a map[string]s
}
}
f.write('fn $receiver$name')
if name in ['+', '-', '*', '/', '%', '<', '>'] {
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!='] {
f.write(' ')
}
if node.is_generic {

View File

@ -4980,7 +4980,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 {
@ -4992,7 +4992,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
} else {
if node.receiver.typ != node.params[1].typ {
c.error('both sides of an operator must be the same type', node.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)
}
}

View File

@ -2868,6 +2868,8 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
return
}
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
} else {
@ -3006,7 +3008,8 @@ 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_ {
} else if node.op in [.eq, .ne] &&
left_sym.kind == .struct_ && right_sym.kind == .struct_ && has_eq_overloaded && has_ne_overloaded {
ptr_typ := g.gen_struct_equality_fn(left_type)
if node.op == .eq {
g.write('${ptr_typ}_struct_eq(')
@ -3154,13 +3157,16 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
g.write(')')
} else {
a := (left_sym.name[0].is_capital() || left_sym.name.contains('.')) &&
left_sym.kind != .enum_
left_sym.kind !in [.enum_, .function, .interface_, .sum_type]
b := left_sym.kind != .alias
c := left_sym.kind == .alias && (left_sym.info as table.Alias).language == .c
// Check if aliased type is a struct
d := !b &&
g.typ((left_sym.info as table.Alias).parent_type).split('__').last()[0].is_capital()
if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt] && ((a && b) || c || d) {
// Do not generate operator overloading with these `right_sym.kind`.
e := right_sym.kind !in [.voidptr, .any_int, .int]
if node.op in [.plus, .minus, .mul, .div, .mod, .lt, .gt, .eq, .ne] &&
((a && b && e) || c || d) {
// Overloaded operators
g.write(g.typ(if !d {
left_type

View File

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

View File

@ -270,7 +270,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
}
}
}
if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt] && p.peek_tok.kind == .lpar {
if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .gt, .lt, .eq, .ne] &&
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',

View File

@ -35,6 +35,14 @@ 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.y && a.y == b.x
}
fn (a Vec) != (b Vec) bool {
return !(a == b)
}
fn test_operator_overloading_with_string_interpolation() {
a := Vec{2, 3}
b := Vec{4, 5}
@ -60,6 +68,8 @@ fn test_operator_overloading_with_string_interpolation() {
////// /////
assert b > a == true
assert a < b == true
assert (Vec{2, 3} == Vec{3, 2}) == true
assert (Vec{2, 3} != Vec{3, 2}) == false
////// /////
assert c.str() == '{6, 8}'
assert d.str() == '{-2, -2}'

View File

@ -278,18 +278,27 @@ pub fn imax(a int, b int) int {
}
pub fn replace_op(s string) string {
last_char := s[s.len - 1]
suffix := match last_char {
`+` { '_plus' }
`-` { '_minus' }
`*` { '_mult' }
`/` { '_div' }
`%` { '_mod' }
`<` { '_lt' }
`>` { '_gt' }
else { '' }
if s.len == 1 {
last_char := s[s.len - 1]
suffix := match last_char {
`+` { '_plus' }
`-` { '_minus' }
`*` { '_mult' }
`/` { '_div' }
`%` { '_mod' }
`<` { '_lt' }
`>` { '_gt' }
else { '' }
}
return s[..s.len - 1] + suffix
} else {
suffix := match s {
'==' { '_eq' }
'!=' { '_ne' }
else { '' }
}
return s[..s.len - 2] + suffix
}
return s[..s.len - 1] + suffix
}
pub fn join_env_vflags_and_os_args() []string {