Skip to content

Commit 8b5f74e

Browse files
committed
comptime: add type metadata accessors .pointee_type, .payload_type, .variant_types, plus $zero(TypeExpr) and $new(TypeExpr) (fixes #26980)
1 parent 11af49e commit 8b5f74e

16 files changed

Lines changed: 512 additions & 38 deletions

File tree

doc/docs.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6644,6 +6644,28 @@ println('This program, was compiled at ${time.unix(@BUILD_TIMESTAMP.i64()).forma
66446644
Having built-in JSON support is nice, but V also allows you to create efficient
66456645
serializers for any data format. V has compile time `if` and `for` constructs:
66466646
6647+
#### <h4 id="comptime-type-metadata">Type metadata</h4>
6648+
6649+
Comptime type expressions expose metadata through fields like `.idx`, `.typ`,
6650+
`.unaliased_typ`, `.indirections`, `.key_type`, `.value_type`, `.element_type`,
6651+
`.pointee_type`, `.payload_type`, and `.variant_types`.
6652+
6653+
```v
6654+
fn zero_payload[T](x ?T) T {
6655+
return $zero(typeof(x).payload_type)
6656+
}
6657+
6658+
fn main() {
6659+
value := ?int(123)
6660+
assert zero_payload(value) == 0
6661+
assert typeof[map[string]int]().key_type == typeof[string]().idx
6662+
assert typeof[?&int]().payload_type == typeof[&int]().idx
6663+
}
6664+
```
6665+
6666+
`$zero(Type)` returns the zero value for a comptime type expression.
6667+
`$new(Type)` returns a pointer to a new zero value.
6668+
66476669
#### <h4 id="comptime-fields">.fields</h4>
66486670
66496671
You can iterate over struct fields using `.fields`, it also works with generic types

vlib/v/ast/ast.v

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,8 @@ pub enum ComptimeCallKind {
22172217
method
22182218
pkgconfig
22192219
embed_file
2220+
zero
2221+
new
22202222
compile_warn
22212223
compile_error
22222224
}
@@ -2284,6 +2286,9 @@ pub fn (cc ComptimeCall) expr_str() string {
22842286
}
22852287
} else if cc.kind == .pkgconfig {
22862288
str = "\$${cc.method_name}('${cc.args_var}')"
2289+
} else if cc.kind in [.zero, .new] {
2290+
arg := cc.args[0] or { return str }
2291+
str = '\$${cc.method_name}(${arg})'
22872292
}
22882293
return str
22892294
}

