Variadic Harbour functions (`FUNCTION foo(...)`) declare 0 params,
so Frame() copied no args into the locals slots PValue/CallerLocal
read from. Result: PValue(n) returned whatever happened to live in
the caller's LOCAL slot n (i.e. the first LOCAL declared after `(...)`,
which loops typically use as a counter). fivenode's AP_RPUTS, AP_ECHO
and any other variadic dispatcher that walks PValue() saw garbage —
fivenode_go shipped a workaround (AP_RPUTS collapsed to a single arg)
that this commit now lets us revert.
Mechanism
CallFrame now carries `actualArgs []Value`. Frame() snapshots every
value the caller pushed (the full pendingParams, not the clipped
declared count) into this slice before moving sp. The locals[]
declared-param region is unchanged so positional LOCAL access keeps
working. CallerLocal reads from actualArgs first.
Stack handling tightens slightly: t.sp now ends at `sp - pushedArgs`
instead of `sp - localsCopy`, dropping the extra-args slots that
variadic callers used to leave on the stack. They're no longer needed
— actualArgs is the canonical home — and leaving them on the stack
was the root of the original "PValue returns the caller's LOCAL"
bug because the next push would overwrite them.
Slice reuse: when capacity permits, we slice down rather than
reallocating, so the hot path (0/1/few args) keeps its no-alloc
characteristics.
Verified
• Variadic 2/1/0-arg case and fixed-arg comparison all print the
expected values (test_variadic_only.prg).
• Full suite: go test ./compiler/... ./hbrt/... ./hbrtl/...,
Compat 56/56, std.ch 17/17, FRB 7/7, FiveSql2 43/43 all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>