cgen: corrupted C output for heap struct literal (&T{}) init — split identifier emitted at file scope #25
Description
A real-world program produced syntactically corrupted C for a heap struct literal initialization. The generated C splits a type identifier in half (main__Object → main__Obj + ect) and splices a different element's initialization statements into the middle of an unfinished expression. The corrupted fragment is emitted outside any function (the C compiler reports "not in a function").
The struct is initialized via the all-fields &T{...} heap path (direct_heap_struct_init in vlib/v/gen/c/struct.v), which allocates with builtin___v_malloc(...) and then assigns each field individually. When several such heap initializations are reordered/hoisted (array element or nested element ordering), the statement-splitting bookkeeping (go_before_last_stmt / go_before_ternary) appears to cut at the wrong buffer position.
Generated C (from the bug report)
VV_LOC bool main__truthy(main__Object* obj) {
return ((obj->kind == (_const_main__kind_always_true))? (true) : /* ... long ternary ... */ (true));
}
main__Object* _t1 = (main__Obj main__Object* _t2 = (main__Object*)builtin___v_malloc(sizeof(main__Object) == 0 ? 1 : sizeof(main__Object));
_t2->kind = _const_main__kind_always_false;
_t2->ival = 0;
_t2->len_val = 0;
ect*)builtin___v_malloc(sizeof(main__Object) == 0 ? 1 : sizeof(main__Ob main__Object* _t3 = (main__Object*)builtin___v_malloc(sizeof(main__Object) == 0 ? 1 : sizeof(main__Object));
_t3->kind = _const_main__kind_length_based;
The _t1 initializer (main__Object*)builtin___v_malloc(... sizeof(main__Object)) is broken in two: it begins with (main__Obj, the full _t2 block is inserted, and the tail ect*)builtin___v_malloc(... sizeof(main__Ob reappears afterwards.
C compiler error
error: 'main__Obj' undeclared here (not in a function); did you mean 'main__Object'?
Triggering conditions (identified)
- Several
&T{...}heap struct literals with all fields specified (this selectsdirect_heap_struct_init, the per-fieldmalloc+assign form, instead of theHEAP(T, (T){...})compound-literal form), and - those initializations are reordered/hoisted (array elements and/or nested elements), and
- the result lands at/near file scope.The struct in the report:
kind/ival/len_valinteger fields, withkindvalues being module-levelconsts (_const_main__kind_*), plus atruthy()method (the long ternary).
Repro status
Not yet reproduced on master (98ea23b). Targeted attempts — array of all-fields &Object{...}, nested child &Object (2–3 levels), and a top-level const array of &Object{...} — all produced correct, well-ordered C on current master (each nested allocation is fully hoisted before use). The exact trigger still needs to be isolated; the original source was not captured by the bug reporter (only the generated-C window above).
Environment
- V 0.5.1
- Original report: Linux (cc)
[!NOTE] You can use the 👍 reaction to increase the issue's priority for developers.Please note that only the 👍 reaction to the issue itself counts as a vote. Other reactions and those to comments will not be taken into account.