checker: improve type check for arithmetic assignment op and fix error pos

pull/4529/head
Ruofan XU 2020-04-21 02:56:05 +08:00 committed by GitHub
parent dbbb27ef8f
commit cdb1b0344c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 116 additions and 60 deletions

View File

@ -751,6 +751,7 @@ fn (expr Expr) position() token.Position {
CharLiteral { return it.pos } CharLiteral { return it.pos }
EnumVal { return it.pos } EnumVal { return it.pos }
FloatLiteral { return it.pos } FloatLiteral { return it.pos }
Ident { return it.pos }
IfExpr { return it.pos } IfExpr { return it.pos }
// ast.IfGuardExpr { } // ast.IfGuardExpr { }
IndexExpr { return it.pos } IndexExpr { return it.pos }

View File

@ -364,43 +364,50 @@ fn (c mut Checker) assign_expr(assign_expr mut ast.AssignExpr) {
} }
else {} else {}
} }
// Single side check
match assign_expr.op {
.assign { } // No need to do single side check for =. But here put it first for speed.
.plus_assign {
if !left.is_number() && left_type != table.string_type && !left.is_pointer() {
c.error('operator += not defined on left operand type `$left.name`', assign_expr.left.position())
}
else if !right.is_number() && right_type != table.string_type && !right.is_pointer() {
c.error('operator += not defined on right operand type `$right.name`', assign_expr.val.position())
}
}
.minus_assign {
if !left.is_number() && !left.is_pointer() {
c.error('operator -= not defined on left operand type `$left.name`', assign_expr.left.position())
}
else if !right.is_number() && !right.is_pointer() {
c.error('operator -= not defined on right operand type `$right.name`', assign_expr.val.position())
}
}
.mult_assign, .div_assign {
if !left.is_number() {
c.error('operator ${assign_expr.op.str()} not defined on left operand type `$left.name`', assign_expr.left.position())
}
else if !right.is_number() {
c.error('operator ${assign_expr.op.str()} not defined on right operand type `$right.name`', assign_expr.val.position())
}
}
.and_assign, .or_assign, .xor_assign, .mod_assign, .left_shift_assign, .right_shift_assign {
if !left.is_int() {
c.error('operator ${assign_expr.op.str()} not defined on left operand type `$left.name`', assign_expr.left.position())
}
else if !right.is_int() {
c.error('operator ${assign_expr.op.str()} not defined on right operand type `$right.name`', assign_expr.val.position())
}
}
else { }
}
// Dual sides check (compatibility check)
if !c.table.check(right_type, left_type) { if !c.table.check(right_type, left_type) {
left_type_sym := c.table.get_type_symbol(left_type) left_type_sym := c.table.get_type_symbol(left_type)
right_type_sym := c.table.get_type_symbol(right_type) right_type_sym := c.table.get_type_symbol(right_type)
c.error('cannot assign `$right_type_sym.name` to variable `${assign_expr.left.str()}` of type `$left_type_sym.name`', c.error('cannot assign `$right_type_sym.name` to variable `${assign_expr.left.str()}` of type `$left_type_sym.name`',
assign_expr.val.position()) assign_expr.val.position())
} }
else if assign_expr.op == .plus_assign {
no_str_related_err := left_type == table.string_type && right_type == table.string_type
no_ptr_related_err := (left.is_pointer() || left.is_int()) && (right.is_pointer() || right.is_int())
no_num_related_err := left.is_number() && right.is_number()
if !no_str_related_err && !no_ptr_related_err && !no_num_related_err {
c.error('operator += not defined on left type `$left.name` and right type `$right.name`', assign_expr.pos)
}
}
else if assign_expr.op == .minus_assign {
no_ptr_related_err := (left.is_pointer() || left.is_int()) && (right.is_pointer() || right.is_int())
no_num_related_err := left.is_number() && right.is_number()
if !no_ptr_related_err && !no_num_related_err {
c.error('operator -= not defined on left type `$left.name` and right type `$right.name`', assign_expr.pos)
}
}
else if assign_expr.op in [.mult_assign, .div_assign] {
if !left.is_number() {
c.error('operator ${assign_expr.op.str()} not defined on left type `$left.name`', assign_expr.pos)
}
else if !right.is_number() {
c.error('operator ${assign_expr.op.str()} not defined on right type `$right.name`', assign_expr.pos)
}
}
else if assign_expr.op in [.and_assign, .or_assign, .xor_assign, .mod_assign, .left_shift_assign, .right_shift_assign] {
if !left.is_int() {
c.error('operator ${assign_expr.op.str()} not defined on left type `$left.name`', assign_expr.pos)
}
else if !right.is_int() {
c.error('operator ${assign_expr.op.str()} not defined on right type `$right.name`', assign_expr.pos)
}
}
c.check_expr_opt_call(assign_expr.val, right_type, true) c.check_expr_opt_call(assign_expr.val, right_type, true)
} }

