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:
parent
b19db3a207
commit
dbfb9c3a90
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 | }
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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('}')
|
||||
|
60
vlib/v/tests/var_type_is_checking2_test.v
Normal file
60
vlib/v/tests/var_type_is_checking2_test.v
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
46
vlib/v/tests/var_type_is_checking_test.v
Normal file
46
vlib/v/tests/var_type_is_checking_test.v
Normal 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')
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user