From 8b5f74e6a7b52c1100cb52ef2832fd0c090fb407 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 25 Apr 2026 14:31:38 +0300 Subject: [PATCH] comptime: add type metadata accessors .pointee_type, .payload_type, .variant_types, plus $zero(TypeExpr) and $new(TypeExpr) (fixes #26980) --- doc/docs.md | 22 ++++ vlib/v/ast/ast.v | 5 + vlib/v/checker/checker.v | 17 ++- vlib/v/checker/comptime.v | 105 +++++++++++++++++- vlib/v/checker/containers.v | 2 +- vlib/v/checker/struct.v | 87 ++++++++++++--- vlib/v/fmt/fmt.v | 5 + vlib/v/gen/c/cgen.v | 46 +++++++- vlib/v/gen/c/comptime.v | 28 +++++ vlib/v/markused/walker.v | 3 + vlib/v/parser/comptime.v | 62 ++++++++++- vlib/v/parser/containers.v | 4 +- vlib/v/parser/expr.v | 2 +- .../comptime_accessor_helper/helper.v | 5 + .../comptime_type_accessors_zero_new_test.v | 93 ++++++++++++++++ vlib/v/type_resolver/comptime_resolver.v | 64 +++++++++-- 16 files changed, 512 insertions(+), 38 deletions(-) create mode 100644 vlib/v/tests/comptime/comptime_accessor_helper/helper.v create mode 100644 vlib/v/tests/comptime/comptime_type_accessors_zero_new_test.v diff --git a/doc/docs.md b/doc/docs.md index 72b310059..47e46a4a6 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -6644,6 +6644,28 @@ println('This program, was compiled at ${time.unix(@BUILD_TIMESTAMP.i64()).forma Having built-in JSON support is nice, but V also allows you to create efficient serializers for any data format. V has compile time `if` and `for` constructs: +####

Type metadata

+ +Comptime type expressions expose metadata through fields like `.idx`, `.typ`, +`.unaliased_typ`, `.indirections`, `.key_type`, `.value_type`, `.element_type`, +`.pointee_type`, `.payload_type`, and `.variant_types`. + +```v +fn zero_payload[T](x ?T) T { + return $zero(typeof(x).payload_type) +} + +fn main() { + value := ?int(123) + assert zero_payload(value) == 0 + assert typeof[map[string]int]().key_type == typeof[string]().idx + assert typeof[?&int]().payload_type == typeof[&int]().idx +} +``` + +`$zero(Type)` returns the zero value for a comptime type expression. +`$new(Type)` returns a pointer to a new zero value. + ####

.fields

You can iterate over struct fields using `.fields`, it also works with generic types diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 2c5b7e93d..89886b6a2 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -2217,6 +2217,8 @@ pub enum ComptimeCallKind { method pkgconfig embed_file + zero + new compile_warn compile_error } @@ -2284,6 +2286,9 @@ pub fn (cc ComptimeCall) expr_str() string { } } else if cc.kind == .pkgconfig { str = "\$${cc.method_name}('${cc.args_var}')" + } else if cc.kind in [.zero, .new] { + arg := cc.args[0] or { return str } + str = '\$${cc.method_name}(${arg})' } return str } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index bf103641a..251246dd2 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2968,8 +2968,10 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { if node.field_name == 'name' { return ast.string_type } else if node.field_name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', - 'element_type'] { + 'element_type', 'pointee_type', 'payload_type'] { return ast.int_type + } else if node.field_name == 'variant_types' { + return ast.new_type(c.table.find_or_register_array(ast.int_type)) } else if node.field_name == 'indirections' { return ast.int_type } @@ -2978,6 +2980,19 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { } } } + if is_array_init_type_expr_field(node.field_name) && c.is_comptime_type_expr(node.expr) { + mut type_expr := node.expr + base_type := c.comptime_call_type_expr_type(mut type_expr) + node.expr = type_expr + resolved := c.type_resolver.typeof_field_type(base_type, node.field_name) + if resolved != ast.no_type { + node.name_type = base_type + if node.field_name == 'variant_types' { + return ast.new_type(c.table.find_or_register_array(ast.int_type)) + } + return ast.int_type + } + } // evaluates comptime field. (from T.fields) if c.comptime.check_comptime_is_field_selector(node) { if c.comptime.check_comptime_is_field_selector_bool(node) { diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 02af16e6b..c34d22b20 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -87,7 +87,7 @@ fn (mut c Checker) eval_comptime_type_meta_value(typ ast.Type, field_name string 'indirections' { return i64(base_type.nr_muls()) } - 'key_type', 'value_type', 'element_type' { + 'key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type' { resolved_type := c.type_resolver.typeof_field_type(base_type, field_name) if resolved_type != ast.no_type { return i64(int(c.unwrap_generic(resolved_type))) @@ -99,6 +99,85 @@ fn (mut c Checker) eval_comptime_type_meta_value(typ ast.Type, field_name string return none } +fn (c &Checker) is_generic_type_expr_ident(name string) bool { + return util.is_generic_type_name(name) && c.table.cur_fn != unsafe { nil } + && name in c.table.cur_fn.generic_names +} + +fn (c &Checker) is_comptime_type_expr(expr ast.Expr) bool { + return match expr { + ast.ParExpr { + c.is_comptime_type_expr(expr.expr) + } + ast.TypeNode { + true + } + ast.TypeOf { + true + } + ast.Ident { + c.is_generic_type_expr_ident(expr.name) + } + ast.SelectorExpr { + is_array_init_type_expr_field(expr.field_name) && c.is_comptime_type_expr(expr.expr) + } + else { + false + } + } +} + +fn (mut c Checker) comptime_call_type_expr_type(mut expr ast.Expr) ast.Type { + match mut expr { + ast.ParExpr { + return c.comptime_call_type_expr_type(mut expr.expr) + } + ast.TypeNode { + return c.recheck_concrete_type(expr.typ) + } + ast.TypeOf { + if expr.is_type { + return c.recheck_concrete_type(expr.typ) + } + if expr.typ == 0 || expr.typ == ast.void_type || expr.typ == ast.no_type { + expr.typ = c.expr(mut expr.expr) + } + resolved_type := c.recheck_concrete_type(expr.typ) + if resolved_type != 0 && resolved_type != ast.void_type && resolved_type != ast.no_type { + return resolved_type + } + return c.recheck_concrete_type(c.type_resolver.typeof_type(expr.expr, expr.typ)) + } + ast.ArrayInit { + if expr.elem_type_expr !is ast.EmptyExpr { + c.resolve_array_init_elem_type_expr(mut expr) + return expr.typ + } + return c.expr(mut expr) + } + ast.SelectorExpr { + if is_array_init_type_expr_field(expr.field_name) { + base_type := c.comptime_call_type_expr_type(mut expr.expr) + resolved := c.type_resolver.typeof_field_type(base_type, expr.field_name) + if resolved != ast.no_type { + return c.recheck_concrete_type(resolved) + } + } + c.expr(mut expr) + return c.recheck_concrete_type(c.get_expr_type(expr)) + } + ast.Ident { + if c.is_generic_type_expr_ident(expr.name) { + return c.table.find_type(expr.name).set_flag(.generic) + } + return c.recheck_concrete_type(c.get_expr_type(expr)) + } + else { + return c.recheck_concrete_type(c.expr(mut expr)) + } + } +} + fn (mut c Checker) eval_comptime_type_selector_value(expr ast.SelectorExpr) ?ast.ComptTimeConstValue { if expr.expr is ast.Ident && c.table.cur_fn != unsafe { nil } { idx := c.table.cur_fn.generic_names.index(expr.expr.name) @@ -317,6 +396,22 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { } return node.result_type } + if node.kind in [.zero, .new] { + if node.args.len != 1 { + c.error('`\$${node.method_name}()` expects 1 type argument', node.pos) + return ast.void_type + } + mut type_expr := node.args[0].expr + resolved_type := c.comptime_call_type_expr_type(mut type_expr) + node.args[0].expr = type_expr + if resolved_type == ast.void_type || resolved_type == ast.no_type { + c.error('`\$${node.method_name}()` expects a valid type expression', node.args[0].pos) + return ast.void_type + } + node.args[0].typ = resolved_type + node.result_type = if node.kind == .new { resolved_type.ref() } else { resolved_type } + return node.result_type + } if node.kind == .embed_file { if node.args.len == 1 { embed_arg := node.args[0] @@ -1410,17 +1505,23 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type { } ast.SelectorExpr { if c.comptime.inside_comptime_for - && cond.field_name in ['typ', 'unaliased_typ', 'indirections'] + && cond.field_name in ['typ', 'unaliased_typ', 'indirections', 'pointee_type', 'payload_type', 'variant_types'] && cond.expr is ast.Ident && (cond.expr.name == c.comptime.comptime_for_variant_var || cond.expr.name == c.comptime.comptime_for_method_param_var || cond.expr.name == c.comptime.comptime_for_field_var) { typ := c.type_resolver.get_type_from_comptime_var(cond.expr as ast.Ident) if cond.field_name == 'unaliased_typ' { return c.table.unaliased_type(typ) + } else if cond.field_name in ['pointee_type', 'payload_type', 'variant_types'] { + return c.type_resolver.typeof_field_type(typ, cond.field_name) } // for `indirections` we also return the `typ` return typ } + if cond.name_type != 0 + && cond.field_name in ['key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type', 'variant_types'] { + return c.type_resolver.typeof_field_type(cond.name_type, cond.field_name) + } if cond.gkind_field in [.typ, .indirections, .unaliased_typ] { if cond.expr is ast.Ident { generic_name := cond.expr.name diff --git a/vlib/v/checker/containers.v b/vlib/v/checker/containers.v index 7f5b1ad3b..2a4f7b7cd 100644 --- a/vlib/v/checker/containers.v +++ b/vlib/v/checker/containers.v @@ -54,7 +54,7 @@ fn is_inferred_fixed_array_size_expr(expr ast.Expr) bool { fn is_array_init_type_expr_field(name string) bool { return name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type', - 'indirections'] + 'pointee_type', 'payload_type', 'variant_types', 'indirections'] } fn (mut c Checker) fixed_array_contains_inferred_size(typ ast.Type) bool { diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index 4f6d636eb..3120f4ad9 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -542,24 +542,50 @@ fn minify_sort_fn(a &ast.StructField, b &ast.StructField) int { } } -fn (mut c Checker) struct_init_selector_type_expr(expr ast.SelectorExpr) ast.Type { - if expr.expr is ast.TypeOf { - return c.type_resolver.typeof_field_type(c.type_resolver.typeof_type(expr.expr.expr, - expr.name_type), expr.field_name) +fn (mut c Checker) struct_init_selector_type_expr(mut expr ast.SelectorExpr) ast.Type { + if !is_array_init_type_expr_field(expr.field_name) { + return ast.void_type + } + base_type := c.struct_init_type_expr(mut expr.expr) + if base_type == ast.void_type { + return ast.void_type } - return ast.void_type + return c.type_resolver.typeof_field_type(base_type, expr.field_name) } -fn (mut c Checker) struct_init_type_expr(expr ast.Expr) ast.Type { - return match expr { +fn (mut c Checker) struct_init_type_expr(mut expr ast.Expr) ast.Type { + return match mut expr { ast.TypeNode { expr.typ } ast.ParExpr { - c.struct_init_type_expr(expr.expr) + c.struct_init_type_expr(mut expr.expr) + } + ast.TypeOf { + if expr.is_type { + c.recheck_concrete_type(expr.typ) + } else { + if expr.typ == 0 || expr.typ == ast.void_type || expr.typ == ast.no_type { + expr.typ = c.expr(mut expr.expr) + } + resolved_type := c.recheck_concrete_type(expr.typ) + if resolved_type != 0 && resolved_type != ast.void_type + && resolved_type != ast.no_type { + resolved_type + } else { + c.recheck_concrete_type(c.type_resolver.typeof_type(expr.expr, expr.typ)) + } + } + } + ast.Ident { + if c.is_generic_type_expr_ident(expr.name) { + c.table.find_type(expr.name).set_flag(.generic) + } else { + c.get_expr_type(expr) + } } ast.SelectorExpr { - c.struct_init_selector_type_expr(expr) + c.struct_init_selector_type_expr(mut expr) } else { ast.void_type @@ -567,6 +593,27 @@ fn (mut c Checker) struct_init_type_expr(expr ast.Expr) ast.Type { } } +fn (c &Checker) struct_init_uses_comptime_type_accessor(expr ast.Expr) bool { + return match expr { + ast.ParExpr { + c.struct_init_uses_comptime_type_accessor(expr.expr) + } + ast.SelectorExpr { + mut is_base_type_expr := expr.expr is ast.TypeOf + || c.struct_init_uses_comptime_type_accessor(expr.expr) + if expr.expr is ast.Ident { + is_base_type_expr = is_base_type_expr + || c.is_generic_type_expr_ident(expr.expr.name) + } + expr.field_name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type'] + && is_base_type_expr + } + else { + false + } + } +} + fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_init bool, mut inited_fields []string) ast.Type { util.timing_start(@METHOD) old_expected_type := c.expected_type @@ -579,14 +626,20 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini && c.expected_type != ast.void_type && c.expected_type.has_flag(.generic) && short_syntax_expected_type_sym.kind == .any && !short_syntax_expected_type_sym.is_builtin() - if node.typ == ast.void_type && !node.is_short_syntax && node.typ_expr !is ast.EmptyExpr { - c.expr(mut node.typ_expr) - node.typ = c.struct_init_type_expr(node.typ_expr) - if node.typ == ast.void_type { + is_comptime_type_struct_init := !node.is_short_syntax && node.typ_expr !is ast.EmptyExpr + && c.struct_init_uses_comptime_type_accessor(node.typ_expr) + should_resolve_typ_expr := node.typ == ast.void_type + || (is_comptime_type_struct_init && c.has_active_generic_recheck_context()) + if should_resolve_typ_expr && !node.is_short_syntax && node.typ_expr !is ast.EmptyExpr { + if !is_comptime_type_struct_init { + c.expr(mut node.typ_expr) + } + node.typ = c.struct_init_type_expr(mut node.typ_expr) + if node.typ == ast.void_type || node.typ == ast.no_type { c.error('cannot use `${node.typ_expr}` as a struct init type', node.typ_expr.pos()) return ast.void_type } - node.unresolved = node.typ.has_flag(.generic) + node.unresolved = !is_comptime_type_struct_init && node.typ.has_flag(.generic) } source_typ := if node.is_short_syntax && c.expected_type != ast.void_type && !short_syntax_infers_anon_from_generic_param { @@ -769,6 +822,8 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini type_sym := c.table.sym(concrete_node_typ) is_generic_zero_struct_init := original_node_typ.has_flag(.generic) && node.init_fields.len == 0 && !node.has_update_expr + is_comptime_type_zero_struct_init := node.init_fields.len == 0 && !node.has_update_expr + && is_comptime_type_struct_init if is_generic_zero_struct_init { // Don't early-return for single-letter types (like F{}) in non-generic functions — // these are unknown structs that should be caught by ensure_type_exists below. @@ -778,7 +833,7 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini return concrete_node_typ } } - if !is_field_zero_struct_init { + if !is_field_zero_struct_init && !is_comptime_type_zero_struct_init { type_exists := c.ensure_type_exists(node.typ, node.pos) if !type_exists && node.typ.idx() > 0 && c.table.sym(node.typ).kind == .placeholder { return ast.void_type @@ -830,7 +885,7 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini if !node.has_update_expr && !type_sym.is_pub && type_sym.kind != .placeholder && type_sym.language != .c && (type_sym.mod != c.mod && !(is_generic_init && type_sym.mod != 'builtin')) - && !is_field_zero_struct_init { + && !is_field_zero_struct_init && !is_comptime_type_zero_struct_init { c.error('type `${type_sym.name}` is private', node.pos) } if type_sym.info is ast.Struct && type_sym.mod != c.mod && !is_field_zero_struct_init { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 7780775f2..13496b561 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -2414,6 +2414,11 @@ pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) { f.write('\$res()') } } + node.kind in [.zero, .new] { + f.write('\$${node.method_name}(') + f.expr(node.args[0].expr) + f.write(')') + } else { inner_args := if node.args_var != '' { node.args_var diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 9382f21e3..1f00a8b1f 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -6414,13 +6414,53 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { g.write(int(g.unwrap_generic(name_type)).str()) } return - } else if node.field_name in ['key_type', 'value_type', 'element_type'] { + } else if node.field_name in ['key_type', 'value_type', 'element_type', + 'pointee_type', 'payload_type'] { // `T.`, `typeof(expr).` mut name_type := node.name_type - name_type = g.type_resolver.typeof_field_type(g.type_resolver.typeof_type(node.expr, g.resolve_typeof_expr_type(node.expr, - name_type)), node.field_name) + if node.expr is ast.TypeOf { + if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { + resolved := g.resolve_typeof_in_generic(node.expr) + if resolved != 0 { + name_type = resolved + } else { + name_type = g.resolved_typeof_name_type(node.expr, name_type) + } + } else { + name_type = g.resolved_typeof_name_type(node.expr, name_type) + } + } else if name_type == 0 { + name_type = g.type_resolver.typeof_type(node.expr, g.resolve_typeof_expr_type(node.expr, + name_type)) + } + name_type = g.type_resolver.typeof_field_type(name_type, node.field_name) g.write(int(name_type).str()) return + } else if node.field_name == 'variant_types' { + mut name_type := node.name_type + if node.expr is ast.TypeOf { + if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { + resolved := g.resolve_typeof_in_generic(node.expr) + if resolved != 0 { + name_type = resolved + } else { + name_type = g.type_resolver.typeof_type(node.expr.expr, g.resolve_typeof_expr_type(node.expr.expr, + name_type)) + } + } else { + name_type = g.type_resolver.typeof_type(node.expr.expr, g.resolve_typeof_expr_type(node.expr.expr, + name_type)) + } + } else { + name_type = g.unwrap_generic(name_type) + } + sym := g.table.final_sym(name_type) + if sym.info is ast.SumType { + g.write(g.gen_type_array(sym.info.variants)) + } else { + g.write(g.gen_type_array([]ast.Type{})) + } + return } else if node.field_name == 'indirections' { mut name_type := node.name_type if node.expr is ast.TypeOf { diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index a49dfe333..fcb21235a 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -53,6 +53,16 @@ fn (mut g Gen) comptime_call_expands_string_args(m &ast.Fn, node ast.ComptimeCal return !g.is_string_array_type(m.params[node.args.len].typ) } +fn (mut g Gen) comptime_zero_value(typ ast.Type) string { + resolved_type := g.unwrap_generic(g.recheck_concrete_type(typ)) + styp := g.styp(resolved_type) + mut default_value := g.type_default(resolved_type) + if default_value.len > 0 && default_value[0] == `{` { + default_value = '(${styp})${default_value}' + } + return default_value +} + fn (mut g Gen) comptime_selector(node ast.ComptimeSelector) { left_type := g.resolved_expr_type(node.left, node.left_type) if node.is_method && g.comptime.comptime_for_method != unsafe { nil } { @@ -162,6 +172,20 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { } return } + if node.kind in [.zero, .new] { + resolved_type := if node.kind == .new { + g.unwrap_generic(g.recheck_concrete_type(node.result_type.deref())) + } else { + g.unwrap_generic(g.recheck_concrete_type(node.result_type)) + } + default_value := g.comptime_zero_value(resolved_type) + if node.kind == .new { + g.write('HEAP(${g.styp(resolved_type)}, (${default_value}))') + } else { + g.write(default_value) + } + return + } if node.kind == .res { if node.args_var != '' { g.write('${g.defer_return_tmp_var}.arg${node.args_var}') @@ -823,6 +847,10 @@ fn (mut g Gen) get_expr_type(cond ast.Expr) ast.Type { return g.unwrap_generic(cond.typ) } ast.SelectorExpr { + if cond.name_type != 0 + && cond.field_name in ['key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type', 'variant_types'] { + return g.type_resolver.typeof_field_type(cond.name_type, cond.field_name) + } if cond.gkind_field == .typ { return g.unwrap_generic(cond.name_type) } else if cond.gkind_field == .unaliased_typ { diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index f91717304..9e56d3e7f 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -1003,6 +1003,9 @@ fn (mut w Walker) expr(node_ ast.Expr) { if node.kind == .embed_file { w.features.used_maps++ } + if node.kind == .new { + w.uses_memdup = true + } } ast.DumpExpr { w.expr(node.expr) diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 07924af27..32b969026 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -11,7 +11,7 @@ import v.util import v.vmod const supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig', 'compile_error', - 'compile_warn', 'd', 'res'] + 'compile_warn', 'd', 'res', 'zero', 'new'] const supported_comptime_for_kinds = ['methods', 'fields', 'values', 'variants', 'attributes', 'params'] const comptime_types = ['map', 'array', 'array_dynamic', 'array_fixed', 'int', 'float', 'struct', @@ -170,7 +170,7 @@ fn (mut p Parser) hash() ast.HashStmt { } } -const error_msg = 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()`, `\$veb.html()`, `\$compile_error()`, `\$compile_warn()`, `\$d()` and `\$res()` comptime functions are supported right now' +const error_msg = 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()`, `\$veb.html()`, `\$compile_error()`, `\$compile_warn()`, `\$d()`, `\$res()`, `\$zero()` and `\$new()` comptime functions are supported right now' fn (p &Parser) resolve_tmpl_path_expr(expr ast.Expr) ?string { return p.resolve_tmpl_path_expr_with_depth(expr, 0) @@ -299,6 +299,49 @@ fn (mut p Parser) resolve_tmpl_pseudo_variables(path string, pos token.Pos) ?str return resolved } +fn (p &Parser) is_comptime_type_selector_at(offset int) bool { + if p.peek_token(offset).kind == .key_typeof { + return true + } + if p.peek_token(offset).kind != .name { + return false + } + mut n := offset + 1 + for p.peek_token(n).kind == .dot && p.peek_token(n + 1).kind == .name { + if is_array_init_type_expr_field(p.peek_token(n + 1).lit) { + return true + } + n += 2 + } + return false +} + +fn (p &Parser) is_comptime_type_expr_arg() bool { + if p.tok.kind in [.key_typeof, .dollar] { + return true + } + if p.tok.kind == .lsbr && p.peek_tok.kind == .rsbr { + return p.is_comptime_type_selector_at(2) + } + return p.is_comptime_type_selector_at(0) +} + +fn (mut p Parser) comptime_call_type_arg() ast.Expr { + arg_pos := p.tok.pos() + if p.is_comptime_type_expr_arg() { + old_inside_array_init_type_expr := p.inside_array_init_type_expr + p.inside_array_init_type_expr = true + expr := p.expr(0) + p.inside_array_init_type_expr = old_inside_array_init_type_expr + return expr + } + typ := p.parse_type() + return ast.TypeNode{ + typ: typ + pos: arg_pos.extend(p.prev_tok.pos()) + } +} + fn (mut p Parser) comptime_call() ast.ComptimeCall { err_node := ast.ComptimeCall{ scope: unsafe { nil } @@ -413,6 +456,21 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall { args: args pos: start_pos.extend(p.prev_tok.pos()) } + } else if method_name in ['zero', 'new'] { + arg_expr := p.comptime_call_type_arg() + p.check(.rpar) + return ast.ComptimeCall{ + scope: unsafe { nil } + method_name: method_name + kind: if method_name == 'zero' { .zero } else { .new } + args: [ + ast.CallArg{ + expr: arg_expr + pos: arg_pos + }, + ] + pos: start_pos.extend(p.prev_tok.pos()) + } } has_string_arg := p.tok.kind == .string mut literal_string_param := if is_html && !has_string_arg { '' } else { p.tok.lit } diff --git a/vlib/v/parser/containers.v b/vlib/v/parser/containers.v index c50933f2c..eb045c20c 100644 --- a/vlib/v/parser/containers.v +++ b/vlib/v/parser/containers.v @@ -12,7 +12,7 @@ fn is_inferred_fixed_array_size_expr(expr ast.Expr) bool { fn is_array_init_type_expr_field(name string) bool { return name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type', - 'indirections'] + 'pointee_type', 'payload_type', 'variant_types', 'indirections'] } fn (mut p Parser) parse_fixed_array_literal_elem_type() ast.Type { @@ -305,7 +305,7 @@ fn (mut p Parser) array_init(is_option bool, alias_array_type ast.Type) ast.Arra } } } - if exprs.len == 0 && p.tok.kind != .lcbr && has_type { + if exprs.len == 0 && p.tok.kind != .lcbr && has_type && !p.inside_array_init_type_expr { if !p.pref.is_fmt { modifier := if is_option { '?' } else { '' } p.warn_with_pos('use `x := ${modifier}[]Type{}` instead of `x := ${modifier}[]Type`', diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index f8993d424..c41febc02 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -676,7 +676,7 @@ fn (p &Parser) can_use_expr_as_struct_init_type(expr ast.Expr) bool { } ast.SelectorExpr { expr.expr is ast.TypeOf - && expr.field_name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type'] + && expr.field_name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type'] } else { false diff --git a/vlib/v/tests/comptime/comptime_accessor_helper/helper.v b/vlib/v/tests/comptime/comptime_accessor_helper/helper.v new file mode 100644 index 000000000..f6b1f7ec3 --- /dev/null +++ b/vlib/v/tests/comptime/comptime_accessor_helper/helper.v @@ -0,0 +1,5 @@ +module comptime_accessor_helper + +pub fn zero_option_payload[T](x ?T) { + _ := typeof(x).payload_type{} +} diff --git a/vlib/v/tests/comptime/comptime_type_accessors_zero_new_test.v b/vlib/v/tests/comptime/comptime_type_accessors_zero_new_test.v new file mode 100644 index 000000000..674087fb2 --- /dev/null +++ b/vlib/v/tests/comptime/comptime_type_accessors_zero_new_test.v @@ -0,0 +1,93 @@ +import comptime_accessor_helper + +struct PrivatePayload { + n int +} + +struct Box { + n int +} + +type ExampleSum = int | string | Box + +fn maybe_private_payload() ?PrivatePayload { + return PrivatePayload{ + n: 7 + } +} + +fn result_string() !string { + return 'abc' +} + +fn maybe_int_ptr() ?&int { + return none +} + +fn result_int_ptr() !&int { + return error('no pointer') +} + +fn payload_idx[T](x T) int { + return typeof(x).payload_type +} + +fn option_payload_idx[T](x ?T) int { + return typeof(x).payload_type +} + +fn pointee_idx[T](x T) int { + return typeof(x).pointee_type +} + +fn variant_type_idxs[T]() []int { + return T.variant_types +} + +fn test_payload_type_accessor() { + opt := maybe_private_payload() + assert typeof(opt).payload_type == typeof[PrivatePayload]().idx + assert option_payload_idx(opt) == typeof[PrivatePayload]().idx + assert typeof(result_string()).payload_type == typeof[string]().idx +} + +fn test_pointee_type_accessor() { + value := 123 + ptr := &value + assert typeof(ptr).pointee_type == typeof[int]().idx + assert pointee_idx(ptr) == typeof[int]().idx + assert typeof(maybe_int_ptr()).pointee_type == typeof[int]().idx + assert typeof(result_int_ptr()).pointee_type == typeof[int]().idx + assert typeof(maybe_int_ptr()).payload_type == typeof[&int]().idx + assert typeof(maybe_int_ptr()).payload_type.pointee_type == typeof[int]().idx +} + +fn test_variant_types_accessor() { + idxs := variant_type_idxs[ExampleSum]() + assert idxs == [typeof[int]().idx, typeof[string]().idx, typeof[Box]().idx] + assert typeof[ExampleSum]().variant_types == idxs +} + +fn test_zero_and_new() { + opt := maybe_private_payload() + zero_int := $zero(int) + zero_payload := $zero(typeof(opt).payload_type) + zero_pointee := $zero(typeof(maybe_int_ptr()).payload_type.pointee_type) + zero_array := $zero([]typeof(opt).payload_type{}) + new_int := $new(int) + new_payload := $new(typeof(opt).payload_type) + new_pointee := $new(typeof(result_int_ptr()).payload_type.pointee_type) + + assert zero_int == 0 + assert zero_payload == PrivatePayload{} + assert zero_pointee == 0 + assert zero_array.len == 0 + assert *new_int == 0 + assert *new_payload == PrivatePayload{} + assert *new_pointee == 0 +} + +fn test_inline_type_accessor_zero_init_can_cross_module_privacy() { + opt := maybe_private_payload() + comptime_accessor_helper.zero_option_payload(opt) +} diff --git a/vlib/v/type_resolver/comptime_resolver.v b/vlib/v/type_resolver/comptime_resolver.v index 6b658c4b2..ce893f285 100644 --- a/vlib/v/type_resolver/comptime_resolver.v +++ b/vlib/v/type_resolver/comptime_resolver.v @@ -116,12 +116,11 @@ pub fn (mut t TypeResolver) typeof_type(node ast.Expr, default_type ast.Type) as return f.typ } } else if node is ast.SelectorExpr && node.name_type != 0 { - if node.field_name in ['value_type', 'element_type'] { - return t.table.value_type(t.resolver.unwrap_generic(node.name_type)) - } else if node.field_name == 'key_type' { - sym := t.table.sym(t.resolver.unwrap_generic(node.name_type)) - if sym.info is ast.Map { - return t.resolver.unwrap_generic(sym.info.key_type) + if node.field_name in ['key_type', 'value_type', 'element_type', 'pointee_type', + 'payload_type', 'variant_types'] { + resolved := t.typeof_field_type(node.name_type, node.field_name) + if resolved != ast.no_type { + return resolved } } } @@ -130,28 +129,73 @@ pub fn (mut t TypeResolver) typeof_type(node ast.Expr, default_type ast.Type) as // typeof_field_type resolves the T. and typeof[T](). type pub fn (mut t TypeResolver) typeof_field_type(typ ast.Type, field_name string) ast.Type { + unwrapped := t.resolver.unwrap_generic(typ) match field_name { 'name' { return ast.string_type } 'idx', 'typ' { - return t.resolver.unwrap_generic(typ) + return unwrapped } 'unaliased_typ' { - return t.table.unaliased_type(t.resolver.unwrap_generic(typ)) + return t.table.unaliased_type(unwrapped) } 'indirections' { return ast.int_type } 'key_type' { - sym := t.table.final_sym(t.resolver.unwrap_generic(typ)) + sym := t.table.final_sym(unwrapped) if sym.info is ast.Map { return t.resolver.unwrap_generic(sym.info.key_type) } + if unwrapped.has_flag(.generic) || t.table.generic_type_names(unwrapped).len > 0 { + return unwrapped + } return ast.no_type } 'value_type', 'element_type' { - return t.table.value_type(t.resolver.unwrap_generic(typ)) + value_type := t.table.value_type(unwrapped) + if value_type != ast.void_type { + return t.resolver.unwrap_generic(value_type) + } + if unwrapped.has_flag(.generic) || t.table.generic_type_names(unwrapped).len > 0 { + return unwrapped + } + return ast.no_type + } + 'pointee_type' { + if unwrapped.has_flag(.option) || unwrapped.has_flag(.result) { + inner := unwrapped.clear_option_and_result() + if inner.is_ptr() { + return inner.deref() + } + } + if unwrapped.is_ptr() { + return unwrapped.deref() + } + if unwrapped.has_flag(.generic) || t.table.generic_type_names(unwrapped).len > 0 { + return unwrapped + } + return ast.no_type + } + 'payload_type' { + if unwrapped.has_flag(.option) || unwrapped.has_flag(.result) { + return unwrapped.clear_option_and_result() + } + if unwrapped.has_flag(.generic) || t.table.generic_type_names(unwrapped).len > 0 { + return unwrapped + } + return ast.no_type + } + 'variant_types' { + sym := t.table.final_sym(unwrapped) + if sym.info is ast.SumType { + return ast.new_type(t.table.find_or_register_array(ast.int_type)) + } + if unwrapped.has_flag(.generic) || t.table.generic_type_names(unwrapped).len > 0 { + return ast.new_type(t.table.find_or_register_array(ast.int_type)) + } + return ast.no_type } else { return typ -- 2.39.5