Skip to content

Commit 88d18f3

Browse files
checker: smartcast in for loops (vlang#7942)
1 parent 5dbc194 commit 88d18f3

5 files changed

Lines changed: 174 additions & 123 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Overloading of `>`, `<`, `!=`, and `==` operators.
66
- New struct updating syntax: `User{ ...u, name: 'new' }` to replace `{ u | name: 'new' }`.
77
- `byte.str()` has been fixed and works like with all other numbers. `byte.ascii_str()` has been added.
8+
- Smart cast in for-loops: `for mut x is string {}`
89

910
## V 0.2.1
1011
*30 Dec 2020*

vlib/v/ast/ast.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ pub mut:
120120
pub fn (e &SelectorExpr) root_ident() Ident {
121121
mut root := e.expr
122122
for root is SelectorExpr {
123+
// TODO: remove this line
123124
selector_expr := root as SelectorExpr
124125
root = selector_expr.expr
125126
}

vlib/v/checker/checker.v

Lines changed: 116 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -2886,19 +2886,7 @@ fn (mut c Checker) stmt(node ast.Stmt) {
28862886
c.in_for_count--
28872887
}
28882888
ast.ForStmt {
2889-
c.in_for_count++
2890-
prev_loop_label := c.loop_label
2891-
c.expected_type = table.bool_type
2892-
typ := c.expr(node.cond)
2893-
if !node.is_inf && typ.idx() != table.bool_type_idx && !c.pref.translated {
2894-
c.error('non-bool used as for condition', node.pos)
2895-
}
2896-
// TODO: update loop var type
2897-
// how does this work currenly?
2898-
c.check_loop_label(node.label, node.pos)
2899-
c.stmts(node.stmts)
2900-
c.loop_label = prev_loop_label
2901-
c.in_for_count--
2889+
c.for_stmt(mut node)
29022890
}
29032891
ast.GlobalDecl {
29042892
for field in node.fields {
@@ -3884,60 +3872,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym table.TypeS
38843872
} else {
38853873
expr_type = expr_types[0].typ
38863874
}
3887-
match mut node.cond {
3888-
ast.SelectorExpr {
3889-
mut is_mut := false
3890-
mut sum_type_casts := []table.Type{}
3891-
expr_sym := c.table.get_type_symbol(node.cond.expr_type)
3892-
if field := c.table.struct_find_field(expr_sym, node.cond.field_name) {
3893-
if field.is_mut {
3894-
root_ident := node.cond.root_ident()
3895-
if v := branch.scope.find_var(root_ident.name) {
3896-
is_mut = v.is_mut
3897-
}
3898-
}
3899-
}
3900-
if field := branch.scope.find_struct_field(node.cond.expr_type,
3901-
node.cond.field_name) {
3902-
sum_type_casts << field.sum_type_casts
3903-
}
3904-
// smartcast either if the value is immutable or if the mut argument is explicitly given
3905-
if !is_mut || node.cond.is_mut {
3906-
sum_type_casts << expr_type
3907-
branch.scope.register_struct_field(ast.ScopeStructField{
3908-
struct_type: node.cond.expr_type
3909-
name: node.cond.field_name
3910-
typ: node.cond_type
3911-
sum_type_casts: sum_type_casts
3912-
pos: node.cond.pos
3913-
})
3914-
}
3915-
}
3916-
ast.Ident {
3917-
mut is_mut := false
3918-
mut sum_type_casts := []table.Type{}
3919-
mut is_already_casted := false
3920-
if node.cond.obj is ast.Var {
3921-
v := node.cond.obj as ast.Var
3922-
is_mut = v.is_mut
3923-
sum_type_casts << v.sum_type_casts
3924-
is_already_casted = v.pos.pos == node.cond.pos.pos
3925-
}
3926-
// smartcast either if the value is immutable or if the mut argument is explicitly given
3927-
if (!is_mut || node.cond.is_mut) && !is_already_casted {
3928-
sum_type_casts << expr_type
3929-
branch.scope.register(ast.Var{
3930-
name: node.cond.name
3931-
typ: node.cond_type
3932-
pos: node.cond.pos
3933-
is_used: true
3934-
is_mut: node.cond.is_mut
3935-
sum_type_casts: sum_type_casts
3936-
})
3937-
}
3938-
}
3939-
else {}
3940-
}
3875+
c.smartcast_sumtype(node.cond, node.cond_type, expr_type, mut branch.scope)
39413876
}
39423877
}
39433878
}
@@ -4017,6 +3952,63 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym table.TypeS
40173952
c.error(err_details, node.pos)
40183953
}
40193954

