checker: check type in `is` InfixExpr (#6407)

pull/6411/head
Enzo 2020-09-18 01:01:05 +02:00 committed by GitHub
parent ff92c3409d
commit a1e127ae46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 59 deletions

View File

@ -519,7 +519,6 @@ pub mut:
cond_type table.Type // type of `x` in `match x {`
expected_type table.Type // for debugging only
is_sum_type bool
is_interface bool
}
pub struct MatchBranch {

View File

@ -217,8 +217,13 @@ fn (c &Checker) promote_num(left_type, right_type table.Type) table.Type {
}
} else if idx_lo >= table.byte_type_idx { // both operands are unsigned
return type_hi
} else if idx_lo >= table.i8_type_idx && (idx_hi <= table.i64_type_idx || idx_hi == table.rune_type_idx) { // both signed
return if idx_lo == table.i64_type_idx { type_lo } else { type_hi }
} else if idx_lo >= table.i8_type_idx &&
(idx_hi <= table.i64_type_idx || idx_hi == table.rune_type_idx) { // both signed
return if idx_lo == table.i64_type_idx {
type_lo
} else {
type_hi
}
} else if idx_hi - idx_lo < (table.byte_type_idx - table.i8_type_idx) {
return type_lo // conversion unsigned -> signed if signed type is larger
} else {
@ -361,11 +366,5 @@ pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) table.T
}
pub fn (c &Checker) check_sumtype_compatibility(a, b table.Type) bool {
if c.table.sumtype_has_variant(a, b) {
return true
}
if c.table.sumtype_has_variant(b, a) {
return true
}
return false
return c.table.sumtype_has_variant(a, b) || c.table.sumtype_has_variant(b, a)
}

View File

