diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 9f551f21a4..597fc19549 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -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) } diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 6edefd15f2..6bc44b3645 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -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 { diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 1d6f1585e5..421c81a29f 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -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 } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index bfffc4251f..4bd83cce92 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -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{ diff --git a/vlib/v/tests/check_in_is_consistency_test.v b/vlib/v/tests/check_in_is_consistency_test.v new file mode 100644 index 0000000000..2a1563366d --- /dev/null +++ b/vlib/v/tests/check_in_is_consistency_test.v @@ -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' +} diff --git a/vlib/v/tests/comptime_in_type_checking_test.v b/vlib/v/tests/comptime_in_type_checking_test.v new file mode 100644 index 0000000000..db82cc7272 --- /dev/null +++ b/vlib/v/tests/comptime_in_type_checking_test.v @@ -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' +}