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 {
|
} else {
|
||||||
.skip
|
.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`
|
// `$if method.@type is string`
|
||||||
c.expr(cond.left)
|
c.expr(cond.left)
|
||||||
return .unknown
|
return .unknown
|
||||||
|
@ -94,6 +94,17 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
|||||||
} else {
|
} else {
|
||||||
.skip
|
.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 {
|
} else {
|
||||||
got_type := c.unwrap_generic((right as ast.TypeNode).typ)
|
got_type := c.unwrap_generic((right as ast.TypeNode).typ)
|
||||||
sym := c.table.sym(got_type)
|
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
|
vlib/v/checker/tests/unknown_comptime_expr.vv:7:6: error: `foo` is mut and may have changed since its definition
|
||||||
3 | fn main() {
|
5 | fn main() {
|
||||||
4 | mut foo := 0
|
6 | mut foo := 0
|
||||||
5 | $if foo == 0 {}
|
7 | $if foo == 0 {
|
||||||
| ~~~
|
| ~~~
|
||||||
6 |
|
8 | }
|
||||||
7 | bar := unknown_at_ct()
|
9 |
|
||||||
vlib/v/checker/tests/unknown_comptime_expr.vv:8:6: error: definition of `bar` is unknown at compile time
|
vlib/v/checker/tests/unknown_comptime_expr.vv:11:6: error: definition of `bar` is unknown at compile time
|
||||||
6 |
|
9 |
|
||||||
7 | bar := unknown_at_ct()
|
10 | bar := unknown_at_ct()
|
||||||
8 | $if bar == 0 {}
|
11 | $if bar == 0 {
|
||||||
| ~~~
|
| ~~~
|
||||||
9 | }
|
12 | }
|
||||||
10 |
|
13 | }
|
||||||
vlib/v/checker/tests/unknown_comptime_expr.vv:13:6: error: undefined ident: `huh`
|
vlib/v/checker/tests/unknown_comptime_expr.vv:17:6: error: undefined ident: `huh`
|
||||||
11 | fn if_is() {
|
15 | fn if_is() {
|
||||||
12 | s := S1{}
|
16 | s := S1{}
|
||||||
13 | $if huh.typ is T {}
|
17 | $if huh.typ is T {
|
||||||
| ~~~
|
| ~~~
|
||||||
14 | $if s is int {}
|
18 | }
|
||||||
15 | $if s.i is 5 {}
|
19 | $if s is int {
|
||||||
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
|
vlib/v/checker/tests/unknown_comptime_expr.vv:21:13: error: invalid `$if` condition: expected a type
|
||||||
12 | s := S1{}
|
19 | $if s is int {
|
||||||
13 | $if huh.typ is T {}
|
20 | }
|
||||||
14 | $if s is int {}
|
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 {}
|
|
||||||
| ^
|
| ^
|
||||||
16 | $if s.i is T {}
|
22 | }
|
||||||
17 | }
|
23 | $if s.i is T {
|
||||||
vlib/v/checker/tests/unknown_comptime_expr.vv:16:13: error: unknown type `T`
|
vlib/v/checker/tests/unknown_comptime_expr.vv:23:13: error: unknown type `T`
|
||||||
14 | $if s is int {}
|
21 | $if s.i is 5 {
|
||||||
15 | $if s.i is 5 {}
|
22 | }
|
||||||
16 | $if s.i is T {}
|
23 | $if s.i is T {
|
||||||
| ^
|
| ^
|
||||||
17 | }
|
24 | }
|
||||||
18 |
|
25 | }
|
||||||
|
@ -1,22 +1,29 @@
|
|||||||
fn unknown_at_ct() int { return 0 }
|
fn unknown_at_ct() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
mut foo := 0
|
mut foo := 0
|
||||||
$if foo == 0 {}
|
$if foo == 0 {
|
||||||
|
}
|
||||||
|
|
||||||
bar := unknown_at_ct()
|
bar := unknown_at_ct()
|
||||||
$if bar == 0 {}
|
$if bar == 0 {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn if_is() {
|
fn if_is() {
|
||||||
s := S1{}
|
s := S1{}
|
||||||
$if huh.typ is T {}
|
$if huh.typ is T {
|
||||||
$if s is int {}
|
}
|
||||||
$if s.i is 5 {}
|
$if s is int {
|
||||||
$if s.i is T {}
|
}
|
||||||
|
$if s.i is 5 {
|
||||||
|
}
|
||||||
|
$if s.i is T {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct S1 {
|
struct S1 {
|
||||||
i int
|
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
|
// 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
|
// 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) {
|
fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
|
||||||
@ -420,42 +446,11 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
|
|||||||
}
|
}
|
||||||
.key_is, .not_is {
|
.key_is, .not_is {
|
||||||
left := cond.left
|
left := cond.left
|
||||||
mut name := ''
|
if left in [ast.TypeNode, ast.Ident, ast.SelectorExpr]
|
||||||
if left is ast.TypeNode && cond.right is ast.ComptimeType {
|
&& cond.right in [ast.ComptimeType, ast.TypeNode] {
|
||||||
checked_type := g.unwrap_generic(left.typ)
|
exp_type := g.get_expr_type(left)
|
||||||
is_true := g.table.is_comptime_type(checked_type, cond.right)
|
if cond.right is ast.ComptimeType {
|
||||||
if cond.op == .key_is {
|
is_true := g.table.is_comptime_type(exp_type, cond.right)
|
||||||
if is_true {
|
|
||||||
g.write('1')
|
|
||||||
} else {
|
|
||||||
g.write('0')
|
|
||||||
}
|
|
||||||
return is_true, true
|
|
||||||
} else {
|
|
||||||
if is_true {
|
|
||||||
g.write('0')
|
|
||||||
} else {
|
|
||||||
g.write('1')
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
got_type)
|
|
||||||
// true // exp_type in interface_sym.info.types
|
|
||||||
if cond.op == .key_is {
|
if cond.op == .key_is {
|
||||||
if is_true {
|
if is_true {
|
||||||
g.write('1')
|
g.write('1')
|
||||||
@ -463,7 +458,7 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
|
|||||||
g.write('0')
|
g.write('0')
|
||||||
}
|
}
|
||||||
return is_true, true
|
return is_true, true
|
||||||
} else if cond.op == .not_is {
|
} else {
|
||||||
if is_true {
|
if is_true {
|
||||||
g.write('0')
|
g.write('0')
|
||||||
} else {
|
} else {
|
||||||
@ -471,27 +466,37 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
|
|||||||
}
|
}
|
||||||
return !is_true, true
|
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 {
|
} else {
|
||||||
name = '${left.expr}.${left.field_name}'
|
got_type := g.unwrap_generic((cond.right as ast.TypeNode).typ)
|
||||||
exp_type = g.comptime_var_type_map[name]
|
got_sym := g.table.sym(got_type)
|
||||||
}
|
|
||||||
} 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 {
|
if got_sym.kind == .interface_ && got_sym.info is ast.Interface {
|
||||||
g.write('${exp_type.idx()} == ${got_type.idx()} && ${exp_type.has_flag(.option)} == ${got_type.has_flag(.option)}')
|
is_true := g.table.does_type_implement_interface(exp_type,
|
||||||
return exp_type == got_type, true
|
got_type)
|
||||||
} else {
|
if cond.op == .key_is {
|
||||||
g.write('${exp_type.idx()} != ${got_type.idx()}')
|
if is_true {
|
||||||
return exp_type != got_type, true
|
g.write('1')
|
||||||
|
} else {
|
||||||
|
g.write('0')
|
||||||
|
}
|
||||||
|
return is_true, true
|
||||||
|
} else if cond.op == .not_is {
|
||||||
|
if is_true {
|
||||||
|
g.write('0')
|
||||||
|
} else {
|
||||||
|
g.write('1')
|
||||||
|
}
|
||||||
|
return !is_true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
g.write('${exp_type.idx()} != ${got_type.idx()}')
|
||||||
|
return exp_type != got_type, true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.eq, .ne {
|
.eq, .ne {
|
||||||
@ -523,17 +528,7 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
|
|||||||
.key_in, .not_in {
|
.key_in, .not_in {
|
||||||
if (cond.left is ast.TypeNode || cond.left is ast.SelectorExpr)
|
if (cond.left is ast.TypeNode || cond.left is ast.SelectorExpr)
|
||||||
&& cond.right is ast.ArrayInit {
|
&& cond.right is ast.ArrayInit {
|
||||||
mut checked_type := ast.Type(0)
|
checked_type := g.get_expr_type(cond.left)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
for expr in cond.right.exprs {
|
for expr in cond.right.exprs {
|
||||||
if expr is ast.ComptimeType {
|
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.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}.typ'] = styp
|
||||||
|
g.comptime_var_type_map['${node.val_var}.unaliased_typ'] = unaliased_styp
|
||||||
g.stmts(node.stmts)
|
g.stmts(node.stmts)
|
||||||
i++
|
i++
|
||||||
g.writeln('}')
|
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…
x
Reference in New Issue
Block a user