Finalize an out Variant and an out managed record
- Type: feature (completes a partial fix) — Track A
- Status: done
- Opened: 2026-08-22
What already works, and why these two do not
[[bug-a-an-out-parameter-of-a-managed-type-is-not-cleared]] made out finalize
its parameter on entry the cheap way: a managed ASSIGNMENT already releases the
old value, and an assignment through a by-ref param already writes the caller's
variable, so s := '' / d := nil / f := nil at the head of the body IS the
finalize, with no new emitter and nothing per backend.
That works only for kinds with an empty-value literal. The remaining two:
| FPC 3.2.2 | pxx | |
|---|---|---|
out v: Variant (caller had 'vv') |
[] |
[vv] |
out r: TRecM (record with an AnsiString field, caller had 'q') |
[] |
[q] |
- Variant:
v := Unassignedcompiles but renders asNone, not the empty FPC prints — so assigning it trades one divergence for another.PXXVarClear(v: Pointer)already exists inbuiltinheapand is the right primitive; it is simply not reachable from Pascal source (see [[bug-b-vartostr-is-missing-from-variants]] for the sibling export gap). - Managed record: no empty literal exists. Needs a genuine finalize —
release each managed field, then zero — through the caller's pointer.
RecordNeedsZeroInit/RecordHasManagedFieldsalready name the field set, andIR_DEFAULT_MEMalready zeroes a record region at an address; what is missing is the release pass in front of it.
Worth doing as one primitive, not two arms
Both want the same thing — finalize the managed thing at this ADDRESS — which
is also what an out param of a future managed kind will want, and close to
what scope-exit cleanup does for locals by frame offset. A single
address-taking finalize op would also give
Initialize/Finalize on a bare dynarray or Variant, which ir.inc:7526
currently refuses outright with "not implemented yet; assign nil instead, or
wrap it in a record (feature-a-finalize-for-bare-dynarray-and-variant)". Check
whether that ticket and this one are the same work before starting either.
Side question this turned up
An empty Variant printing None is a NilPy spelling reaching Pascal output;
FPC prints the empty string. Worth its own look — it is not specific to out.
Gate
Track A's, plus the two rows above matching fpc 3.2.2 added to
test/test_out_parameter_of_a_managed_type_is_cleared.pas, and its ARC round
trip extended to a Variant and a managed record (a second owner of the record's
string field must survive the clear).
Landed 2026-08-24
Both rows now match fpc 3.2.2 byte for byte, on x86-64, aarch64, arm32 and i386.
The two tickets in the "worth doing as one primitive" note above turned out to
be the same work, and the answer was that the primitive already existed:
AN_MANAGED_INIT, the node Finalize(x) builds. It knew records from the day
it landed, and it learned bare Variants in this same change
([[feature-a-finalize-for-bare-dynarray-and-variant]]'s separable half). So
ClearManagedOutParam did not need a new emitter after all — it builds that
node over the parameter's ident and compiles it, which is literally "emit the
intrinsic the user could have written by hand, at the head of the body".
Finalize and not Initialize, for the reason the two differ by: an out param's
incoming bytes ARE the caller's live references, so dropping them without
releasing is a leak, which is the second half of what the ticket reported.
Two negative rows guard the other direction, and both are in the gated test:
unmgdrec 1 2 (a record with NO managed fields is not cleared — FPC does not
finalize it either, so the caller's value must survive) and rec [] 3 (the
managed field is released, the unmanaged n is untouched — the line between
Finalize and FillChar).
The side question — an empty Variant rendering as None — was fixed first, as
[[bug-a-a-null-variant-renders-as-none-in-pascal]]; without it the variant []
row could not have been written at all.
Not done, and not this ticket: VarClear is still missing from the Pascal
surface (uses variants; VarClear(a) is "undefined variable"), which is
[[bug-b-vartostr-is-missing-from-variants]]' shape in Track B's lane.
Log
- 2026-08-24 — resolved, commit 7fcf3108b.