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 {` cond_type table.Type // type of `x` in `match x {`
expected_type table.Type // for debugging only expected_type table.Type // for debugging only
is_sum_type bool is_sum_type bool
is_interface bool
} }
pub struct MatchBranch { 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 } else if idx_lo >= table.byte_type_idx { // both operands are unsigned
return type_hi 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 } else if idx_lo >= table.i8_type_idx &&
return if idx_lo == table.i64_type_idx { type_lo } else { type_hi } (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) { } 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 return type_lo // conversion unsigned -> signed if signed type is larger
} else { } 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 { pub fn (c &Checker) check_sumtype_compatibility(a, b table.Type) bool {
if c.table.sumtype_has_variant(a, b) { return c.table.sumtype_has_variant(a, b) || c.table.sumtype_has_variant(b, a)
return true
}
if c.table.sumtype_has_variant(b, a) {
return true
}
return false
} }

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', c.error('$infix_expr.op.str(): type `$typ_sym.source_name` does not exist',
type_expr.pos) 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', c.error('`$infix_expr.op.str()` can only be used with interfaces and sum types',
infix_expr.pos) 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 require_return := false
mut branch_without_return := false mut branch_without_return := false
for branch in node.branches { 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) c.stmts(branch.stmts)
// If the last statement is an expression, return its type // If the last statement is an expression, return its type
if branch.stmts.len > 0 { 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 // branch_exprs is a histogram of how many times
// an expr was used in the match // an expr was used in the match
mut branch_exprs := map[string]int{} mut branch_exprs := map[string]int{}
cond_type_sym := c.table.get_type_symbol(node.cond_type)
for branch in node.branches { for branch in node.branches {
for expr in branch.exprs { for expr in branch.exprs {
mut key := '' 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 c.expected_type = node.cond_type
expr_type := c.expr(expr) 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) expr_str := c.table.type_to_str(expr_type)
expect_str := c.table.type_to_str(c.expected_type) expect_str := c.table.type_to_str(c.expected_type)
c.error('cannot use type `$expect_str` as type `$expr_str`', node.pos) 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` // smartcast sumtypes and interfaces when using `is`
if !is_ct && branch.cond is ast.InfixExpr { if !is_ct && branch.cond is ast.InfixExpr {
infix := branch.cond as ast.InfixExpr infix := branch.cond as ast.InfixExpr
if infix.op == .key_is && if infix.op == .key_is {
(infix.left is ast.Ident || infix.left is ast.SelectorExpr) && infix.right is ast.Type {
right_expr := infix.right as ast.Type right_expr := infix.right as ast.Type
is_variable := if infix.left is ast.Ident { (infix.left as ast.Ident).kind == left_sym := c.table.get_type_symbol(infix.left_type)
.variable } else { true } expr_type := c.expr(infix.left)
// Register shadow variable or `as` variable with actual type if left_sym.kind == .interface_ {
if is_variable { c.type_implements(right_expr.typ, expr_type, branch.pos)
left_sym := c.table.get_type_symbol(infix.left_type) } else if !c.check_types(expr_type, right_expr.typ) {
if left_sym.kind in [.sum_type, .interface_] && branch.left_as_name.len > 0 { expect_str := c.table.type_to_str(right_expr.typ)
mut is_mut := false expr_str := c.table.type_to_str(expr_type)
mut scope := c.file.scope.innermost(branch.body_pos.pos) c.error('cannot use type `$expect_str` as type `$expr_str`', branch.pos)
if infix.left is ast.Ident as infix_left { }
if var := scope.find_var(infix_left.name) { if (infix.left is ast.Ident ||
is_mut = var.is_mut 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 { scope.register(branch.left_as_name, ast.Var{
selector := infix.left as ast.SelectorExpr name: branch.left_as_name
field := c.table.struct_find_field(left_sym, selector.field_name) or { typ: right_expr.typ.to_ptr()
table.Field{} pos: infix.left.position()
} is_used: true
is_mut = field.is_mut 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 single_line = false
break break
} }
} else if stmt is ast.Comment {
single_line = false
break
} }
} }
for branch in it.branches { for branch in it.branches {