From 439eceb5ce67003141d2d2be84adb01659380f59 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 05:50:04 +0300 Subject: [PATCH] checker: support $for variant in field.typ.variants for sumtype fields (fixes #26902) --- vlib/v/checker/comptime.v | 9 ++++++- vlib/v/fmt/fmt.v | 9 ++++--- vlib/v/gen/c/comptime.v | 8 +++++- vlib/v/parser/comptime.v | 25 +++++++++++++++++-- vlib/v/tests/comptime/comptime_variant_test.v | 25 +++++++++++++++++++ 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index b85bf30a7..461f3d004 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -414,7 +414,7 @@ fn (mut c Checker) comptime_selector(mut node ast.ComptimeSelector) ast.Type { } fn (mut c Checker) comptime_for(mut node ast.ComptimeFor) { - typ := if node.expr !is ast.EmptyExpr { + mut typ := if node.expr !is ast.EmptyExpr { node.typ = c.expr(mut node.expr) c.unwrap_generic(node.typ) } else if node.typ != ast.void_type { @@ -423,6 +423,13 @@ fn (mut c Checker) comptime_for(mut node ast.ComptimeFor) { node.typ = c.expr(mut node.expr) c.unwrap_generic(node.typ) } + if node.expr !is ast.EmptyExpr { + resolved_typ := c.type_resolver.get_type(node.expr) + if resolved_typ != ast.void_type { + node.typ = resolved_typ + typ = c.unwrap_generic(node.typ) + } + } sym := if node.typ != c.field_data_type { c.table.final_sym(typ) } else { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 086f95ca3..3ccf79612 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -941,12 +941,13 @@ pub fn (mut f Fmt) branch_stmt(node ast.BranchStmt) { } pub fn (mut f Fmt) comptime_for(node ast.ComptimeFor) { - typ := if node.typ != ast.void_type { - f.no_cur_mod(f.type_to_str_using_aliases(node.typ, f.mod2alias)) + f.write('\$for ${node.val_var} in ') + if node.typ != ast.void_type { + f.write(f.no_cur_mod(f.type_to_str_using_aliases(node.typ, f.mod2alias))) } else { - (node.expr as ast.Ident).name + f.expr(node.expr) } - f.write('\$for ${node.val_var} in ${typ}.${node.kind.str()} {') + f.write('.${node.kind.str()} {') if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { f.writeln('') f.stmts(node.stmts) diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 9cc1885a0..a49dfe333 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -903,7 +903,13 @@ fn (mut g Gen) pop_comptime_info() { fn (mut g Gen) comptime_for(node ast.ComptimeFor) { resolved_typ := if node.expr !is ast.EmptyExpr { - g.unwrap_generic(g.recheck_concrete_type(g.resolved_expr_type(node.expr, node.typ))) + mut expr_typ := g.unwrap_generic(g.recheck_concrete_type(g.resolved_expr_type(node.expr, + node.typ))) + resolved_ct_typ := g.type_resolver.get_type(node.expr) + if resolved_ct_typ != ast.void_type { + expr_typ = g.unwrap_generic(g.recheck_concrete_type(resolved_ct_typ)) + } + expr_typ } else if node.typ != g.field_data_type { g.unwrap_generic(node.typ) } else { diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 658613bab..50cf7b607 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -11,10 +11,17 @@ import v.util const supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig', 'compile_error', 'compile_warn', 'd', 'res'] +const supported_comptime_for_kinds = ['methods', 'fields', 'values', 'variants', 'attributes', + 'params'] const comptime_types = ['map', 'array', 'array_dynamic', 'array_fixed', 'int', 'float', 'struct', 'interface', 'enum', 'sumtype', 'alias', 'function', 'option', 'shared', 'string', 'pointer', 'voidptr'] +@[inline] +fn is_supported_comptime_for_kind(name string) bool { + return name in supported_comptime_for_kinds +} + fn (mut p Parser) parse_comptime_type() ast.ComptimeType { pos := p.tok.pos() p.check(.dollar) @@ -538,6 +545,7 @@ fn (mut p Parser) comptime_for() ast.ComptimeFor { // `$for field in App.fields {` // `$for attr in App.attributes {` // `$for variant in App.variants {` + // `$for variant in field.typ.variants {` p.next() p.check(.key_for) var_pos := p.tok.pos() @@ -555,8 +563,21 @@ fn (mut p Parser) comptime_for() ast.ComptimeFor { if p.tok.lit[0].is_capital() || p.tok.lit in p.imports { typ = p.parse_any_type(lang, false, true, false) } else { - expr = p.ident(lang) - p.scope.mark_var_as_used((expr as ast.Ident).name) + mut selector_expr := ast.Expr(p.ident(lang)) + p.scope.mark_var_as_used((selector_expr as ast.Ident).name) + for p.tok.kind == .dot && p.peek_tok.kind == .name + && !is_supported_comptime_for_kind(p.peek_tok.lit) { + selector_expr = p.dot_expr(selector_expr) + if p.name_error { + return ast.ComptimeFor{} + } + if selector_expr !is ast.SelectorExpr { + p.error_with_pos('invalid expr, use a selector like `field.typ`', + selector_expr.pos()) + return ast.ComptimeFor{} + } + } + expr = selector_expr } typ_pos = typ_pos.extend(p.prev_tok.pos()) p.check(.dot) diff --git a/vlib/v/tests/comptime/comptime_variant_test.v b/vlib/v/tests/comptime/comptime_variant_test.v index f87e3ae2b..dbf7ced93 100644 --- a/vlib/v/tests/comptime/comptime_variant_test.v +++ b/vlib/v/tests/comptime/comptime_variant_test.v @@ -20,3 +20,28 @@ fn test_main() { a := TestSum(123) gen(a) } + +type FieldVariantsMixed = []bool | []int | []string | string + +struct FieldVariantsHolder { +mut: + t FieldVariantsMixed +} + +fn enumerate_sumtype_field_variants[T]() int { + mut count := 0 + $for field in T.fields { + $if field.typ is $sumtype { + $for variant in field.typ.variants { + if typeof(variant.typ).name != '' { + count++ + } + } + } + } + return count +} + +fn test_comptime_for_field_typ_variants() { + assert enumerate_sumtype_field_variants[FieldVariantsHolder]() == 4 +} -- 2.39.5