From 7122be4dfe73c52c9f0b16e400500bbfa86acc1a Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 05:49:36 +0300 Subject: [PATCH] parser: add inline syntax to allocate a fresh pointer of comptime-discovered field type (fixes #26897) --- vlib/v/ast/ast.v | 5 +-- vlib/v/ast/str.v | 12 ++++--- vlib/v/checker/struct.v | 34 +++++++++++++++++++ vlib/v/fmt/struct.v | 31 ++++++++++++++--- vlib/v/parser/expr.v | 21 ++++++++++++ vlib/v/parser/struct.v | 28 +++++++++++++++ .../comptime_field_indirections_test.v | 24 +++++++++++++ 7 files changed, 144 insertions(+), 11 deletions(-) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 3de85bc85..a3801c1ed 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -538,8 +538,9 @@ pub mut: pre_comments []Comment typ_str string // 'Foo' typ Type // the type of this struct - generic_typ Type // original generic struct type; reused for later concrete instantiations - update_expr Expr // `a` in `...a` + typ_expr Expr = EmptyExpr{} // `typeof(x).idx` in `typeof(x).idx{}` + generic_typ Type // original generic struct type; reused for later concrete instantiations + update_expr Expr // `a` in `...a` update_expr_type Type update_expr_pos token.Pos update_expr_comments []Comment diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index d21c86690..d1364ce92 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -728,11 +728,15 @@ pub fn (x Expr) str() string { return s + ' := ' + x.expr.str() } StructInit { - idx := x.typ.idx() - sname := if idx > 0 && idx < global_table.type_symbols.len { - global_table.type_symbols[idx].name + sname := if x.typ_expr !is EmptyExpr && x.typ == 0 { + x.typ_expr.str() } else { - 'unknown' + idx := x.typ.idx() + if idx > 0 && idx < global_table.type_symbols.len { + global_table.type_symbols[idx].name + } else { + 'unknown' + } } return '${sname}{....}' } diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index 12b3165f8..c49a229ae 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -505,6 +505,31 @@ 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) + } + return ast.void_type +} + +fn (mut c Checker) struct_init_type_expr(expr ast.Expr) ast.Type { + return match expr { + ast.TypeNode { + expr.typ + } + ast.ParExpr { + c.struct_init_type_expr(expr.expr) + } + ast.SelectorExpr { + c.struct_init_selector_type_expr(expr) + } + else { + ast.void_type + } + } +} + 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 @@ -517,6 +542,15 @@ 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 { + 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) + } source_typ := if node.is_short_syntax && c.expected_type != ast.void_type && !short_syntax_infers_anon_from_generic_param { c.expected_type diff --git a/vlib/v/fmt/struct.v b/vlib/v/fmt/struct.v index 20138a19d..d0e462aa2 100644 --- a/vlib/v/fmt/struct.v +++ b/vlib/v/fmt/struct.v @@ -204,7 +204,8 @@ pub fn (mut f Fmt) struct_init(node ast.StructInit) { defer { f.is_struct_init = struct_init_save } - sym_name := f.table.sym(node.typ).name + use_type_expr := node.typ_expr !is ast.EmptyExpr && node.typ == ast.void_type + sym_name := if use_type_expr { '' } else { f.table.sym(node.typ).name } // f.write('') mut name := if !sym_name.starts_with('C.') && !sym_name.starts_with('JS.') { f.no_cur_mod(f.short_module(sym_name)) // TODO: f.type_to_str? @@ -252,15 +253,30 @@ pub fn (mut f Fmt) struct_init(node ast.StructInit) { if node.init_fields.len == 0 && !node.has_update_expr { // `Foo{}` on one line if there are no fields or comments if node.pre_comments.len == 0 { - f.write('${name}{}') + if use_type_expr { + f.expr(node.typ_expr) + f.write('{}') + } else { + f.write('${name}{}') + } } else { - f.writeln('${name}{') + if use_type_expr { + f.expr(node.typ_expr) + f.writeln('{') + } else { + f.writeln('${name}{') + } f.comments(node.pre_comments, same_line: true, has_nl: true, level: .indent) f.write('}') } } else if node.no_keys { // `Foo{1,2,3}` (short syntax, no keys) - f.write('${name}{') + if use_type_expr { + f.expr(node.typ_expr) + f.write('{') + } else { + f.write('${name}{') + } if node.has_update_expr { f.write('...') f.expr(node.update_expr) @@ -282,7 +298,12 @@ pub fn (mut f Fmt) struct_init(node ast.StructInit) { single_line_fields = false } if !use_short_args || node.is_anon { - f.write('${name}{') + if use_type_expr { + f.expr(node.typ_expr) + f.write('{') + } else { + f.write('${name}{') + } if single_line_fields { f.write(' ') } diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 01cd00cad..cfe8a0182 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -663,6 +663,21 @@ fn (mut p Parser) parse_offsetof_expr(start_pos token.Pos) ast.Expr { } } +fn (p &Parser) can_use_expr_as_struct_init_type(expr ast.Expr) bool { + return match expr { + ast.ParExpr { + p.can_use_expr_as_struct_init_type(expr.expr) + } + ast.SelectorExpr { + expr.expr is ast.TypeOf + && expr.field_name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type'] + } + else { + false + } + } +} + fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_ident bool) ast.Expr { mut node := left if p.inside_asm && p.prev_tok.pos().line_nr < p.tok.pos().line_nr { @@ -680,6 +695,12 @@ fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_ident bo p.is_stmt_ident = is_stmt_ident continue } + if p.tok.kind == .lcbr && p.tok.is_next_to(p.prev_tok) + && p.can_use_expr_as_struct_init_type(node) { + node = p.struct_init_with_type_expr(node, .normal) + p.is_stmt_ident = is_stmt_ident + continue + } if precedence >= p.tok.kind.precedence() { return node } diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 4b1b700dc..d56696229 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -600,6 +600,33 @@ fn (mut p Parser) struct_init(typ_str string, kind ast.StructInitKind, is_option if is_option { typ = typ.set_flag(.option) } + return p.struct_init_from_parts(first_pos, typ_str, typ, ast.empty_expr, + struct_init_generic_types, kind) +} + +fn (mut p Parser) struct_init_with_type_expr(type_expr ast.Expr, kind ast.StructInitKind) ast.StructInit { + p.init_generic_types = []ast.Type{} + mut typ := ast.void_type + mut typ_expr := type_expr + match type_expr { + ast.TypeNode { + typ = type_expr.typ + typ_expr = ast.empty_expr + } + ast.ParExpr { + if type_expr.expr is ast.TypeNode { + typ = type_expr.expr.typ + typ_expr = ast.empty_expr + } + } + else {} + } + + return p.struct_init_from_parts(type_expr.pos(), type_expr.str(), typ, typ_expr, []ast.Type{}, + kind) +} + +fn (mut p Parser) struct_init_from_parts(first_pos token.Pos, typ_str string, typ ast.Type, typ_expr ast.Expr, struct_init_generic_types []ast.Type, kind ast.StructInitKind) ast.StructInit { p.expr_mod = '' if kind != .short_syntax { p.check(.lcbr) @@ -712,6 +739,7 @@ fn (mut p Parser) struct_init(typ_str string, kind ast.StructInitKind, is_option unresolved: typ.has_flag(.generic) typ_str: typ_str typ: typ + typ_expr: typ_expr init_fields: init_fields update_expr: update_expr update_expr_pos: update_expr_pos diff --git a/vlib/v/tests/comptime/comptime_field_indirections_test.v b/vlib/v/tests/comptime/comptime_field_indirections_test.v index eb3f5e455..6bc1914f3 100644 --- a/vlib/v/tests/comptime/comptime_field_indirections_test.v +++ b/vlib/v/tests/comptime/comptime_field_indirections_test.v @@ -2,6 +2,15 @@ struct Encoder {} struct Writer {} +struct ComptimeTypeofIdxInner { + x int +} + +struct ComptimeTypeofIdxOuter { +mut: + inner &ComptimeTypeofIdxInner = unsafe { nil } +} + struct StructType[T] { mut: val &T @@ -30,3 +39,18 @@ fn test_indirection_checking() { mut string_pointer := 'ads' e.encode_struct(StructType[string]{ val: &string_pointer }, mut sb)! } + +fn allocate_comptime_typeof_idx_ptrs[T](mut s T) { + $for f in T.fields { + $if f.indirections == 1 { + s.$(f.name) = &typeof(s.$(f.name)).idx{} + } + } +} + +fn test_comptime_typeof_idx_struct_init() { + mut outer := ComptimeTypeofIdxOuter{} + allocate_comptime_typeof_idx_ptrs(mut outer) + assert outer.inner != unsafe { nil } + assert outer.inner.x == 0 +} -- 2.39.5