1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

cgen, checker: var type checking at compile-time (#16951)

This commit is contained in:
Felipe Pena 2023-01-14 11:20:12 -03:00 committed by GitHub
parent b19db3a207
commit dbfb9c3a90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 226 additions and 113 deletions

View File

@ -551,7 +551,7 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) ComptimeBran
} else {
.skip
}
} else if cond.left in [ast.SelectorExpr, ast.TypeNode] {
} else if cond.left in [ast.Ident, ast.SelectorExpr, ast.TypeNode] {
// `$if method.@type is string`
c.expr(cond.left)
return .unknown

View File

@ -94,6 +94,17 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
} else {
.skip
}
} else if right is ast.ComptimeType && left is ast.Ident
&& (left as ast.Ident).info is ast.IdentVar {
is_comptime_type_is_expr = true
if var := left.scope.find_var(left.name) {
checked_type := c.unwrap_generic(var.typ)
skip_state = if c.table.is_comptime_type(checked_type, right as ast.ComptimeType) {
.eval
} else {
.skip
}
}
} else {
got_type := c.unwrap_generic((right as ast.TypeNode).typ)
sym := c.table.sym(got_type)

View File

@ -1,42 +1,35 @@
vlib/v/checker/tests/unknown_comptime_expr.vv:5:6: error: `foo` is mut and may have changed since its definition
3 | fn main() {
4 | mut foo := 0
5 | $if foo == 0 {}
vlib/v/checker/tests/unknown_comptime_expr.vv:7:6: error: `foo` is mut and may have changed since its definition
5 | fn main() {
6 | mut foo := 0
7 | $if foo == 0 {
| ~~~
6 |
7 | bar := unknown_at_ct()
vlib/v/checker/tests/unknown_comptime_expr.vv:8:6: error: definition of `bar` is unknown at compile time
6 |
7 | bar := unknown_at_ct()
8 | $if bar == 0 {}
8 | }
9 |
vlib/v/checker/tests/unknown_comptime_expr.vv:11:6: error: definition of `bar` is unknown at compile time
9 |
10 | bar := unknown_at_ct()
11 | $if bar == 0 {
| ~~~
9 | }
10 |
vlib/v/checker/tests/unknown_comptime_expr.vv:13:6: error: undefined ident: `huh`
11 | fn if_is() {
12 | s := S1{}
13 | $if huh.typ is T {}
12 | }
13 | }
vlib/v/checker/tests/unknown_comptime_expr.vv:17:6: error: undefined ident: `huh`
15 | fn if_is() {
16 | s := S1{}
17 | $if huh.typ is T {
| ~~~
14 | $if s is int {}
15 | $if s.i is 5 {}
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
12 | s := S1{}
13 | $if huh.typ is T {}
14 | $if s is int {}
18 | }
19 | $if s is int {
vlib/v/checker/tests/unknown_comptime_expr.vv:21:13: error: invalid `$if` condition: expected a type
19 | $if s is int {
20 | }
21 | $if s.i is 5 {
| ^
15 | $if s.i is 5 {}
16 | $if s.i is T {}
vlib/v/checker/tests/unknown_comptime_expr.vv:15:13: error: invalid `$if` condition: expected a type
13 | $if huh.typ is T {}
14 | $if s is int {}
15 | $if s.i is 5 {}
22 | }
23 | $if s.i is T {
vlib/v/checker/tests/unknown_comptime_expr.vv:23:13: error: unknown type `T`
21 | $if s.i is 5 {
22 | }
23 | $if s.i is T {
| ^
16 | $if s.i is T {}
17 | }
vlib/v/checker/tests/unknown_comptime_expr.vv:16:13: error: unknown type `T`
14 | $if s is int {}
15 | $if s.i is 5 {}
16 | $if s.i is T {}
| ^
17 | }
18 |
24 | }
25 | }

View File

@ -1,22 +1,29 @@
fn unknown_at_ct() int { return 0 }
fn unknown_at_ct() int {
return 0
}
fn main() {
mut foo := 0
$if foo == 0 {}
$if foo == 0 {
}
bar := unknown_at_ct()
$if bar == 0 {}
$if bar == 0 {
}
}
fn if_is() {
s := S1{}
$if huh.typ is T {}
$if s is int {}
$if s.i is 5 {}
$if s.i is T {}
$if huh.typ is T {
}
$if s is int {
}
$if s.i is 5 {
}
$if s.i is T {
}
}
struct S1 {
i int
}

View File

@ -378,6 +378,32 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
}
}
fn (mut g Gen) get_expr_type(cond ast.Expr) ast.Type {
match cond {
ast.Ident {
return g.unwrap_generic(cond.obj.typ)
}
ast.TypeNode {
return g.unwrap_generic(cond.typ)
}
ast.SelectorExpr {
if cond.gkind_field == .typ {
return g.unwrap_generic(cond.name_type)
} else {
name := '${cond.expr}.${cond.field_name}'
if name in g.comptime_var_type_map {
return g.comptime_var_type_map[name]
} else {
return g.unwrap_generic(cond.typ)
}
}
}
else {
return ast.void_type
}
}
}
// returns the value of the bool comptime expression and if next branches may be discarded
// returning `false` means the statements inside the $if can be skipped
fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
@ -420,10 +446,11 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
}
.key_is, .not_is {
left := cond.left
mut name := ''
if left is ast.TypeNode && cond.right is ast.ComptimeType {
checked_type := g.unwrap_generic(left.typ)
is_true := g.table.is_comptime_type(checked_type, cond.right)
if left in [ast.TypeNode, ast.Ident, ast.SelectorExpr]
&& cond.right in [ast.ComptimeType, ast.TypeNode] {
exp_type := g.get_expr_type(left)
if cond.right is ast.ComptimeType {
is_true := g.table.is_comptime_type(exp_type, cond.right)
if cond.op == .key_is {
if is_true {
g.write('1')
@ -439,23 +466,13 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
}
return !is_true, true
}
}
mut exp_type := ast.Type(0)
got_type := (cond.right as ast.TypeNode).typ
// Handle `$if x is Interface {`
// mut matches_interface := 'false'
if left is ast.TypeNode && cond.right is ast.TypeNode
&& g.table.sym(got_type).kind == .interface_ {
// `$if Foo is Interface {`
interface_sym := g.table.sym(got_type)
if interface_sym.info is ast.Interface {
// q := g.table.sym(interface_sym.info.types[0])
checked_type := g.unwrap_generic(left.typ)
// TODO PERF this check is run twice (also in the checker)
// store the result in a field
is_true := g.table.does_type_implement_interface(checked_type,
} else {
got_type := g.unwrap_generic((cond.right as ast.TypeNode).typ)
got_sym := g.table.sym(got_type)
if got_sym.kind == .interface_ && got_sym.info is ast.Interface {
is_true := g.table.does_type_implement_interface(exp_type,
got_type)
// true // exp_type in interface_sym.info.types
if cond.op == .key_is {
if is_true {
g.write('1')
@ -471,21 +488,7 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
}
return !is_true, true
}
// matches_interface = '/*iface:$got_type $exp_type*/ true'
//}
}
} else if left is ast.SelectorExpr {
if left.gkind_field == .typ {
exp_type = g.unwrap_generic(left.name_type)
} else {
name = '${left.expr}.${left.field_name}'
exp_type = g.comptime_var_type_map[name]
}
} else if left is ast.TypeNode {
// this is only allowed for generics currently, otherwise blocked by checker
exp_type = g.unwrap_generic(left.typ)
}
if cond.op == .key_is {
g.write('${exp_type.idx()} == ${got_type.idx()} && ${exp_type.has_flag(.option)} == ${got_type.has_flag(.option)}')
return exp_type == got_type, true
@ -494,6 +497,8 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
return exp_type != got_type, true
}
}
}
}
.eq, .ne {
// TODO Implement `$if method.args.len == 1`
if cond.left is ast.SelectorExpr && g.comptime_for_method.len > 0
@ -523,17 +528,7 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
.key_in, .not_in {
if (cond.left is ast.TypeNode || cond.left is ast.SelectorExpr)
&& cond.right is ast.ArrayInit {
mut checked_type := ast.Type(0)
if cond.left is ast.SelectorExpr {
if cond.left.gkind_field == .typ {
checked_type = g.unwrap_generic(cond.left.name_type)
} else {
name := '${cond.left.expr}.${cond.left.field_name}'
checked_type = g.comptime_var_type_map[name]
}
} else {
checked_type = g.unwrap_generic((cond.left as ast.TypeNode).typ)
}
checked_type := g.get_expr_type(cond.left)
for expr in cond.right.exprs {
if expr is ast.ComptimeType {
@ -767,6 +762,7 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
g.writeln('\t${node.val_var}.indirections = ${field.typ.nr_muls()};')
//
g.comptime_var_type_map['${node.val_var}.typ'] = styp
g.comptime_var_type_map['${node.val_var}.unaliased_typ'] = unaliased_styp
g.stmts(node.stmts)
i++
g.writeln('}')

View File

@ -0,0 +1,60 @@
type MyAlias = string
type MyAlias2 = rune
struct Foo {
a string
b ?string
c MyAlias
d MyAlias2
e []int
}
fn is_t[T](val T) bool {
$if val is T {
return true
}
return false
}
fn test_main() {
a := Foo{}
$for field in Foo.fields {
$if field.unaliased_typ is ?string {
assert field.name == 'b'
println('is option string')
} $else $if field.unaliased_typ is string {
assert field.name in ['a', 'c']
println('is string')
} $else $if field.unaliased_typ is MyAlias {
assert false
println('is string')
} $else $if field.typ is MyAlias2 {
assert field.name == 'd'
println('is MyAlias')
} $else $if field.typ is []int {
assert field.name == 'e'
println('is int')
} $else {
assert false
}
}
}
fn test_generic() {
a := Foo{}
$for field in Foo.fields {
if is_t(field) {
println('T')
assert true
} else {
assert false
}
val := a.$(field.name)
if is_t(val) {
println('T')
assert true
} else {
assert false
}
}
}

View File

@ -0,0 +1,46 @@
struct Test {
a int
b ?int
}
fn test_main() {
hh := 1 // i64(4)
$if hh is $Int {
println('int')
assert true
} $else $if hh !is $Int {
println('string')
assert false
}
b := 1.2
$if b is $Int {
assert false
} $else $if b is $Float {
println('float')
assert true
}
c := true
$if c is bool {
println('bool')
assert true
}
d := Test{}
$if d.b is ?int {
println('?int')
assert true
}
$if d.a is ?int {
println('?int')
assert false
} $else $if d.a is int {
println('int')
assert true
}
$if d is Test {
println('Test')
}
}