mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
checker, cgen: support $if T in [$Array, $Struct[operator for comptime type checking (#16896)
This commit is contained in:
parent
942130ff6e
commit
1b78f430ab
@ -604,6 +604,19 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) ComptimeBran
|
|||||||
cond.pos)
|
cond.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.key_in, .not_in {
|
||||||
|
if cond.left in [ast.SelectorExpr, ast.TypeNode] && cond.right is ast.ArrayInit {
|
||||||
|
for expr in cond.right.exprs {
|
||||||
|
if expr !is ast.ComptimeType && expr !is ast.TypeNode {
|
||||||
|
c.error('invalid `\$if` condition, only types are allowed',
|
||||||
|
expr.pos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .unknown
|
||||||
|
} else {
|
||||||
|
c.error('invalid `\$if` condition', cond.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
c.error('invalid `\$if` condition', cond.pos)
|
c.error('invalid `\$if` condition', cond.pos)
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ pub mut:
|
|||||||
in_lambda_depth int
|
in_lambda_depth int
|
||||||
inside_const bool
|
inside_const bool
|
||||||
inside_unsafe bool
|
inside_unsafe bool
|
||||||
|
inside_comptime_if bool
|
||||||
is_mbranch_expr bool // match a { x...y { } }
|
is_mbranch_expr bool // match a { x...y { } }
|
||||||
fn_scope &ast.Scope = unsafe { nil }
|
fn_scope &ast.Scope = unsafe { nil }
|
||||||
wsinfix_depth int
|
wsinfix_depth int
|
||||||
@ -1998,6 +1999,7 @@ pub fn (mut f Fmt) ident(node ast.Ident) {
|
|||||||
|
|
||||||
pub fn (mut f Fmt) if_expr(node ast.IfExpr) {
|
pub fn (mut f Fmt) if_expr(node ast.IfExpr) {
|
||||||
dollar := if node.is_comptime { '$' } else { '' }
|
dollar := if node.is_comptime { '$' } else { '' }
|
||||||
|
f.inside_comptime_if = node.is_comptime
|
||||||
mut is_ternary := node.branches.len == 2 && node.has_else
|
mut is_ternary := node.branches.len == 2 && node.has_else
|
||||||
&& branch_is_single_line(node.branches[0]) && branch_is_single_line(node.branches[1])
|
&& branch_is_single_line(node.branches[0]) && branch_is_single_line(node.branches[1])
|
||||||
&& (node.is_expr || f.is_assign || f.is_struct_init || f.single_line_fields)
|
&& (node.is_expr || f.is_assign || f.is_struct_init || f.single_line_fields)
|
||||||
@ -2057,6 +2059,7 @@ pub fn (mut f Fmt) if_expr(node ast.IfExpr) {
|
|||||||
}
|
}
|
||||||
f.write('}')
|
f.write('}')
|
||||||
f.single_line_if = false
|
f.single_line_if = false
|
||||||
|
f.inside_comptime_if = false
|
||||||
if node.post_comments.len > 0 {
|
if node.post_comments.len > 0 {
|
||||||
f.writeln('')
|
f.writeln('')
|
||||||
f.comments(node.post_comments,
|
f.comments(node.post_comments,
|
||||||
@ -2118,7 +2121,7 @@ pub fn (mut f Fmt) infix_expr(node ast.InfixExpr) {
|
|||||||
is_one_val_array_init := node.op in [.key_in, .not_in] && node.right is ast.ArrayInit
|
is_one_val_array_init := node.op in [.key_in, .not_in] && node.right is ast.ArrayInit
|
||||||
&& (node.right as ast.ArrayInit).exprs.len == 1
|
&& (node.right as ast.ArrayInit).exprs.len == 1
|
||||||
is_and := node.op == .amp && f.node_str(node.right).starts_with('&')
|
is_and := node.op == .amp && f.node_str(node.right).starts_with('&')
|
||||||
if is_one_val_array_init {
|
if is_one_val_array_init && !f.inside_comptime_if {
|
||||||
// `var in [val]` => `var == val`
|
// `var in [val]` => `var == val`
|
||||||
op := if node.op == .key_in { ' == ' } else { ' != ' }
|
op := if node.op == .key_in { ' == ' } else { ' != ' }
|
||||||
f.write(op)
|
f.write(op)
|
||||||
@ -2127,7 +2130,7 @@ pub fn (mut f Fmt) infix_expr(node ast.InfixExpr) {
|
|||||||
} else {
|
} else {
|
||||||
f.write(' ${node.op.str()} ')
|
f.write(' ${node.op.str()} ')
|
||||||
}
|
}
|
||||||
if is_one_val_array_init {
|
if is_one_val_array_init && !f.inside_comptime_if {
|
||||||
// `var in [val]` => `var == val`
|
// `var in [val]` => `var == val`
|
||||||
f.expr((node.right as ast.ArrayInit).exprs[0])
|
f.expr((node.right as ast.ArrayInit).exprs[0])
|
||||||
} else if is_and {
|
} else if is_and {
|
||||||
|
@ -505,6 +505,56 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
|
|||||||
return true, true
|
return true, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
for expr in cond.right.exprs {
|
||||||
|
if expr is ast.ComptimeType {
|
||||||
|
if g.table.is_comptime_type(checked_type, expr as ast.ComptimeType) {
|
||||||
|
if cond.op == .key_in {
|
||||||
|
g.write('1')
|
||||||
|
} else {
|
||||||
|
g.write('0')
|
||||||
|
}
|
||||||
|
return cond.op == .key_in, true
|
||||||
|
}
|
||||||
|
} else if expr is ast.TypeNode {
|
||||||
|
got_type := g.unwrap_generic(expr.typ)
|
||||||
|
is_true := checked_type.idx() == got_type.idx()
|
||||||
|
&& checked_type.has_flag(.optional) == got_type.has_flag(.optional)
|
||||||
|
if is_true {
|
||||||
|
if cond.op == .key_in {
|
||||||
|
g.write('1')
|
||||||
|
} else {
|
||||||
|
g.write('0')
|
||||||
|
}
|
||||||
|
return cond.op == .key_in, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cond.op == .not_in {
|
||||||
|
g.write('1')
|
||||||
|
} else {
|
||||||
|
g.write('0')
|
||||||
|
}
|
||||||
|
return cond.op == .not_in, true
|
||||||
|
} else {
|
||||||
|
g.write('1')
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
return true, false
|
return true, false
|
||||||
}
|
}
|
||||||
|
@ -2629,7 +2629,8 @@ pub fn (mut p Parser) name_expr() ast.Expr {
|
|||||||
if p.inside_in_array && ((lit0_is_capital && !known_var && language == .v)
|
if p.inside_in_array && ((lit0_is_capital && !known_var && language == .v)
|
||||||
|| (p.peek_tok.kind == .dot && p.peek_token(2).lit.len > 0
|
|| (p.peek_tok.kind == .dot && p.peek_token(2).lit.len > 0
|
||||||
&& p.peek_token(2).lit[0].is_capital())
|
&& p.peek_token(2).lit[0].is_capital())
|
||||||
|| p.table.find_type_idx(p.mod + '.' + p.tok.lit) > 0) {
|
|| p.table.find_type_idx(p.mod + '.' + p.tok.lit) > 0
|
||||||
|
|| p.inside_comptime_if) {
|
||||||
type_pos := p.tok.pos()
|
type_pos := p.tok.pos()
|
||||||
typ := p.parse_type()
|
typ := p.parse_type()
|
||||||
return ast.TypeNode{
|
return ast.TypeNode{
|
||||||
|
86
vlib/v/tests/check_in_is_consistency_test.v
Normal file
86
vlib/v/tests/check_in_is_consistency_test.v
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
type Abc = f64 | int
|
||||||
|
|
||||||
|
struct Test {
|
||||||
|
a int
|
||||||
|
b ?int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check[T](val T) string {
|
||||||
|
$if T in [?int, ?int] {
|
||||||
|
return 'optional int'
|
||||||
|
}
|
||||||
|
$if T in [int, int] {
|
||||||
|
return 'int'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check2[T](val T) string {
|
||||||
|
mut str := string{}
|
||||||
|
$for field in T.fields {
|
||||||
|
$if field.typ in [?int, ?int] {
|
||||||
|
str += 'optional int'
|
||||||
|
}
|
||||||
|
$if field.typ in [int, int] {
|
||||||
|
str += 'int'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_is[T](val T) string {
|
||||||
|
$if T is ?int {
|
||||||
|
return 'optional int'
|
||||||
|
}
|
||||||
|
$if T is int {
|
||||||
|
return 'int'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_is2[T](val T) string {
|
||||||
|
mut str := string{}
|
||||||
|
$for field in T.fields {
|
||||||
|
$if field.typ is ?int {
|
||||||
|
str += 'optional int'
|
||||||
|
}
|
||||||
|
$if field.typ is int {
|
||||||
|
str += 'int'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_in() {
|
||||||
|
var := Test{
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
}
|
||||||
|
assert check(var.a) == 'int'
|
||||||
|
assert check(var.b?) == 'int'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_in_2() {
|
||||||
|
var := Test{
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
}
|
||||||
|
assert check2(var) == 'intoptional int'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_is() {
|
||||||
|
var := Test{
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
}
|
||||||
|
assert check_is(var.a) == 'int'
|
||||||
|
assert check_is(var.b?) == 'int'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_is_2() {
|
||||||
|
var := Test{
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
}
|
||||||
|
assert check_is2(var) == 'intoptional int'
|
||||||
|
}
|
95
vlib/v/tests/comptime_in_type_checking_test.v
Normal file
95
vlib/v/tests/comptime_in_type_checking_test.v
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
type Abc = f64 | int
|
||||||
|
|
||||||
|
struct Test {
|
||||||
|
a int
|
||||||
|
b []string
|
||||||
|
c f64
|
||||||
|
d Abc
|
||||||
|
e map[string]string
|
||||||
|
f struct {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check[T](val T) string {
|
||||||
|
$if T in [$Array, $Struct] {
|
||||||
|
return 'array or struct'
|
||||||
|
} $else $if T in [u8, u16, u32] {
|
||||||
|
return 'unsigned int'
|
||||||
|
} $else $if T in [int, $Int] {
|
||||||
|
return 'int'
|
||||||
|
} $else $if T in [$Float, f64, f32] {
|
||||||
|
return 'f64'
|
||||||
|
} $else $if T in [$Map, map] {
|
||||||
|
return 'map'
|
||||||
|
} $else $if T in [Abc, Abc] {
|
||||||
|
return 'Abc'
|
||||||
|
} $else $if T in [$Sumtype, Abc] {
|
||||||
|
return 'sumtype'
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_not[T](val T) string {
|
||||||
|
mut str := string{}
|
||||||
|
$if T !in [$Array, $Array] {
|
||||||
|
str += '1'
|
||||||
|
}
|
||||||
|
$if T !in [u8, u16, u32] {
|
||||||
|
str += '2'
|
||||||
|
}
|
||||||
|
$if T !in [int, $Int] {
|
||||||
|
str += '3'
|
||||||
|
}
|
||||||
|
$if T !in [$Float, f64, f32] {
|
||||||
|
str += '4'
|
||||||
|
} $else $if T !in [$Map, map] {
|
||||||
|
str += '5'
|
||||||
|
} $else $if T !in [Abc, Abc] {
|
||||||
|
str += '6'
|
||||||
|
} $else $if T !in [$Sumtype, Abc] {
|
||||||
|
str += '7'
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_in() {
|
||||||
|
var := Test{
|
||||||
|
a: 1
|
||||||
|
b: ['foo']
|
||||||
|
c: 1.2
|
||||||
|
d: 1
|
||||||
|
e: {
|
||||||
|
'a': 'b'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert check(var) == 'array or struct'
|
||||||
|
assert check(var.a) == 'int'
|
||||||
|
assert check(var.b) == 'array or struct'
|
||||||
|
assert check(var.c) == 'f64'
|
||||||
|
assert check(var.d) == 'Abc'
|
||||||
|
assert check(var.e) == 'map'
|
||||||
|
assert check(var.f) == 'array or struct'
|
||||||
|
assert check(Test{}) == 'array or struct'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_not_in() {
|
||||||
|
var := Test{
|
||||||
|
a: 1
|
||||||
|
b: ['foo']
|
||||||
|
c: 1.2
|
||||||
|
d: 1
|
||||||
|
e: {
|
||||||
|
'a': 'b'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert check_not(var) == '1234'
|
||||||
|
assert check_not(var.a) == '124'
|
||||||
|
assert check_not(var.b) == '234'
|
||||||
|
assert check_not(var.c) == '1235'
|
||||||
|
assert check_not(var.d) == '1234'
|
||||||
|
assert check_not(var.e) == '1234'
|
||||||
|
assert check_not(var.f) == '1234'
|
||||||
|
assert check_not(Test{}) == '1234'
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user