Skip to content

Commit dbfb9c3

Browse files
authored
cgen, checker: var type checking at compile-time (#16951)
1 parent b19db3a commit dbfb9c3

File tree

7 files changed

+226
-113
lines changed

7 files changed

+226
-113
lines changed

vlib/v/checker/comptime.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) ComptimeBran
551551
} else {
552552
.skip
553553
}
554-
} else if cond.left in [ast.SelectorExpr, ast.TypeNode] {
554+
} else if cond.left in [ast.Ident, ast.SelectorExpr, ast.TypeNode] {
555555
// `$if method.@type is string`
556556
c.expr(cond.left)
557557
return .unknown

vlib/v/checker/if.v

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
9494
} else {
9595
.skip
9696
}
97+
} else if right is ast.ComptimeType && left is ast.Ident
98+
&& (left as ast.Ident).info is ast.IdentVar {
99+
is_comptime_type_is_expr = true
100+
if var := left.scope.find_var(left.name) {
101+
checked_type := c.unwrap_generic(var.typ)
102+
skip_state = if c.table.is_comptime_type(checked_type, right as ast.ComptimeType) {
103+
.eval
104+
} else {
105+
.skip
106+
}
107+
}
97108
} else {
98109
got_type := c.unwrap_generic((right as ast.TypeNode).typ)
99110
sym := c.table.sym(got_type)
Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,35 @@
1-
vlib/v/checker/tests/unknown_comptime_expr.vv:5:6: error: `foo` is mut and may have changed since its definition
2-
3 | fn main() {
3-
4 | mut foo := 0
4-
5 | $if foo == 0 {}
1+
vlib/v/checker/tests/unknown_comptime_expr.vv:7:6: error: `foo` is mut and may have changed since its definition
2+
5 | fn main() {
3+
6 | mut foo := 0
4+
7 | $if foo == 0 {
55
| ~~~
6-
6 |
7-
7 | bar := unknown_at_ct()
8-
vlib/v/checker/tests/unknown_comptime_expr.vv:8:6: error: definition of `bar` is unknown at compile time
9-
6 |
10-
7 | bar := unknown_at_ct()
11-
8 | $if bar == 0 {}
6+
8 | }
7+
9 |
8+
vlib/v/checker/tests/unknown_comptime_expr.vv:11:6: error: definition of `bar` is unknown at compile time
9+
9 |
10+
10 | bar := unknown_at_ct()
11+
11 | $if bar == 0 {
1212
| ~~~
13-
9 | }
14-
10 |
15-
vlib/v/checker/tests/unknown_comptime_expr.vv:13:6: error: undefined ident: `huh`
16-
11 | fn if_is() {
17-
12 | s := S1{}
18-
13 | $if huh.typ is T {}
13+
12 | }
14+
13 | }
15+
vlib/v/checker/tests/unknown_comptime_expr.vv:17:6: error: undefined ident: `huh`
16+
15 | fn if_is() {
17+
16 | s := S1{}
18+
17 | $if huh.typ is T {
1919
| ~~~
20-
14 | $if s is int {}
21-
15 | $if s.i is 5 {}
22-
vlib/v/checker/tests/unknown_comptime_expr.vv:14:6: error: invalid `$if` condition: expected a type or a selector expression or an interface check
23-
12 | s := S1{}
24-
13 | $if huh.typ is T {}
25-
14 | $if s is int {}
26-
| ^
27-
15 | $if s.i is 5 {}
28-
16 | $if s.i is T {}
29-
vlib/v/checker/tests/unknown_comptime_expr.vv:15:13: error: invalid `$if` condition: expected a type
30-
13 | $if huh.typ is T {}
31-
14 | $if s is int {}
32-
15 | $if s.i is 5 {}
20+
18 | }
21+
19 | $if s is int {
22+
vlib/v/checker/tests/unknown_comptime_expr.vv:21:13: error: invalid `$if` condition: expected a type
23+
19 | $if s is int {
24+
20 | }
25+
21 | $if s.i is 5 {
3326
| ^
34-
16 | $if s.i is T {}
35-
17 | }
36-
vlib/v/checker/tests/unknown_comptime_expr.vv:16:13: error: unknown type `T`
37-
14 | $if s is int {}
38-
15 | $if s.i is 5 {}
39-
16 | $if s.i is T {}
27+
22 | }
28+
23 | $if s.i is T {
29+
vlib/v/checker/tests/unknown_comptime_expr.vv:23:13: error: unknown type `T`
30+
21 | $if s.i is 5 {
31+
22 | }
32+
23 | $if s.i is T {
4033
| ^
41-
17 | }
42-
18 |
34+
24 | }
35+
25 | }

vlib/v/checker/tests/unknown_comptime_expr.vv

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
1-
fn unknown_at_ct() int { return 0 }
1+
fn unknown_at_ct() int {
2+
return 0
3+
}
24

35
fn main() {
46
mut foo := 0
5-
$if foo == 0 {}
7+
$if foo == 0 {
8+
}
69

710
bar := unknown_at_ct()
8-
$if bar == 0 {}
11+
$if bar == 0 {
12+
}
913
}
1014

1115
fn if_is() {
1216
s := S1{}
13-
$if huh.typ is T {}
14-
$if s is int {}
15-
$if s.i is 5 {}
16-
$if s.i is T {}
17+
$if huh.typ is T {
18+
}
19+
$if s is int {
20+
}
21+
$if s.i is 5 {
22+
}
23+
$if s.i is T {
24+
}
1725
}
1826

1927
struct S1 {
2028
i int
2129
}
22-

vlib/v/gen/c/comptime.v

Lines changed: 63 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,32 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
378378
}
379379
}
380380

