checker: smartcast only if type is not mut (#6841)
parent
4559b4138f
commit
20bec81678
|
@ -201,3 +201,21 @@ pub fn (sc &Scope) show(depth int, max_depth int) string {
|
||||||
pub fn (sc &Scope) str() string {
|
pub fn (sc &Scope) str() string {
|
||||||
return sc.show(0, 0)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -2202,44 +2202,8 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) {
|
||||||
else {}
|
else {}
|
||||||
}
|
}
|
||||||
if !is_blank_ident && right_sym.kind != .placeholder {
|
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)
|
// 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())
|
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) {
|
if v := scope.find_var(infix_left.name) {
|
||||||
is_mut = v.is_mut
|
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{
|
scope.register(branch.left_as_name, ast.Var{
|
||||||
name: branch.left_as_name
|
name: branch.left_as_name
|
||||||
typ: infix.left_type
|
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 {
|
} 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{}
|
table.Field{}
|
||||||
}
|
}
|
||||||
is_mut = field.is_mut
|
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{
|
scope.register_struct_field(ast.ScopeStructField{
|
||||||
struct_type: selector.expr_type
|
struct_type: selector.expr_type
|
||||||
name: selector.field_name
|
name: selector.field_name
|
||||||
|
|
|
@ -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 | }
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -279,19 +279,19 @@ fn test_assignment() {
|
||||||
x = 'test'
|
x = 'test'
|
||||||
|
|
||||||
if x is string {
|
if x is string {
|
||||||
assert x == 'test'
|
x2 := x as string
|
||||||
|
assert x2 == 'test'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__type Inner = int | string
|
__type Inner = int | string
|
||||||
struct InnerStruct {
|
struct InnerStruct {
|
||||||
mut:
|
|
||||||
x Inner
|
x Inner
|
||||||
}
|
}
|
||||||
__type Outer = string | InnerStruct
|
__type Outer = string | InnerStruct
|
||||||
|
|
||||||
fn test_nested_if_is() {
|
fn test_nested_if_is() {
|
||||||
mut b := Outer(InnerStruct{Inner(0)})
|
b := Outer(InnerStruct{Inner(0)})
|
||||||
if b is InnerStruct {
|
if b is InnerStruct {
|
||||||
if b.x is int {
|
if b.x is int {
|
||||||
assert b.x == 0
|
assert b.x == 0
|
||||||
|
@ -299,113 +299,12 @@ fn test_nested_if_is() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_casted_sum_type_selector_reassign() {
|
__type Expr3 = CallExpr | CTempVarExpr
|
||||||
mut b := InnerStruct{Inner(0)}
|
struct Expr3Wrapper {
|
||||||
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 {
|
|
||||||
mut:
|
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 {
|
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
|
y int
|
||||||
x string
|
x string
|
||||||
}
|
}
|
||||||
|
@ -414,29 +313,28 @@ struct CTempVarExpr {
|
||||||
x string
|
x string
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen(_ Expr4) CTempVarExpr {
|
fn gen(_ Expr3) CTempVarExpr {
|
||||||
return CTempVarExpr{}
|
return CTempVarExpr{}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_reassign_from_function_with_parameter() {
|
fn test_reassign_from_function_with_parameter() {
|
||||||
mut f := Expr4(CallExpr2{})
|
mut f := Expr3(CallExpr{})
|
||||||
if f is CallExpr2 {
|
if f is CallExpr {
|
||||||
f = gen(f)
|
f = gen(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_reassign_from_function_with_parameter_selector() {
|
fn test_reassign_from_function_with_parameter_selector() {
|
||||||
mut f := Expr4Wrapper{Expr4(CallExpr2{})}
|
mut f := Expr3Wrapper{Expr3(CallExpr{})}
|
||||||
if f.expr is CallExpr2 {
|
if f.expr is CallExpr {
|
||||||
f.expr = gen(f.expr)
|
f.expr = gen(f.expr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_match_multi_branch() {
|
fn test_match_multi_branch() {
|
||||||
f := Expr4(CTempVarExpr{'ctemp'})
|
f := Expr3(CTempVarExpr{'ctemp'})
|
||||||
mut y := ''
|
|
||||||
match union f {
|
match union f {
|
||||||
CallExpr2, CTempVarExpr {
|
CallExpr, CTempVarExpr {
|
||||||
// this check works only if f is not castet
|
// this check works only if f is not castet
|
||||||
assert f is CTempVarExpr
|
assert f is CTempVarExpr
|
||||||
}
|
}
|
||||||
|
@ -444,17 +342,17 @@ fn test_match_multi_branch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_typeof() {
|
fn test_typeof() {
|
||||||
x := Expr4(CTempVarExpr{})
|
x := Expr3(CTempVarExpr{})
|
||||||
assert typeof(x) == 'CTempVarExpr'
|
assert typeof(x) == 'CTempVarExpr'
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Outer2 {
|
struct Outer2 {
|
||||||
e Expr4
|
e Expr3
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_zero_value_init() {
|
fn test_zero_value_init() {
|
||||||
// no c compiler error then it's successful
|
// no c compiler error then it's successful
|
||||||
o := Outer2{}
|
_ := Outer2{}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_sum_type_match() {
|
fn test_sum_type_match() {
|
||||||
|
|
Loading…
Reference in New Issue