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)
|
||||
}
|
||||
}
|
||||
.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 {
|
||||
c.error('invalid `\$if` condition', cond.pos)
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ pub mut:
|
||||
in_lambda_depth int
|
||||
inside_const bool
|
||||
inside_unsafe bool
|
||||
inside_comptime_if bool
|
||||
is_mbranch_expr bool // match a { x...y { } }
|
||||
fn_scope &ast.Scope = unsafe { nil }
|
||||
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) {
|
||||
dollar := if node.is_comptime { '$' } else { '' }
|
||||
f.inside_comptime_if = node.is_comptime
|
||||
mut is_ternary := node.branches.len == 2 && node.has_else
|
||||
&& 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)
|
||||
@ -2057,6 +2059,7 @@ pub fn (mut f Fmt) if_expr(node ast.IfExpr) {
|
||||
}
|
||||
f.write('}')
|
||||
f.single_line_if = false
|
||||
f.inside_comptime_if = false
|
||||
if node.post_comments.len > 0 {
|
||||
f.writeln('')
|
||||
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
|
||||
&& (node.right as ast.ArrayInit).exprs.len == 1
|
||||
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`
|
||||
op := if node.op == .key_in { ' == ' } else { ' != ' }
|
||||
f.write(op)
|
||||
@ -2127,7 +2130,7 @@ pub fn (mut f Fmt) infix_expr(node ast.InfixExpr) {
|
||||
} else {
|
||||
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`
|
||||
f.expr((node.right as ast.ArrayInit).exprs[0])
|
||||
} 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
|
||||
}
|
||||
}
|
||||
.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 {
|
||||
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)
|
||||
|| (p.peek_tok.kind == .dot && p.peek_token(2).lit.len > 0
|
||||
&& 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()
|
||||
typ := p.parse_type()
|
||||
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