← board

A member read off a CONSTRUCTOR result silently yields garbage

Repro

program mem;
type
  TThing = class
    n: Integer;
    constructor Create(k: Integer);
    function Val: Integer;
  end;
constructor TThing.Create(k: Integer); begin n := k; end;
function TThing.Val: Integer; begin Result := n; end;
function Make(k: Integer): TThing; begin Result := TThing.Create(k); end;
var a, b, c, d: Integer;
begin
  a := TThing.Create(2).n;
  b := TThing.Create(3).Val;
  c := Make(4).n;
  d := Make(5).Val;
  writeln(a, '|', b, '|', c, '|', d);
end.
output
FPC 2|3|4|5
pxx -801112056|-801112032|4|5

What the split says

Make(4).n and Make(5).Val — a member off an ordinary function result — are correct. Only the constructor result is wrong, for both a field and a method. So the machinery for "bind a member to a call result" works; the constructor path is not producing (or not keeping) the instance pointer the member access then reads through.

Two more facts worth having before touching it:

Relationship to the neighbours

Root cause (measured, FIXED)

PXXDBG=a.ir:<proc> on the two shapes side by side said it in six lines:

ViaFunc  (Make(4).n, correct)        ViaCtor  (TThing.Create(2).n, wrong)
  2: call  a=90                        4: call  a=-45   (-Ord(tkGetMem))
  3: field a=2 ival=8 [offset=8]       5: store_sym [sym=Result]
  4: load_mem a=3
  5: store_sym [sym=Result]

The field + load_mem pair is simply absent. The expression-position constructor branch in ParseFactorCore built the GetMem node and did CurASTNode := node; Exit without ever looking at what followed, so the selector chain was dropped on the floor and the store received the instance POINTER. No reasoning needed once the IR was printed — this is the debugging-playbook case exactly.

The fix is the ParseClassRecordSelectors call every other class-valued path already makes, at that exit.

One trap on the way in: idx there is already a full rec id (REC_UCLASS_BASE + ci, assigned a few lines above with the arity check), despite ASTRight's "preserve created user-class id" comment reading like a bare class index. Passing REC_UCLASS_BASE + idx lands on an unrelated record and every member comes back "no such member".

Verification

2|3|4|5, = FPC. Also correct now, all previously broken or unparseable: writeln(TThing.Create(2).Val) (this was the parse-error half), TThing.Create(4).Twice + 1, TThing.Create(5).ClassName, TStringList.Create.Count.

Not fixed by this

[[compat-pascal-index-a-function-call-result]] — Copy(s,2,3)[1] and Make[1] still do not parse, and b.ArrP(3)[0] still hits IR_UNSUPPORTED. Those are the loud half of the family and a different code path.

Gate

Track P: make test + self-host fixedpoint (byte-identical).

Log