View File

@ -1,6 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_a.v:3:8: error: operator <<= not defined on right type `f64` vlib/v/checker/tests/inout/assign_expr_type_err_a.v:3:2: error: operator <<= not defined on left operand type `f64`
1| fn main() { 1| fn main() {
2| mut a := 10 2| mut foo := 0.5
3| a <<= 0.5 3| foo <<= 1
~~~ ~~~
4| } 4| }

View File

@ -1,4 +1,4 @@
fn main() { fn main() {
mut a := 10 mut foo := 0.5
a <<= 0.5 foo <<= 1
} }

View File

@ -1,6 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_b.v:3:7: error: operator *= not defined on left type `bool` vlib/v/checker/tests/inout/assign_expr_type_err_b.v:3:9: error: operator %= not defined on right operand type `string`
1| fn main() { 1| fn main() {
2| mut a := true 2| mut foo := 10
3| a *= false 3| foo %= 'hello'
~~~~~ ~~~~~~~
4| } 4| }

View File

@ -1,4 +1,4 @@
fn main() { fn main() {
mut a := true mut foo := 10
a *= false foo %= 'hello'
} }

View File

@ -1,6 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_c.v:3:7: error: operator -= not defined on left type `string` and right type `string` vlib/v/checker/tests/inout/assign_expr_type_err_c.v:3:2: error: operator *= not defined on left operand type `string`
1| fn main() { 1| fn main() {
2| mut a := 'hello' 2| mut foo := 'hello'
3| a -= 'world' 3| foo *= 10
~~~~~~~ ~~~
4| } 4| }

View File

@ -1,4 +1,4 @@
fn main() { fn main() {
mut a := 'hello' mut foo := 'hello'
a -= 'world' foo *= 10
} }

View File

@ -1,6 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_d.v:5:7: error: operator += not defined on left type `Foo` and right type `Foo` vlib/v/checker/tests/inout/assign_expr_type_err_d.v:3:9: error: operator /= not defined on right operand type `bool`
3| fn main() { 1| fn main() {
4| mut a := Foo{ } 2| mut foo := 1.5
5| a += Foo{ } 3| foo /= true
~~~ ~~~~
6| } 4| }

View File

@ -1,6 +1,4 @@
struct Foo { }
fn main() { fn main() {
mut a := Foo{ } mut foo := 1.5
a += Foo{ } foo /= true
} }

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_e.v:3:2: error: operator -= not defined on left operand type `string`
1| fn main() {
2| mut foo := 'hello'
3| foo -= `a`
~~~
4| }

View File

@ -0,0 +1,4 @@
fn main() {
mut foo := 'hello'
foo -= `a`
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_f.v:3:9: error: operator -= not defined on right operand type `bool`
1| fn main() {
2| mut foo := 10
3| foo -= false
~~~~~
4| }

View File

@ -0,0 +1,4 @@
fn main() {
mut foo := 10
foo -= false
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_g.v:3:2: error: operator += not defined on left operand type `bool`
1| fn main() {
2| mut foo := true
3| foo += false
~~~
4| }

View File

@ -0,0 +1,4 @@
fn main() {
mut foo := true
foo += false
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_h.v:3:9: error: operator += not defined on right operand type `bool`
1| fn main() {
2| mut foo := 'hello'
3| foo += false
~~~~~
4| }

View File

@ -0,0 +1,4 @@
fn main() {
mut foo := 'hello'
foo += false
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/inout/assign_expr_type_err_i.v:3:9: error: cannot assign `string` to variable `foo` of type `f64`
1| fn main() {
2| mut foo := 1.5
3| foo += 'hello'
~~~~~~~
4| }

View File

@ -0,0 +1,4 @@
fn main() {
mut foo := 1.5
foo += 'hello'
}

View File

@ -53,8 +53,8 @@ fn (var p Parser) assign_stmt() ast.Stmt {
// TODO: is it possible to merge with AssignStmt? // TODO: is it possible to merge with AssignStmt?
pub fn (var p Parser) assign_expr(left ast.Expr) ast.AssignExpr { pub fn (var p Parser) assign_expr(left ast.Expr) ast.AssignExpr {
op := p.tok.kind op := p.tok.kind
p.next()
pos := p.tok.position() pos := p.tok.position()
p.next()
val := p.expr(0) val := p.expr(0)
match left { match left {
ast.IndexExpr { ast.IndexExpr {