@ -711,7 +711,7 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
c.error('$infix_expr.op.str(): type `$typ_sym.source_name` does not exist',
type_expr.pos)
}
if left.kind != .interface_ && left.kind != .sum_type {
if left.kind !in [.interface_, .sum_type] {
c.error('`$infix_expr.op.str()` can only be used with interfaces and sum types',
infix_expr.pos)
}
@ -2891,23 +2891,6 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) table.Type {
mut require_return := false
mut branch_without_return := false
for branch in node.branches {
for expr in branch.exprs {
c.expected_type = cond_type
typ := c.expr(expr)
typ_sym := c.table.get_type_symbol(typ)
if node.is_sum_type || node.is_interface {
ok := if cond_type_sym.kind == .sum_type {
c.table.sumtype_has_variant(cond_type, typ)
} else {
// interface match
c.type_implements(typ, cond_type, node.pos)
}
if !ok {
c.error('cannot use `$typ_sym.source_name` as `$cond_type_sym.source_name` in `match`',
node.pos)
}
}
}
c.stmts(branch.stmts)
// If the last statement is an expression, return its type
if branch.stmts.len > 0 {
@ -2953,6 +2936,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, type_sym table.TypeSymbol
// branch_exprs is a histogram of how many times
// an expr was used in the match
mut branch_exprs := map[string]int{}
cond_type_sym := c.table.get_type_symbol(node.cond_type)
for branch in node.branches {
for expr in branch.exprs {
mut key := ''
@ -3001,7 +2985,9 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, type_sym table.TypeSymbol
}
c.expected_type = node.cond_type
expr_type := c.expr(expr)
if !c.check_types(expr_type, c.expected_type) {
if cond_type_sym.kind == .interface_ {
c.type_implements(expr_type, c.expected_type, branch.pos)
} else if !c.check_types(expr_type, c.expected_type) {
expr_str := c.table.type_to_str(expr_type)
expect_str := c.table.type_to_str(c.expected_type)
c.error('cannot use type `$expect_str` as type `$expr_str`', node.pos)
@ -3161,36 +3147,47 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
// smartcast sumtypes and interfaces when using `is`
if !is_ct && branch.cond is ast.InfixExpr {
infix := branch.cond as ast.InfixExpr
if infix.op == .key_is &&
(infix.left is ast.Ident || infix.left is ast.SelectorExpr) && infix.right is ast.Type {
if infix.op == .key_is {
right_expr := infix.right as ast.Type
is_variable := if infix.left is ast.Ident { (infix.left as ast.Ident).kind ==
.variable } else { true }
// Register shadow variable or `as` variable with actual type
if is_variable {
left_sym := c.table.get_type_symbol(infix.left_type)
if left_sym.kind in [.sum_type, .interface_] && branch.left_as_name.len > 0 {
mut is_mut := false
mut scope := c.file.scope.innermost(branch.body_pos.pos)
if infix.left is ast.Ident as infix_left {
if var := scope.find_var(infix_left.name) {
is_mut = var.is_mut
left_sym := c.table.get_type_symbol(infix.left_type)
expr_type := c.expr(infix.left)
if left_sym.kind == .interface_ {
c.type_implements(right_expr.typ, expr_type, branch.pos)
} else if !c.check_types(expr_type, right_expr.typ) {
expect_str := c.table.type_to_str(right_expr.typ)
expr_str := c.table.type_to_str(expr_type)
c.error('cannot use type `$expect_str` as type `$expr_str`', branch.pos)
}
if (infix.left is ast.Ident ||
infix.left is ast.SelectorExpr) &&
infix.right is ast.Type {
is_variable := if infix.left is ast.Ident { (infix.left as ast.Ident).kind ==
.variable } else { true }
// Register shadow variable or `as` variable with actual type
if is_variable {
if left_sym.kind in [.sum_type, .interface_] && branch.left_as_name.len > 0 {
mut is_mut := false
mut scope := c.file.scope.innermost(branch.body_pos.pos)
if infix.left is ast.Ident as infix_left {
if var := scope.find_var(infix_left.name) {
is_mut = var.is_mut
}
} else if infix.left is ast.SelectorExpr {
selector := infix.left as ast.SelectorExpr
field := c.table.struct_find_field(left_sym, selector.field_name) or {
table.Field{}
}
is_mut = field.is_mut
}
} else if infix.left is ast.SelectorExpr {
selector := infix.left as ast.SelectorExpr
field := c.table.struct_find_field(left_sym, selector.field_name) or {
table.Field{}
}
is_mut = field.is_mut
scope.register(branch.left_as_name, ast.Var{
name: branch.left_as_name
typ: right_expr.typ.to_ptr()
pos: infix.left.position()
is_used: true
is_mut: is_mut
})
node.branches[i].smartcast = true
}
scope.register(branch.left_as_name, ast.Var{
name: branch.left_as_name
typ: right_expr.typ.to_ptr()
pos: infix.left.position()
is_used: true
is_mut: is_mut
})
node.branches[i].smartcast = true
}
}
}

View File

@ -0,0 +1,14 @@
vlib/v/checker/tests/is_type_invalid.vv:14:2: error: cannot use type `byte` as type `IoS`
12 |
13 | fn main() {
14 | if IoS(1) is byte {
| ~~~~~~~~~~~~~~~~~
15 | println('not cool')
16 | }
vlib/v/checker/tests/is_type_invalid.vv:18:2: error: `Cat` doesn't implement method `speak`
16 | }
17 | a := Animal(Dog{})
18 | if a is Cat {
| ~~~~~~~~~~~
19 | println('not cool either')
20 | }

View File

@ -0,0 +1,21 @@
type IoS = int | string
interface Animal {
speak()
}
struct Dog {}
fn (d Dog) speak() {}
struct Cat {}
fn main() {
if IoS(1) is byte {
println('not cool')
}
a := Animal(Dog{})
if a is Cat {
println('not cool either')
}
}

View File

@ -0,0 +1,14 @@
vlib/v/checker/tests/match_sumtype_type_invalid.vv:14:2: error: cannot use type `IoS` as type `byte`
12 |
13 | fn main() {
14 | match IoS(1) {
| ~~~~~~~~~~~~~~
15 | byte {
16 | println('not cool')
vlib/v/checker/tests/match_sumtype_type_invalid.vv:21:3: error: `Cat` doesn't implement method `speak`
19 | a := Animal(Dog{})
20 | match a {
21 | Cat {
| ~~~~~
22 | println('not cool either')
23 | }

View File

@ -0,0 +1,26 @@
type IoS = int | string
interface Animal {
speak()
}
struct Dog {}
fn (d Dog) speak() {}
struct Cat {}
fn main() {
match IoS(1) {
byte {
println('not cool')
}
}
a := Animal(Dog{})
match a {
Cat {
println('not cool either')
}
else {}
}
}

View File

@ -1521,9 +1521,6 @@ pub fn (mut f Fmt) match_expr(it ast.MatchExpr) {
single_line = false
break
}
} else if stmt is ast.Comment {
single_line = false
break
}
}
for branch in it.branches {