diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 80a08da6db..9daee0c623 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -24,6 +24,7 @@ pub type Expr = AnonFn | Comment | ComptimeCall | ComptimeSelector + | ComptimeType | ConcatExpr | DumpExpr | EmptyExpr @@ -114,6 +115,36 @@ pub: pos token.Pos } +pub enum ComptimeTypeKind { + map_ + int + float + struct_ + iface + array + sum_type + enum_ +} + +pub struct ComptimeType { +pub: + kind ComptimeTypeKind + pos token.Pos +} + +pub fn (cty ComptimeType) str() string { + return match cty.kind { + .map_ { '\$Map' } + .int { '\$Int' } + .float { '\$Float' } + .struct_ { '\$Struct' } + .iface { '\$Interface' } + .array { '\$Array' } + .sum_type { '\$Sumtype' } + .enum_ { '\$Enum' } + } +} + pub struct EmptyExpr { x int } @@ -1683,7 +1714,7 @@ pub fn (expr Expr) pos() token.Pos { EnumVal, DumpExpr, FloatLiteral, GoExpr, Ident, IfExpr, IntegerLiteral, IsRefType, Likely, LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, - StructInit, TypeNode, TypeOf, UnsafeExpr { + StructInit, TypeNode, TypeOf, UnsafeExpr, ComptimeType { return expr.pos } IndexExpr { diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 28c9c8a6e6..f232c0226f 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -261,6 +261,9 @@ pub fn (x Expr) str() string { AnonFn { return 'anon_fn' } + ComptimeType { + return x.str() + } DumpExpr { return 'dump($x.expr.str())' } diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index cdc3008022..fdb4470b1c 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -2044,3 +2044,49 @@ pub fn (mut t Table) generic_insts_to_concrete() { } } } + +pub fn (t &Table) is_comptime_type(x Type, y ComptimeType) bool { + x_kind := t.type_kind(x) + match y.kind { + .map_ { + return x_kind == .map + } + .int { + return x_kind in [ + .i8, + .i16, + .int, + .i64, + .byte, + .u8, + .u16, + .u32, + .u64, + .usize, + .int_literal, + ] + } + .float { + return x_kind in [ + .f32, + .f64, + .float_literal, + ] + } + .struct_ { + return x_kind == .struct_ + } + .iface { + return x_kind == .interface_ + } + .array { + return x_kind in [.array, .array_fixed] + } + .sum_type { + return x_kind == .sum_type + } + .enum_ { + return x_kind == .enum_ + } + } +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 13fa9dda83..9e2a0f4e40 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2384,6 +2384,9 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type { } match mut node { ast.NodeError {} + ast.ComptimeType { + c.error('incorrect use of compile-time type', node.pos) + } ast.EmptyExpr { c.error('checker.expr(): unhandled EmptyExpr', token.Pos{}) } diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index e534044736..0a07ab2aec 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -468,6 +468,10 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { // c.error('`$sym.name` is not an interface', cond.right.pos()) } return false + } else if cond.left is ast.TypeNode && cond.right is ast.ComptimeType { + left := cond.left as ast.TypeNode + checked_type := c.unwrap_generic(left.typ) + return c.table.is_comptime_type(checked_type, cond.right) } else if cond.left in [ast.SelectorExpr, ast.TypeNode] { // `$if method.@type is string` c.expr(cond.left) diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 9bc3dc5e68..362326718a 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -52,32 +52,39 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { mut comptime_field_name := '' if mut branch.cond is ast.InfixExpr { if branch.cond.op == .key_is { - if branch.cond.right !is ast.TypeNode { + if branch.cond.right !is ast.TypeNode && branch.cond.right !is ast.ComptimeType { c.error('invalid `\$if` condition: expected a type', branch.cond.right.pos()) return 0 } - got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ) - sym := c.table.sym(got_type) - if sym.kind == .placeholder || got_type.has_flag(.generic) { - c.error('unknown type `$sym.name`', branch.cond.right.pos()) - } left := branch.cond.left - if left is ast.SelectorExpr { - comptime_field_name = left.expr.str() - c.comptime_fields_type[comptime_field_name] = got_type + if branch.cond.right is ast.ComptimeType && left is ast.TypeNode { is_comptime_type_is_expr = true - } else if branch.cond.right is ast.TypeNode && left is ast.TypeNode - && sym.kind == .interface_ { - is_comptime_type_is_expr = true - // is interface checked_type := c.unwrap_generic(left.typ) - should_skip = !c.table.does_type_implement_interface(checked_type, - got_type) - } else if left is ast.TypeNode { - is_comptime_type_is_expr = true - left_type := c.unwrap_generic(left.typ) - if left_type != got_type { - should_skip = true + should_skip = !c.table.is_comptime_type(checked_type, branch.cond.right as ast.ComptimeType) + } else { + got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ) + sym := c.table.sym(got_type) + if sym.kind == .placeholder || got_type.has_flag(.generic) { + c.error('unknown type `$sym.name`', branch.cond.right.pos()) + } + + if left is ast.SelectorExpr { + comptime_field_name = left.expr.str() + c.comptime_fields_type[comptime_field_name] = got_type + is_comptime_type_is_expr = true + } else if branch.cond.right is ast.TypeNode && left is ast.TypeNode + && sym.kind == .interface_ { + is_comptime_type_is_expr = true + // is interface + checked_type := c.unwrap_generic(left.typ) + should_skip = !c.table.does_type_implement_interface(checked_type, + got_type) + } else if left is ast.TypeNode { + is_comptime_type_is_expr = true + left_type := c.unwrap_generic(left.typ) + if left_type != got_type { + should_skip = true + } } } } diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index c88dff1a8b..c9ccd98980 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -660,6 +660,18 @@ pub fn (mut f Fmt) expr(node ast.Expr) { ast.UnsafeExpr { f.unsafe_expr(node) } + ast.ComptimeType { + match node.kind { + .array { f.write('\$Array') } + .struct_ { f.write('\$Struct') } + .iface { f.write('\$Interface') } + .map_ { f.write('\$Map') } + .int { f.write('\$Int') } + .float { f.write('\$Float') } + .sum_type { f.write('\$Sumtype') } + .enum_ { f.write('\$Enum') } + } + } } } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 6c9585190e..a2408ab251 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2740,6 +2740,9 @@ fn (mut g Gen) expr(node ast.Expr) { } // NB: please keep the type names in the match here in alphabetical order: match mut node { + ast.ComptimeType { + g.error('g.expr(): Unhandled ComptimeType', node.pos) + } ast.EmptyExpr { g.error('g.expr(): unhandled EmptyExpr', token.Pos{}) } diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 0985b31949..8e596bbf47 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -337,6 +337,25 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) bool { .key_is, .not_is { left := cond.left mut name := '' + if left is ast.TypeNode && cond.right is ast.ComptimeType { + checked_type := g.unwrap_generic(left.typ) + is_true := g.table.is_comptime_type(checked_type, cond.right) + if cond.op == .key_is { + if is_true { + g.write('1') + } else { + g.write('0') + } + return is_true + } else { + if is_true { + g.write('0') + } else { + g.write('1') + } + return !is_true + } + } mut exp_type := ast.Type(0) got_type := (cond.right as ast.TypeNode).typ // Handle `$if x is Interface {` diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 83ab59b46a..9805cebf1e 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -856,6 +856,9 @@ fn (mut g JsGen) stmt(node ast.Stmt) { fn (mut g JsGen) expr(node ast.Expr) { // NB: please keep the type names in the match here in alphabetical order: match mut node { + ast.ComptimeType { + verror('not yet implemented') + } ast.EmptyExpr {} ast.AnonFn { g.gen_anon_fn(mut node) diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 4d399aae09..26c659aedf 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -221,6 +221,7 @@ fn (mut w Walker) expr(node ast.Expr) { // TODO make sure this doesn't happen // panic('Walker: EmptyExpr') } + ast.ComptimeType {} ast.AnonFn { w.fn_decl(mut node.decl) } diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 7d0ea51ecb..02b2a2f0ac 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -10,8 +10,50 @@ import v.token const ( supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig'] + comptime_types = ['Map', 'Array', 'Int', 'Float', 'Struct', 'Interface', 'Enum', + 'Sumtype'] ) +pub fn (mut p Parser) parse_comptime_type() ast.ComptimeType { + mut node := ast.ComptimeType{ast.ComptimeTypeKind.map_, p.tok.pos()} + + p.check(.dollar) + name := p.check_name() + if name !in parser.comptime_types { + p.error('unsupported compile-time type `$name`: only $parser.comptime_types are supported') + } + mut cty := ast.ComptimeTypeKind.map_ + match name { + 'Map' { + cty = .map_ + } + 'Struct' { + cty = .struct_ + } + 'Interface' { + cty = .iface + } + 'Int' { + cty = .int + } + 'Float' { + cty = .float + } + 'Array' { + cty = .array + } + 'Enum' { + cty = .enum_ + } + 'Sumtype' { + cty = .sum_type + } + else {} + } + node = ast.ComptimeType{cty, node.pos} + return node +} + // // #include, #flag, #v fn (mut p Parser) hash() ast.HashStmt { pos := p.tok.pos() diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index c9bd4d5d29..c1b14e79f7 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -78,7 +78,11 @@ pub fn (mut p Parser) check_expr(precedence int) ?ast.Expr { .dollar { match p.peek_tok.kind { .name { - node = p.comptime_call() + if p.peek_tok.lit in comptime_types { + node = p.parse_comptime_type() + } else { + node = p.comptime_call() + } p.is_stmt_ident = is_stmt_ident } .key_if { @@ -490,6 +494,7 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { if is_key_in { p.inside_in_array = true } + right = p.expr(precedence) if is_key_in { p.inside_in_array = false diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 853041390f..3fcfe4815a 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2105,6 +2105,11 @@ pub fn (mut p Parser) name_expr() ast.Expr { prev_tok_kind := p.prev_tok.kind mut node := ast.empty_expr() if p.expecting_type { + if p.tok.kind == .dollar { + node = p.parse_comptime_type() + p.expecting_type = false + return node + } p.expecting_type = false // get type position before moving to next type_pos := p.tok.pos() diff --git a/vlib/v/tests/comptime_kinds_test.v b/vlib/v/tests/comptime_kinds_test.v new file mode 100644 index 0000000000..a581add2f8 --- /dev/null +++ b/vlib/v/tests/comptime_kinds_test.v @@ -0,0 +1,84 @@ +fn assert_map() { + $if T is $Map { + assert true + } $else { + assert false + } +} + +fn assert_array() { + $if T is $Array { + assert true + } $else { + assert false + } +} + +fn assert_struct() { + $if T is $Struct { + assert true + } $else { + assert false + } +} + +fn assert_not_struct() { + $if T is $Struct { + assert false + } $else { + assert true + } +} + +fn assert_not_map() { + $if T is $Map { + assert false + } $else { + assert true + } +} + +fn assert_not_array() { + $if T is $Array { + assert false + } $else { + assert true + } +} + +struct Abc {} + +struct Bc {} + +struct Cd {} + +fn test_kind_map() { + assert_map() + assert_map() + assert_map() + + assert_not_map() + assert_not_map() + assert_not_map<[]int>() +} + +fn test_kind_array() { + assert_array<[]int>() + assert_array<[]f32>() + assert_array<[]string>() + + assert_not_array() + assert_not_array() + assert_not_array() + assert_not_array() +} + +fn test_kind_struct() { + assert_struct() + assert_struct() + assert_struct() + + assert_not_struct() + assert_not_struct<[]int>() + assert_not_struct() +}