3955+
// smartcast takes the expression with the current type which should be smartcasted to the target type in the given scope
3956+
fn (c Checker) smartcast_sumtype(expr ast.Expr, cur_type table.Type, to_type table.Type, mut scope ast.Scope) {
3957+
match mut expr {
3958+
ast.SelectorExpr {
3959+
mut is_mut := false
3960+
mut sum_type_casts := []table.Type{}
3961+
expr_sym := c.table.get_type_symbol(expr.expr_type)
3962+
if field := c.table.struct_find_field(expr_sym, expr.field_name) {
3963+
if field.is_mut {
3964+
root_ident := expr.root_ident()
3965+
if v := scope.find_var(root_ident.name) {
3966+
is_mut = v.is_mut
3967+
}
3968+
}
3969+
}
3970+
if field := scope.find_struct_field(expr.expr_type, expr.field_name) {
3971+
sum_type_casts << field.sum_type_casts
3972+
}
3973+
// smartcast either if the value is immutable or if the mut argument is explicitly given
3974+
if !is_mut || expr.is_mut {
3975+
sum_type_casts << to_type
3976+
scope.register_struct_field(ast.ScopeStructField{
3977+
struct_type: expr.expr_type
3978+
name: expr.field_name
3979+
typ: cur_type
3980+
sum_type_casts: sum_type_casts
3981+
pos: expr.pos
3982+
})
3983+
}
3984+
}
3985+
ast.Ident {
3986+
mut is_mut := false
3987+
mut sum_type_casts := []table.Type{}
3988+
mut is_already_casted := false
3989+
if expr.obj is ast.Var {
3990+
v := expr.obj as ast.Var
3991+
is_mut = v.is_mut
3992+
sum_type_casts << v.sum_type_casts
3993+
is_already_casted = v.pos.pos == expr.pos.pos
3994+
}
3995+
// smartcast either if the value is immutable or if the mut argument is explicitly given
3996+
if (!is_mut || expr.is_mut) && !is_already_casted {
3997+
sum_type_casts << to_type
3998+
scope.register(ast.Var{
3999+
name: expr.name
4000+
typ: cur_type
4001+
pos: expr.pos
4002+
is_used: true
4003+
is_mut: expr.is_mut
4004+
sum_type_casts: sum_type_casts
4005+
})
4006+
}
4007+
}
4008+
else {}
4009+
}
4010+
}
4011+
40204012
pub fn (mut c Checker) select_expr(mut node ast.SelectExpr) table.Type {
40214013
node.is_expr = c.expected_type != table.void_type
40224014
node.expected_type = c.expected_type
@@ -4114,6 +4106,45 @@ pub fn (mut c Checker) unsafe_expr(mut node ast.UnsafeExpr) table.Type {
41144106
return t
41154107
}
41164108

4109+
fn (mut c Checker) for_stmt(mut node ast.ForStmt) {
4110+
c.in_for_count++
4111+
prev_loop_label := c.loop_label
4112+
c.expected_type = table.bool_type
4113+
typ := c.expr(node.cond)
4114+
if !node.is_inf && typ.idx() != table.bool_type_idx && !c.pref.translated {
4115+
c.error('non-bool used as for condition', node.pos)
4116+
}
4117+
if node.cond is ast.InfixExpr {
4118+
infix := node.cond
4119+
if infix.op == .key_is {
4120+
if (infix.left is ast.Ident ||
4121+
infix.left is ast.SelectorExpr) &&
4122+
infix.right is ast.Type {
4123+
right_expr := infix.right as ast.Type
4124+
is_variable := if mut infix.left is ast.Ident {
4125+
infix.left.kind == .variable
4126+
} else {
4127+
true
4128+
}
4129+
left_type := c.expr(infix.left)
4130+
left_sym := c.table.get_type_symbol(left_type)
4131+
if is_variable {
4132+
if left_sym.kind == .sum_type {
4133+
c.smartcast_sumtype(infix.left, infix.left_type, right_expr.typ, mut
4134+
node.scope)
4135+
}
4136+
}
4137+
}
4138+
}
4139+
}
4140+
// TODO: update loop var type
4141+
// how does this work currenly?
4142+
c.check_loop_label(node.label, node.pos)
4143+
c.stmts(node.stmts)
4144+
c.loop_label = prev_loop_label
4145+
c.in_for_count--
4146+
}
4147+
41174148
pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
41184149
if_kind := if node.is_comptime { '\$if' } else { 'if' }
41194150
expr_required := c.expected_type != table.void_type
@@ -4167,68 +4198,30 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
41674198
} else {
41684199
true
41694200
}
4170-
// Register shadow variable or `as` variable with actual type
41714201
if is_variable {
4172-
// TODO: merge this code with match_expr because it has the same logic implemented
41734202
if left_sym.kind in [.interface_, .sum_type] {
4174-
mut is_mut := false
4175-
if mut infix.left is ast.Ident {
4203+
if infix.left is ast.Ident && left_sym.kind == .interface_ {
4204+
// TODO: rewrite interface smartcast
4205+
left := infix.left as ast.Ident
4206+
mut is_mut := false
41764207
mut sum_type_casts := []table.Type{}
4177-
if v := branch.scope.find_var(infix.left.name) {
4208+
if v := branch.scope.find_var(left.name) {
41784209
is_mut = v.is_mut
41794210
sum_type_casts << v.sum_type_casts
41804211
}
4181-
if left_sym.kind == .sum_type {
4182-
// smartcast either if the value is immutable or if the mut argument is explicitly given
4183-
if !is_mut || infix.left.is_mut {
4184-
sum_type_casts << right_expr.typ
4185-
branch.scope.register(ast.Var{
4186-
name: infix.left.name
4187-
typ: infix.left_type
4188-
sum_type_casts: sum_type_casts
4189-
pos: infix.left.pos
4190-
is_used: true
4191-
is_mut: is_mut
4192-
})
4193-
}
4194-
} else if left_sym.kind == .interface_ {
4195-
branch.scope.register(ast.Var{
4196-
name: infix.left.name
4197-
typ: right_expr.typ.to_ptr()
4198-
sum_type_casts: sum_type_casts
4199-
pos: infix.left.pos
4200-
is_used: true
4201-
is_mut: is_mut
4202-
})
4203-
// TODO: remove that later @danieldaeschle
4204-
node.branches[i].smartcast = true
4205-
}
4206-
} else if mut infix.left is ast.SelectorExpr {
4207-
mut sum_type_casts := []table.Type{}
4208-
expr_sym := c.table.get_type_symbol(infix.left.expr_type)
4209-
if field := c.table.struct_find_field(expr_sym, infix.left.field_name) {
4210-
if field.is_mut {
4211-
root_ident := infix.left.root_ident()
4212-
if root_ident.obj is ast.Var {
4213-
is_mut = root_ident.obj.is_mut
4214-
}
4215-
}
4216-
}
4217-
if field := branch.scope.find_struct_field(infix.left.expr_type,
4218-
infix.left.field_name) {
4219-
sum_type_casts << field.sum_type_casts
4220-
}
4221-
// smartcast either if the value is immutable or if the mut argument is explicitly given
4222-
if (!is_mut || infix.left.is_mut) && left_sym.kind == .sum_type {
4223-
sum_type_casts << right_expr.typ
4224-
branch.scope.register_struct_field(ast.ScopeStructField{
4225-
struct_type: infix.left.expr_type
4226-
name: infix.left.field_name
4227-
typ: infix.left_type
4228-
sum_type_casts: sum_type_casts
4229-
pos: infix.left.pos
4230-
})
4231-
}
4212+
branch.scope.register(ast.Var{
4213+
name: left.name
4214+
typ: right_expr.typ.to_ptr()
4215+
sum_type_casts: sum_type_casts
4216+
pos: left.pos
4217+
is_used: true
4218+
is_mut: is_mut
4219+
})
4220+
// TODO: needs to be removed
4221+
node.branches[i].smartcast = true
4222+
} else {
4223+
c.smartcast_sumtype(infix.left, infix.left_type, right_expr.typ, mut
4224+
branch.scope)
42324225
}
42334226
}
42344227
}

vlib/v/parser/for.v

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ fn (mut p Parser) for_stmt() ast.Stmt {
177177
// `for cond {`
178178
cond := p.expr(0)
179179
p.inside_for = false
180+
// extra scope for the body
181+
p.open_scope()
180182
stmts := p.parse_block_no_scope(false)
181183
pos.last_line = p.prev_tok.line_nr - 1
182184
for_stmt := ast.ForStmt{
@@ -186,5 +188,6 @@ fn (mut p Parser) for_stmt() ast.Stmt {
186188
scope: p.scope
187189
}
188190
p.close_scope()
191+
p.close_scope()
189192
return for_stmt
190193
}

vlib/v/tests/for_smartcast_test.v

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
type Node = Expr | string
2+
type Expr = IfExpr | IntegerLiteral
3+
4+
struct IntegerLiteral {}
5+
struct IfExpr {
6+
pos int
7+
}
8+
9+
struct NodeWrapper {
10+
node Node
11+
}
12+
13+
fn test_nested_sumtype_selector() {
14+
c := NodeWrapper{Node(Expr(IfExpr{pos: 1}))}
15+
for c.node is Expr {
16+
assert typeof(c.node).name == 'Expr'
17+
break
18+
}
19+
}
20+
21+
struct Milk {
22+
mut:
23+
name string
24+
}
25+
26+
struct Eggs {
27+
mut:
28+
name string
29+
}
30+
31+
type Food = Milk | Eggs
32+
33+
struct FoodWrapper {
34+
mut:
35+
food Food
36+
}
37+
38+
fn test_match_mut() {
39+
mut f := Food(Eggs{'test'})
40+
for mut f is Eggs {
41+
f.name = 'eggs'
42+
assert f.name == 'eggs'
43+
break
44+
}
45+
}
46+
47+
fn test_conditional_break() {
48+
mut f := Food(Eggs{'test'})
49+
for mut f is Eggs {
50+
f = Milk{'test'}
51+
}
52+
assert true
53+
}

0 commit comments

Comments
 (0)