1
0
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:
Felipe Pena 2023-01-08 13:22:10 -03:00 committed by GitHub
parent 942130ff6e
commit 1b78f430ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 251 additions and 3 deletions

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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{

View 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'
}

View 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'
}