vlib/v/checker/checker.v

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2968,8 +2968,10 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
29682968
if node.field_name == 'name' {
29692969
return ast.string_type
29702970
} else if node.field_name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type',
2971-
'element_type'] {
2971+
'element_type', 'pointee_type', 'payload_type'] {
29722972
return ast.int_type
2973+
} else if node.field_name == 'variant_types' {
2974+
return ast.new_type(c.table.find_or_register_array(ast.int_type))
29732975
} else if node.field_name == 'indirections' {
29742976
return ast.int_type
29752977
}
@@ -2978,6 +2980,19 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
29782980
}
29792981
}
29802982
}
2983+
if is_array_init_type_expr_field(node.field_name) && c.is_comptime_type_expr(node.expr) {
2984+
mut type_expr := node.expr
2985+
base_type := c.comptime_call_type_expr_type(mut type_expr)
2986+
node.expr = type_expr
2987+
resolved := c.type_resolver.typeof_field_type(base_type, node.field_name)
2988+
if resolved != ast.no_type {
2989+
node.name_type = base_type
2990+
if node.field_name == 'variant_types' {
2991+
return ast.new_type(c.table.find_or_register_array(ast.int_type))
2992+
}
2993+
return ast.int_type
2994+
}
2995+
}
29812996
// evaluates comptime field.<name> (from T.fields)
29822997
if c.comptime.check_comptime_is_field_selector(node) {
29832998
if c.comptime.check_comptime_is_field_selector_bool(node) {

vlib/v/checker/comptime.v

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fn (mut c Checker) eval_comptime_type_meta_value(typ ast.Type, field_name string
8787
'indirections' {
8888
return i64(base_type.nr_muls())
8989
}
90-
'key_type', 'value_type', 'element_type' {
90+
'key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type' {
9191
resolved_type := c.type_resolver.typeof_field_type(base_type, field_name)
9292
if resolved_type != ast.no_type {
9393
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
9999
return none
100100
}
101101

102+
fn (c &Checker) is_generic_type_expr_ident(name string) bool {
103+
return util.is_generic_type_name(name) && c.table.cur_fn != unsafe { nil }
104+
&& name in c.table.cur_fn.generic_names
105+
}
106+
107+
fn (c &Checker) is_comptime_type_expr(expr ast.Expr) bool {
108+
return match expr {
109+
ast.ParExpr {
110+
c.is_comptime_type_expr(expr.expr)
111+
}
112+
ast.TypeNode {
113+
true
114+
}
115+
ast.TypeOf {
116+
true
117+
}
118+
ast.Ident {
119+
c.is_generic_type_expr_ident(expr.name)
120+
}
121+
ast.SelectorExpr {
122+
is_array_init_type_expr_field(expr.field_name) && c.is_comptime_type_expr(expr.expr)
123+
}
124+
else {
125+
false
126+
}
127+
}
128+
}
129+
130+
fn (mut c Checker) comptime_call_type_expr_type(mut expr ast.Expr) ast.Type {
131+
match mut expr {
132+
ast.ParExpr {
133+
return c.comptime_call_type_expr_type(mut expr.expr)
134+
}
135+
ast.TypeNode {
136+
return c.recheck_concrete_type(expr.typ)
137+
}
138+
ast.TypeOf {
139+
if expr.is_type {
140+
return c.recheck_concrete_type(expr.typ)
141+
}
142+
if expr.typ == 0 || expr.typ == ast.void_type || expr.typ == ast.no_type {
143+
expr.typ = c.expr(mut expr.expr)
144+
}
145+
resolved_type := c.recheck_concrete_type(expr.typ)
146+
if resolved_type != 0 && resolved_type != ast.void_type && resolved_type != ast.no_type {
147+
return resolved_type
148+
}
149+
return c.recheck_concrete_type(c.type_resolver.typeof_type(expr.expr, expr.typ))
150+
}
151+
ast.ArrayInit {
152+
if expr.elem_type_expr !is ast.EmptyExpr {
153+
c.resolve_array_init_elem_type_expr(mut expr)
154+
return expr.typ
155+
}
156+
return c.expr(mut expr)
157+
}
158+
ast.SelectorExpr {
159+
if is_array_init_type_expr_field(expr.field_name) {
160+
base_type := c.comptime_call_type_expr_type(mut expr.expr)
161+
resolved := c.type_resolver.typeof_field_type(base_type, expr.field_name)
162+
if resolved != ast.no_type {
163+
return c.recheck_concrete_type(resolved)
164+
}
165+
}
166+
c.expr(mut expr)
167+
return c.recheck_concrete_type(c.get_expr_type(expr))
168+
}
169+
ast.Ident {
170+
if c.is_generic_type_expr_ident(expr.name) {
171+
return c.table.find_type(expr.name).set_flag(.generic)
172+
}
173+
return c.recheck_concrete_type(c.get_expr_type(expr))
174+
}
175+
else {
176+
return c.recheck_concrete_type(c.expr(mut expr))
177+
}
178+
}
179+
}
180+
102181
fn (mut c Checker) eval_comptime_type_selector_value(expr ast.SelectorExpr) ?ast.ComptTimeConstValue {
103182
if expr.expr is ast.Ident && c.table.cur_fn != unsafe { nil } {
104183
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 {
317396
}
318397
return node.result_type
319398
}
399+
if node.kind in [.zero, .new] {
400+
if node.args.len != 1 {
401+
c.error('`\$${node.method_name}()` expects 1 type argument', node.pos)
402+
return ast.void_type
403+
}
404+
mut type_expr := node.args[0].expr
405+
resolved_type := c.comptime_call_type_expr_type(mut type_expr)
406+
node.args[0].expr = type_expr
407+
if resolved_type == ast.void_type || resolved_type == ast.no_type {
408+
c.error('`\$${node.method_name}()` expects a valid type expression', node.args[0].pos)
409+
return ast.void_type
410+
}
411+
node.args[0].typ = resolved_type
412+
node.result_type = if node.kind == .new { resolved_type.ref() } else { resolved_type }
413+
return node.result_type
414+
}
320415
if node.kind == .embed_file {
321416
if node.args.len == 1 {
322417
embed_arg := node.args[0]
@@ -1410,17 +1505,23 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type {
14101505
}
14111506
ast.SelectorExpr {
14121507
if c.comptime.inside_comptime_for
1413-
&& cond.field_name in ['typ', 'unaliased_typ', 'indirections']
1508+
&& cond.field_name in ['typ', 'unaliased_typ', 'indirections', 'pointee_type', 'payload_type', 'variant_types']
14141509
&& cond.expr is ast.Ident && (cond.expr.name == c.comptime.comptime_for_variant_var
14151510
|| cond.expr.name == c.comptime.comptime_for_method_param_var
14161511
|| cond.expr.name == c.comptime.comptime_for_field_var) {
14171512
typ := c.type_resolver.get_type_from_comptime_var(cond.expr as ast.Ident)
14181513
if cond.field_name == 'unaliased_typ' {
14191514
return c.table.unaliased_type(typ)
1515+
} else if cond.field_name in ['pointee_type', 'payload_type', 'variant_types'] {
1516+
return c.type_resolver.typeof_field_type(typ, cond.field_name)
14201517
}
14211518
// for `indirections` we also return the `typ`
14221519
return typ
14231520
}
1521+
if cond.name_type != 0
1522+
&& cond.field_name in ['key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type', 'variant_types'] {
1523+
return c.type_resolver.typeof_field_type(cond.name_type, cond.field_name)
1524+
}
14241525
if cond.gkind_field in [.typ, .indirections, .unaliased_typ] {
14251526
if cond.expr is ast.Ident {
14261527
generic_name := cond.expr.name

vlib/v/checker/containers.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn is_inferred_fixed_array_size_expr(expr ast.Expr) bool {
5454

5555
fn is_array_init_type_expr_field(name string) bool {
5656
return name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type',
57-
'indirections']
57+
'pointee_type', 'payload_type', 'variant_types', 'indirections']
5858
}
5959

6060
fn (mut c Checker) fixed_array_contains_inferred_size(typ ast.Type) bool {

vlib/v/checker/struct.v

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -542,31 +542,78 @@ fn minify_sort_fn(a &ast.StructField, b &ast.StructField) int {
542542
}
543543
}
544544

545-
fn (mut c Checker) struct_init_selector_type_expr(expr ast.SelectorExpr) ast.Type {
546-
if expr.expr is ast.TypeOf {
547-
return c.type_resolver.typeof_field_type(c.type_resolver.typeof_type(expr.expr.expr,
548-
expr.name_type), expr.field_name)
545+
fn (mut c Checker) struct_init_selector_type_expr(mut expr ast.SelectorExpr) ast.Type {
546+
if !is_array_init_type_expr_field(expr.field_name) {
547+
return ast.void_type
548+
}
549+
base_type := c.struct_init_type_expr(mut expr.expr)
550+
if base_type == ast.void_type {
551+
return ast.void_type
549552
}
550-
return ast.void_type
553+
return c.type_resolver.typeof_field_type(base_type, expr.field_name)
551554
}
552555

553-
fn (mut c Checker) struct_init_type_expr(expr ast.Expr) ast.Type {
554-
return match expr {
556+
fn (mut c Checker) struct_init_type_expr(mut expr ast.Expr) ast.Type {
557+
return match mut expr {
555558
ast.TypeNode {
556559
expr.typ
557560
}
558561
ast.ParExpr {
559-
c.struct_init_type_expr(expr.expr)
562+
c.struct_init_type_expr(mut expr.expr)
563+
}
564+
ast.TypeOf {
565+
if expr.is_type {
566+
c.recheck_concrete_type(expr.typ)
567+
} else {
568+
if expr.typ == 0 || expr.typ == ast.void_type || expr.typ == ast.no_type {
569+
expr.typ = c.expr(mut expr.expr)
570+
}
571+
resolved_type := c.recheck_concrete_type(expr.typ)
572+
if resolved_type != 0 && resolved_type != ast.void_type
573+
&& resolved_type != ast.no_type {
574+
resolved_type
575+
} else {
576+
c.recheck_concrete_type(c.type_resolver.typeof_type(expr.expr, expr.typ))
577+
}
578+
}
579+
}
580+
ast.Ident {
581+
if c.is_generic_type_expr_ident(expr.name) {
582+
c.table.find_type(expr.name).set_flag(.generic)
583+
} else {
584+
c.get_expr_type(expr)
585+
}
560586
}
561587
ast.SelectorExpr {
562-
c.struct_init_selector_type_expr(expr)
588+
c.struct_init_selector_type_expr(mut expr)
563589
}
564590
else {
565591
ast.void_type
566592
}
567593
}
568594
}
569595

596+
fn (c &Checker) struct_init_uses_comptime_type_accessor(expr ast.Expr) bool {
597+
return match expr {
598+
ast.ParExpr {
599+
c.struct_init_uses_comptime_type_accessor(expr.expr)
600+
}
601+
ast.SelectorExpr {
602+
mut is_base_type_expr := expr.expr is ast.TypeOf
603+
|| c.struct_init_uses_comptime_type_accessor(expr.expr)
604+
if expr.expr is ast.Ident {
605+
is_base_type_expr = is_base_type_expr
606+
|| c.is_generic_type_expr_ident(expr.expr.name)
607+
}
608+
expr.field_name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type']
609+
&& is_base_type_expr
610+
}
611+
else {
612+
false
613+
}
614+
}
615+
}
616+
570617
fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_init bool, mut inited_fields []string) ast.Type {
571618
util.timing_start(@METHOD)
572619
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
579626
&& c.expected_type != ast.void_type && c.expected_type.has_flag(.generic)
580627
&& short_syntax_expected_type_sym.kind == .any
581628
&& !short_syntax_expected_type_sym.is_builtin()
582-
if node.typ == ast.void_type && !node.is_short_syntax && node.typ_expr !is ast.EmptyExpr {
583-
c.expr(mut node.typ_expr)
584-
node.typ = c.struct_init_type_expr(node.typ_expr)
585-
if node.typ == ast.void_type {
629+
is_comptime_type_struct_init := !node.is_short_syntax && node.typ_expr !is ast.EmptyExpr
630+
&& c.struct_init_uses_comptime_type_accessor(node.typ_expr)
631+
should_resolve_typ_expr := node.typ == ast.void_type
632+
|| (is_comptime_type_struct_init && c.has_active_generic_recheck_context())
633+
if should_resolve_typ_expr && !node.is_short_syntax && node.typ_expr !is ast.EmptyExpr {
634+
if !is_comptime_type_struct_init {
635+
c.expr(mut node.typ_expr)
636+
}
637+
node.typ = c.struct_init_type_expr(mut node.typ_expr)
638+
if node.typ == ast.void_type || node.typ == ast.no_type {
586639
c.error('cannot use `${node.typ_expr}` as a struct init type', node.typ_expr.pos())
587640
return ast.void_type
588641
}
589-
node.unresolved = node.typ.has_flag(.generic)
642+
node.unresolved = !is_comptime_type_struct_init && node.typ.has_flag(.generic)
590643
}
591644
source_typ := if node.is_short_syntax && c.expected_type != ast.void_type
592645
&& !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
769822
type_sym := c.table.sym(concrete_node_typ)
770823
is_generic_zero_struct_init := original_node_typ.has_flag(.generic) && node.init_fields.len == 0
771824
&& !node.has_update_expr
825+
is_comptime_type_zero_struct_init := node.init_fields.len == 0 && !node.has_update_expr
826+
&& is_comptime_type_struct_init
772827
if is_generic_zero_struct_init {
773828
// Don't early-return for single-letter types (like F{}) in non-generic functions —
774829
// 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
778833
return concrete_node_typ
779834
}
780835
}
781-
if !is_field_zero_struct_init {
836+
if !is_field_zero_struct_init && !is_comptime_type_zero_struct_init {
782837
type_exists := c.ensure_type_exists(node.typ, node.pos)
783838
if !type_exists && node.typ.idx() > 0 && c.table.sym(node.typ).kind == .placeholder {
784839
return ast.void_type
@@ -830,7 +885,7 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini
830885
if !node.has_update_expr && !type_sym.is_pub && type_sym.kind != .placeholder
831886
&& type_sym.language != .c
832887
&& (type_sym.mod != c.mod && !(is_generic_init && type_sym.mod != 'builtin'))
833-
&& !is_field_zero_struct_init {
888+
&& !is_field_zero_struct_init && !is_comptime_type_zero_struct_init {
834889
c.error('type `${type_sym.name}` is private', node.pos)
835890
}
836891
if type_sym.info is ast.Struct && type_sym.mod != c.mod && !is_field_zero_struct_init {

vlib/v/fmt/fmt.v

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2414,6 +2414,11 @@ pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) {
24142414
f.write('\$res()')
24152415
}
24162416
}
2417+
node.kind in [.zero, .new] {
2418+
f.write('\$${node.method_name}(')
2419+
f.expr(node.args[0].expr)
2420+
f.write(')')
2421+
}
24172422
else {
24182423
inner_args := if node.args_var != '' {
24192424
node.args_var

0 commit comments

Comments
 (0)