checker: smartcast only if type is not mut (#6841)

pull/6842/head
Daniel Däschle 2020-11-15 15:53:51 +01:00 committed by GitHub
parent 4559b4138f
commit 20bec81678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 158 deletions

View File

@ -201,3 +201,21 @@ pub fn (sc &Scope) show(depth int, max_depth int) string {
pub fn (sc &Scope) str() string {
return sc.show(0, 0)
}
// is_selector_root_mutable checks if the root ident is mutable
// Example:
// ```
// mut x := MyStruct{}
// x.foo.bar.z
// ```
// Since x is mutable, it returns true.
pub fn (s &Scope) is_selector_root_mutable(t &table.Table, selector_expr SelectorExpr) bool {
if selector_expr.expr is SelectorExpr as left_expr {
return s.is_selector_root_mutable(t, left_expr)
} else if selector_expr.expr is Ident as left_expr {
if v := s.find_var(left_expr.name) {
return v.is_mut
}
}
return false
}

View File

@ -2202,44 +2202,8 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) {
else {}
}
if !is_blank_ident && right_sym.kind != .placeholder {
// Assign to sum type if ordinary value
mut final_left_type := left_type_unwrapped
mut scope := c.file.scope.innermost(left.position().pos)
match left {
ast.SelectorExpr {
if _ := scope.find_struct_field(left.expr_type, left.field_name) {
final_left_type = right_type_unwrapped
mut inner_scope := c.open_scope(mut scope, left.pos.pos)
inner_scope.register_struct_field(ast.ScopeStructField{
struct_type: left.expr_type
name: left.field_name
typ: final_left_type
sum_type_cast: right_type_unwrapped
pos: left.pos
})
}
}
ast.Ident {
if v := scope.find_var(left.name) {
if v.sum_type_cast != 0 &&
c.table.sumtype_has_variant(final_left_type, right_type_unwrapped) {
final_left_type = right_type_unwrapped
mut inner_scope := c.open_scope(mut scope, left.pos.pos)
inner_scope.register(left.name, ast.Var{
name: left.name
typ: final_left_type
pos: left.pos
is_used: true
is_mut: left.is_mut
sum_type_cast: right_type_unwrapped
})
}
}
}
else {}
}
// Dual sides check (compatibility check)
c.check_expected(right_type_unwrapped, final_left_type) or {
c.check_expected(right_type_unwrapped, left_type_unwrapped) or {
c.error('cannot assign to `$left`: $err', right.position())
}
}
@ -3760,7 +3724,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
if v := scope.find_var(infix_left.name) {
is_mut = v.is_mut
}
if left_sym.kind == .union_sum_type {
if !is_mut && left_sym.kind == .union_sum_type {
scope.register(branch.left_as_name, ast.Var{
name: branch.left_as_name
typ: infix.left_type
@ -3771,11 +3735,14 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
})
}
} else if infix.left is ast.SelectorExpr as selector {
field := c.table.struct_find_field(left_sym, selector.field_name) or {
expr_sym := c.table.get_type_symbol(selector.expr_type)
field := c.table.struct_find_field(expr_sym, selector.field_name) or {
table.Field{}
}
is_mut = field.is_mut
if left_sym.kind == .union_sum_type {
is_root_mut := scope.is_selector_root_mutable(c.table,
selector)
if !is_root_mut && !is_mut && left_sym.kind == .union_sum_type {
scope.register_struct_field(ast.ScopeStructField{
struct_type: selector.expr_type
name: selector.field_name

View File

@ -0,0 +1,21 @@
vlib/v/checker/tests/sum_type_mutable_cast_err.vv:23:10: error: infix expr: cannot use `any_int` (right expression) as `Abc`
21 | mut x := Abc(0)
22 | if x is int {
23 | _ := x + 5
| ^
24 | }
25 | f := Foo{Bar{Abc(0)}}
vlib/v/checker/tests/sum_type_mutable_cast_err.vv:27:14: error: infix expr: cannot use `any_int` (right expression) as `Abc`
25 | f := Foo{Bar{Abc(0)}}
26 | if f.b.a is int {
27 | _ := f.b.a + 5
| ^
28 | }
29 | mut f2 := Foo2{Bar2{Abc(0)}}
vlib/v/checker/tests/sum_type_mutable_cast_err.vv:31:14: error: infix expr: cannot use `any_int` (right expression) as `Abc`
29 | mut f2 := Foo2{Bar2{Abc(0)}}
30 | if f2.b.a is int {
31 | _ := f.b.a + 5
| ^
32 | }
33 | }

View File

@ -0,0 +1,33 @@
__type Abc = int | string
struct Bar {
mut:
a Abc
}
struct Foo {
b Bar
}
struct Bar2 {
a Abc
}
struct Foo2 {
b Bar2
}
fn main() {
mut x := Abc(0)
if x is int {
_ := x + 5
}
f := Foo{Bar{Abc(0)}}
if f.b.a is int {
_ := f.b.a + 5
}
mut f2 := Foo2{Bar2{Abc(0)}}
if f2.b.a is int {
_ := f.b.a + 5
}
}

View File

@ -279,19 +279,19 @@ fn test_assignment() {
x = 'test'
if x is string {
assert x == 'test'
x2 := x as string
assert x2 == 'test'
}
}
__type Inner = int | string
struct InnerStruct {
mut:
x Inner
}
__type Outer = string | InnerStruct
fn test_nested_if_is() {
mut b := Outer(InnerStruct{Inner(0)})
b := Outer(InnerStruct{Inner(0)})
if b is InnerStruct {
if b.x is int {
assert b.x == 0
@ -299,113 +299,12 @@ fn test_nested_if_is() {
}
}
fn test_casted_sum_type_selector_reassign() {
mut b := InnerStruct{Inner(0)}
if b.x is int {
assert typeof(b.x) == 'int'
// this check works only if x is castet
assert b.x == 0
b.x = 'test'
// this check works only if x is castet
assert b.x[0] == `t`
assert typeof(b.x) == 'string'
}
// this check works only if x is not castet
assert b.x is string
}
fn test_casted_sum_type_ident_reassign() {
mut x := Inner(0)
if x is int {
// this check works only if x is castet
assert x == 0
assert typeof(x) == 'int'
x = 'test'
// this check works only if x is castet
assert x[0] == `t`
assert typeof(x) == 'string'
}
// this check works only if x is not castet
assert x is string
}
__type Expr2 = int | string
fn test_match_with_reassign_casted_type() {
mut e := Expr2(0)
match union mut e {
int {
e = int(5)
assert e == 5
}
else {}
}
}
fn test_if_is_with_reassign_casted_type() {
mut e := Expr2(0)
if e is int {
e = int(5)
assert e == 5
}
}
struct Expr2Wrapper {
__type Expr3 = CallExpr | CTempVarExpr
struct Expr3Wrapper {
mut:
expr Expr2
expr Expr3
}
fn test_change_type_if_is_selector() {
mut e := Expr2Wrapper{Expr2(0)}
if e.expr is int {
e.expr = 'str'
assert e.expr.len == 3
}
assert e.expr is string
}
fn test_change_type_if_is() {
mut e := Expr2(0)
if e is int {
e = 'str'
assert e.len == 3
}
assert e is string
}
fn test_change_type_match() {
mut e := Expr2(0)
match union mut e {
int {
e = 'str'
assert e.len == 3
}
else {}
}
assert e is string
}
__type Expr3 = CallExpr | string
struct CallExpr {
mut:
is_expr bool
}
fn test_assign_sum_type_casted_field() {
mut e := Expr3(CallExpr{})
if e is CallExpr {
e.is_expr = true
assert e.is_expr
}
}
__type Expr4 = CallExpr2 | CTempVarExpr
struct Expr4Wrapper {
mut:
expr Expr4
}
struct CallExpr2 {
y int
x string
}
@ -414,29 +313,28 @@ struct CTempVarExpr {
x string
}
fn gen(_ Expr4) CTempVarExpr {
fn gen(_ Expr3) CTempVarExpr {
return CTempVarExpr{}
}
fn test_reassign_from_function_with_parameter() {
mut f := Expr4(CallExpr2{})
if f is CallExpr2 {
mut f := Expr3(CallExpr{})
if f is CallExpr {
f = gen(f)
}
}
fn test_reassign_from_function_with_parameter_selector() {
mut f := Expr4Wrapper{Expr4(CallExpr2{})}
if f.expr is CallExpr2 {
mut f := Expr3Wrapper{Expr3(CallExpr{})}
if f.expr is CallExpr {
f.expr = gen(f.expr)
}
}
fn test_match_multi_branch() {
f := Expr4(CTempVarExpr{'ctemp'})
mut y := ''
f := Expr3(CTempVarExpr{'ctemp'})
match union f {
CallExpr2, CTempVarExpr {
CallExpr, CTempVarExpr {
// this check works only if f is not castet
assert f is CTempVarExpr
}
@ -444,17 +342,17 @@ fn test_match_multi_branch() {
}
fn test_typeof() {
x := Expr4(CTempVarExpr{})
x := Expr3(CTempVarExpr{})
assert typeof(x) == 'CTempVarExpr'
}
struct Outer2 {
e Expr4
e Expr3
}
fn test_zero_value_init() {
// no c compiler error then it's successful
o := Outer2{}
_ := Outer2{}
}
fn test_sum_type_match() {