3030 runtime_local_types map [string ]string
3131 cur_fn_returned_idents map [string ]bool
3232 active_generic_types map [string ]types.Type
33+ // Comptime $for field iteration state
34+ comptime_field_var string // variable name (e.g., 'field')
35+ comptime_field_name string // current field name (e.g., 'id')
36+ comptime_field_type string // current field C type name
37+ comptime_field_raw_type types.Type = types.Struct{} // raw types.Type for comptime checks
38+ comptime_field_attrs []string // current field attributes
39+ comptime_field_idx int // current field index
40+ comptime_val_var string // the struct variable being decoded (e.g., 'val')
41+ comptime_val_type string // C type of val (e.g., 'Slack')
3342
3443 fixed_array_fields map [string ]bool
3544 fixed_array_field_elem map [string ]string
7685 resolved_module_names map [string ]string // per-function cache for resolve_module_name
7786 cached_env_scopes map [string ]voidptr // cache of env_scope results (avoids repeated locking)
7887
79- const_exprs map [string ]string // const name → C expression string (for inlining)
80- const_types map [string ]string // const name → C type string
81- runtime_const_targets map [string ]bool // module-scoped consts initialized in __v_init_consts_*
82- used_fn_keys map [string ]bool
83- force_emit_fn_names map [string ]bool // function C names that must be emitted regardless of mark_used
84- export_fn_names map [string ]string // V-qualified name → export name (from @[export:] attribute)
85- called_fn_names map [string ]bool
86- generic_spec_index map [string ][]string // fn_name → matching keys in env.generic_types
87- anon_fn_defs []string // lifted anonymous function definitions
88- pass5_ start_pos int // position in sb where pass 5 starts
89- deferred_m_includes []string // Objective-C .m file #include lines deferred until after type definitions
90- spawned_fns map [string ]bool // spawn wrapper names already emitted
91- spawn_wrapper_defs []string // spawn wrapper struct + function definitions
92- emitted_trampolines map [string ]bool // bound method trampoline names already emitted
93- trampoline_defs []string // bound method trampoline definitions
88+ const_exprs map [string ]string // const name → C expression string (for inlining)
89+ const_types map [string ]string // const name → C type string
90+ runtime_const_targets map [string ]bool // module-scoped consts initialized in __v_init_consts_*
91+ used_fn_keys map [string ]bool
92+ force_emit_fn_names map [string ]bool // function C names that must be emitted regardless of mark_used
93+ export_fn_names map [string ]string // V-qualified name → export name (from @[export:] attribute)
94+ called_fn_names map [string ]bool
95+ generic_spec_index map [string ][]string // fn_name → matching keys in env.generic_types
96+ late_generic_specs map [string ][]map [string ]types.Type // additional comptime-discovered specs
97+ anon_fn_defs []string // lifted anonymous function definitions
98+ late_struct_defs []string // struct definitions discovered during pass 5 codegen
99+ pending_late_body_keys map [string ]bool // body_keys in late_struct_defs but not yet flushed to g.sb
100+ late_generic_str_instances []string // c_names of late generic struct instances needing str macro check
101+ pass5_ start_pos int // position in sb where pass 5 starts
102+ deferred_m_includes []string // Objective-C .m file #include lines deferred until after type definitions
103+ spawned_fns map [string ]bool // spawn wrapper names already emitted
104+ spawn_wrapper_defs []string // spawn wrapper struct + function definitions
105+ emitted_trampolines map [string ]bool // bound method trampoline names already emitted
106+ trampoline_defs []string // bound method trampoline definitions
94107 // @[live] hot code reloading
95108 live_fns []LiveFnInfo // @[live] functions detected during code generation
96109 live_source_file string // source file containing @[live] functions
@@ -99,10 +112,20 @@ mut:
99112 fn_owner_file map [string ]int // fn_key -> first file index (for parallel dedup)
100113 global_owner_file map [string ]int // global_name -> first file index (for parallel dedup)
101114 generic_struct_bindings map [string ]map [string ]types.Type // struct_name -> {T: concrete_type}
102- c_file_fn_keys map [string ]bool // fn_key -> emitted from a .c.v file, so plain .v fallback should be skipped
103- typedef_c_types map [string ]bool // C struct names with @[typedef] attribute (emit without 'struct' prefix)
104- blocked_fn_keys map [string ]bool // worker-only fn keys reserved to other pass5 chunks
105- cached_vhash string // cached git short hash for @VHASH/@VCURRENTHASH
115+ // Multi-instantiation support: maps base struct C name (e.g. "json2__Node") to
116+ // a list of (suffix, bindings) pairs for each distinct concrete instantiation.
117+ // E.g. [("json2__ValueInfo", {T: ValueInfo}), ("json2__StructFieldInfo", {T: StructFieldInfo})]
118+ generic_struct_instances map [string ][]GenericStructInstance
119+ c_file_fn_keys map [string ]bool // fn_key -> emitted from a .c.v file, so plain .v fallback should be skipped
120+ typedef_c_types map [string ]bool // C struct names with @[typedef] attribute (emit without 'struct' prefix)
121+ blocked_fn_keys map [string ]bool // worker-only fn keys reserved to other pass5 chunks
122+ cached_vhash string // cached git short hash for @VHASH/@VCURRENTHASH
123+ }
124+
125+ struct GenericStructInstance {
126+ params_key string // e.g. "json2__ValueInfo" — unique key per instantiation
127+ bindings map [string ]types.Type // e.g. {T: ValueInfo}
128+ c_name string // full C struct name, e.g. "json2__Node_T_json2__StructFieldInfo"
106129}
107130
108131struct LiveFnInfo {
@@ -534,9 +557,9 @@ pub fn (mut g Gen) gen_passes_1_to_4() {
534557
535558 g.write_preamble ()
536559 g.collect_typedef_c_types ()
560+ g.collect_generic_struct_bindings ()
537561 g.collect_module_type_names ()
538562 g.collect_runtime_aliases ()
539- g.collect_generic_struct_bindings ()
540563 // Force eventbus generic structs to use T=string binding.
541564 // Without full monomorphization, eventbus methods assume T=string
542565 // (see fn.v hardcoded eventbus workaround).
@@ -552,6 +575,7 @@ pub fn (mut g Gen) gen_passes_1_to_4() {
552575 g.generic_struct_bindings['stdatomic__AtomicVal' ] = {
553576 'T' : types.Type (types.f64_ )
554577 }
578+ g.discover_comptime_generic_specs ()
555579 g.collect_fn_signatures ()
556580 g.collect_c_file_fn_keys ()
557581 g.collect_runtime_const_targets ()
@@ -597,6 +621,19 @@ pub fn (mut g Gen) gen_passes_1_to_4() {
597621 g.emitted_types[name] = true
598622 keyword := if stmt.is_union { 'union' } else { 'struct' }
599623 g.sb.writeln ('typedef ${keyword } ${name } ${name };' )
624+ // Also emit forward declarations for additional generic instances
625+ if stmt.generic_params.len > 0 {
626+ instances := g.generic_struct_instances[name]
627+ for inst in instances {
628+ if inst.c_name == name {
629+ continue
630+ }
631+ if inst.c_name ! in g.emitted_types {
632+ g.emitted_types[inst.c_name] = true
633+ g.sb.writeln ('typedef ${keyword } ${inst .c_name } ${inst .c_name };' )
634+ }
635+ }
636+ }
600637 } else if stmt is ast.TypeDecl {
601638 if stmt.variants.len > 0 {
602639 // Sum type needs forward struct declaration
@@ -676,6 +713,10 @@ pub fn (mut g Gen) gen_passes_1_to_4() {
676713 stage_start = g.mark_cgen_step (stats_enabled, stats_scope, mut stats_sw, stage_start,
677714 'pass 2 type declarations' )
678715
716+ // Emit pointer element typedefs (e.g. 'typedef Color* Colorptr;') now that
717+ // enums and type aliases have been defined.
718+ g.emit_pointer_typedefs ()
719+
679720 // Pass 3: Full struct definitions (use named struct/union to match forward decls)
680721 // Collect all struct decls, then emit in dependency order
681722 mut all_structs := []StructDeclInfo{}
@@ -756,6 +797,9 @@ pub fn (mut g Gen) gen_passes_1_to_4() {
756797 'pass 3.1 fixed arrays' )
757798
758799 // Pass 3.25: Tuple aliases (multiple-return lowering support)
800+ if 'body_array' ! in g.emitted_types {
801+ eprintln ('WARNING: body_array not emitted before pass 3.25 tuples' )
802+ }
759803 g.emit_tuple_aliases ()
760804 if g.tuple_aliases.len > 0 {
761805 g.sb.writeln ('' )
@@ -1088,10 +1132,35 @@ pub fn (mut g Gen) gen_finalize() string {
10881132 g.emit_exported_const_symbols ()
10891133
10901134 mut out := ''
1091- if g.anon_fn_defs.len > 0 || g.spawn_wrapper_defs.len > 0 || g.trampoline_defs.len > 0 {
1135+ // Emit deferred str macros for late-discovered generic struct instances.
1136+ // At this point fn_return_types is fully populated (pass 4 complete).
1137+ for inst_name in g.late_generic_str_instances {
1138+ str_fn := '${inst_name }__str'
1139+ if str_fn ! in g.fn_return_types {
1140+ label := '${inst_name }{}'
1141+ g.late_struct_defs << '#define ${inst_name }__str(v) ((string){.str = "${label }", .len = ${label .len }, .is_lit = 1})\n #define ${inst_name }_str(v) ${inst_name }__str(v)\n '
1142+ }
1143+ }
1144+ if g.anon_fn_defs.len > 0 || g.spawn_wrapper_defs.len > 0 || g.trampoline_defs.len > 0
1145+ || g.late_struct_defs.len > 0 || g.pending_late_body_keys.len > 0 {
10921146 full := g.sb.str ()
10931147 mut out_sb := strings.new_builder (full.len + 4096 )
10941148 unsafe { out_sb.write_ptr (full.str, g.pass5_ start_pos) }
1149+ // Late-discovered generic struct definitions (discovered during setup/pass 4 codegen)
1150+ for def in g.late_struct_defs {
1151+ out_sb.write_string (def)
1152+ }
1153+ // Mark pending late body keys as emitted now that they're in the output.
1154+ for key, _ in g.pending_late_body_keys {
1155+ g.emitted_types[key] = true
1156+ }
1157+ g.pending_late_body_keys = map [string ]bool {}
1158+ // Emit any option/result wrappers that were deferred because their payload
1159+ // types were only in late_struct_defs. Temporarily swap sb with out_sb.
1160+ g.sb = out_sb
1161+ g.emit_option_result_structs ()
1162+ out_sb = g.sb
1163+ g.sb = strings.new_builder (0 )
10951164 mut seen_spawn_defs := map [string ]bool {}
10961165 for def in g.spawn_wrapper_defs {
10971166 if def in seen_spawn_defs {
@@ -1263,6 +1332,9 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int, worker_id int) &Gen {
12631332 export_fn_names: g.export_fn_names.clone ()
12641333 called_fn_names: g.called_fn_names.clone ()
12651334 generic_spec_index: g.generic_spec_index.clone ()
1335+ late_generic_specs: g.late_generic_specs.clone ()
1336+ generic_struct_bindings: g.generic_struct_bindings.clone ()
1337+ generic_struct_instances: g.generic_struct_instances.clone ()
12661338 typedef_c_types: g.typedef_c_types.clone ()
12671339 // Per-worker mutable state (starts fresh).
12681340 // Each worker gets a unique tmp_counter offset to avoid name collisions
0 commit comments