381+
fn (mut g Gen) get_expr_type(cond ast.Expr) ast.Type {
382+
match cond {
383+
ast.Ident {
384+
return g.unwrap_generic(cond.obj.typ)
385+
}
386+
ast.TypeNode {
387+
return g.unwrap_generic(cond.typ)
388+
}
389+
ast.SelectorExpr {
390+
if cond.gkind_field == .typ {
391+
return g.unwrap_generic(cond.name_type)
392+
} else {
393+
name := '${cond.expr}.${cond.field_name}'
394+
if name in g.comptime_var_type_map {
395+
return g.comptime_var_type_map[name]
396+
} else {
397+
return g.unwrap_generic(cond.typ)
398+
}
399+
}
400+
}
401+
else {
402+
return ast.void_type
403+
}
404+
}
405+
}
406+
381407
// returns the value of the bool comptime expression and if next branches may be discarded
382408
// returning `false` means the statements inside the $if can be skipped
383409
fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
@@ -420,78 +446,57 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
420446
}
421447
.key_is, .not_is {
422448
left := cond.left
423-
mut name := ''
424-
if left is ast.TypeNode && cond.right is ast.ComptimeType {
425-
checked_type := g.unwrap_generic(left.typ)
426-
is_true := g.table.is_comptime_type(checked_type, cond.right)
427-
if cond.op == .key_is {
428-
if is_true {
429-
g.write('1')
430-
} else {
431-
g.write('0')
432-
}
433-
return is_true, true
434-
} else {
435-
if is_true {
436-
g.write('0')
437-
} else {
438-
g.write('1')
439-
}
440-
return !is_true, true
441-
}
442-
}
443-
mut exp_type := ast.Type(0)
444-
got_type := (cond.right as ast.TypeNode).typ
445-
// Handle `$if x is Interface {`
446-
// mut matches_interface := 'false'
447-
if left is ast.TypeNode && cond.right is ast.TypeNode
448-
&& g.table.sym(got_type).kind == .interface_ {
449-
// `$if Foo is Interface {`
450-
interface_sym := g.table.sym(got_type)
451-
if interface_sym.info is ast.Interface {
452-
// q := g.table.sym(interface_sym.info.types[0])
453-
checked_type := g.unwrap_generic(left.typ)
454-
// TODO PERF this check is run twice (also in the checker)
455-
// store the result in a field
456-
is_true := g.table.does_type_implement_interface(checked_type,
457-
got_type)
458-
// true // exp_type in interface_sym.info.types
449+
if left in [ast.TypeNode, ast.Ident, ast.SelectorExpr]
450+
&& cond.right in [ast.ComptimeType, ast.TypeNode] {
451+
exp_type := g.get_expr_type(left)
452+
if cond.right is ast.ComptimeType {
453+
is_true := g.table.is_comptime_type(exp_type, cond.right)
459454
if cond.op == .key_is {
460455
if is_true {
461456
g.write('1')
462457
} else {
463458
g.write('0')
464459
}
465460
return is_true, true
466-
} else if cond.op == .not_is {
461+
} else {
467462
if is_true {
468463
g.write('0')
469464
} else {
470465
g.write('1')
471466
}
472467
return !is_true, true
473468
}
474-
// matches_interface = '/*iface:$got_type $exp_type*/ true'
475-
//}
476-
}
477-
} else if left is ast.SelectorExpr {
478-
if left.gkind_field == .typ {
479-
exp_type = g.unwrap_generic(left.name_type)
480469
} else {
481-
name = '${left.expr}.${left.field_name}'
482-
exp_type = g.comptime_var_type_map[name]
483-
}
484-
} else if left is ast.TypeNode {
485-
// this is only allowed for generics currently, otherwise blocked by checker
486-
exp_type = g.unwrap_generic(left.typ)
487-
}
470+
got_type := g.unwrap_generic((cond.right as ast.TypeNode).typ)
471+
got_sym := g.table.sym(got_type)
488472

489-
if cond.op == .key_is {
490-
g.write('${exp_type.idx()} == ${got_type.idx()} && ${exp_type.has_flag(.option)} == ${got_type.has_flag(.option)}')
491-
return exp_type == got_type, true
492-
} else {
493-
g.write('${exp_type.idx()} != ${got_type.idx()}')
494-
return exp_type != got_type, true
473+
if got_sym.kind == .interface_ && got_sym.info is ast.Interface {
474+
is_true := g.table.does_type_implement_interface(exp_type,
475+
got_type)
476+
if cond.op == .key_is {
477+
if is_true {
478+
g.write('1')
479+
} else {
480+
g.write('0')
481+
}
482+
return is_true, true
483+
} else if cond.op == .not_is {
484+
if is_true {
485+
g.write('0')
486+
} else {
487+
g.write('1')
488+
}
489+
return !is_true, true
490+
}
491+
}
492+
if cond.op == .key_is {
493+
g.write('${exp_type.idx()} == ${got_type.idx()} && ${exp_type.has_flag(.option)} == ${got_type.has_flag(.option)}')
494+
return exp_type == got_type, true
495+
} else {
496+
g.write('${exp_type.idx()} != ${got_type.idx()}')
497+
return exp_type != got_type, true
498+
}
499+
}
495500
}
496501
}
497502
.eq, .ne {
@@ -523,17 +528,7 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
523528
.key_in, .not_in {
524529
if (cond.left is ast.TypeNode || cond.left is ast.SelectorExpr)
525530
&& cond.right is ast.ArrayInit {
526-
mut checked_type := ast.Type(0)
527-
if cond.left is ast.SelectorExpr {
528-
if cond.left.gkind_field == .typ {
529-
checked_type = g.unwrap_generic(cond.left.name_type)
530-
} else {
531-
name := '${cond.left.expr}.${cond.left.field_name}'
532-
checked_type = g.comptime_var_type_map[name]
533-
}
534-
} else {
535-
checked_type = g.unwrap_generic((cond.left as ast.TypeNode).typ)
536-
}
531+
checked_type := g.get_expr_type(cond.left)
537532

538533
for expr in cond.right.exprs {
539534
if expr is ast.ComptimeType {
@@ -767,6 +762,7 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
767762
g.writeln('\t${node.val_var}.indirections = ${field.typ.nr_muls()};')
768763
//
769764
g.comptime_var_type_map['${node.val_var}.typ'] = styp
765+
g.comptime_var_type_map['${node.val_var}.unaliased_typ'] = unaliased_styp
770766
g.stmts(node.stmts)
771767
i++
772768
g.writeln('}')
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
type MyAlias = string
2+
type MyAlias2 = rune
3+
4+
struct Foo {
5+
a string
6+
b ?string
7+
c MyAlias
8+
d MyAlias2
9+
e []int
10+
}
11+
12+
fn is_t[T](val T) bool {
13+
$if val is T {
14+
return true
15+
}
16+
return false
17+
}
18+
19+
fn test_main() {
20+
a := Foo{}
21+
$for field in Foo.fields {
22+
$if field.unaliased_typ is ?string {
23+
assert field.name == 'b'
24+
println('is option string')
25+
} $else $if field.unaliased_typ is string {
26+
assert field.name in ['a', 'c']
27+
println('is string')
28+
} $else $if field.unaliased_typ is MyAlias {
29+
assert false
30+
println('is string')
31+
} $else $if field.typ is MyAlias2 {
32+
assert field.name == 'd'
33+
println('is MyAlias')
34+
} $else $if field.typ is []int {
35+
assert field.name == 'e'
36+
println('is int')
37+
} $else {
38+
assert false
39+
}
40+
}
41+
}
42+
43+
fn test_generic() {
44+
a := Foo{}
45+
$for field in Foo.fields {
46+
if is_t(field) {
47+
println('T')
48+
assert true
49+
} else {
50+
assert false
51+
}
52+
val := a.$(field.name)
53+
if is_t(val) {
54+
println('T')
55+
assert true
56+
} else {
57+
assert false
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)