← board

PNode = ^TNode — the forward pointer's record FIELDS never got their pointee patched

The defect

The classic self-referential idiom

type
  PNode = ^TNode;
  TNode = record v: Integer; next: PNode; end;

declares PNode before TNode exists. ResolvePendingPointerAliases already fixed that up for the alias, by name, at the end of the type section — so a variable p: PNode got a correct pointee record.

A record field did not. AddUField copies the pointee out of LastTypePointerElemRec at field-declaration time, which for next: PNode is before the back-patch pass runs. So UFldPtrElemRec[next] kept REC_NONE forever, and nothing ever revisited it.

Why it stayed hidden for so long

Losing the record id makes every field reached through a deref of that field resolve at offset 0. And the first field of the pointee IS at offset 0 — so:

writeln(p^.next^.v);       { 42 — correct, by accident: v is at offset 0 }
writeln(p^.next^.next);    { 42 — WRONG. reads v again instead of the next pointer }

The idiom's most-written line is the one that works. Only a second field read back the wrong slot, silently. Same shape as the rest of the OOP-corpus findings: the asymmetry is the camouflage ([[project_oop_corpus_ladder_findings]]).

Note the non-forward form was always fine — TBPB = ^TBTA = record b: PB end resolves PB at field-decl time, so it never entered the pending path. The bug needed the forward reference, i.e. exactly the linked-list/tree case.

Fix

Remember which alias a pointer field was declared through (UFldPtrAlias, set from a new LastTypePointerAlias that ParseTypeKind fills when a pointer type resolves via a NAMED alias) and re-read it in the same pass that patches the aliases. C struct fields carry their pointee directly and set the marker to -1, so they are excluded.

Two siblings fixed in the same commit

Tests

test/test_forward_ptr_record_field.pas (chain reads + writes through the forward pointer, implicit deref) and test/test_pointer_member_fail.pas (negative). Both wired into make test.

Gate

make test green + self-host byte-identical.

Log