← board

A field selection directly on a record-typed cast is not parsed

Measured — binary 490a2cfd83a2, identical on pinned

  writeln('inst ', PtrUInt(TMethod(TSel(s.Pick)).Code) <> 0);
pascal26:12: error: expected ')' before '.'
  near: ( s . Pick ) ) >>> . Code )

FPC 3.2.2 compiles the same program and prints inst TRUE.

The two-step form works, which is what isolates the postfix selection as the variable rather than anything about the operand:

  f := TSel(s.Pick);
  m := TMethod(f);
  writeln('via var ', PtrUInt(m.Code) <> 0);     { fine }

And it fails identically for every receiver spelling inside the inner cast — instance variable, Self, bare name — so it is not about method references. Reduced further, the subject is: a cast whose target is a RECORD type is not accepted as the base of a .field postfix.

Why it is worth more than its own repro

TMethod(X).Code is the standard way to look at a method pointer's two halves, and it is the spelling the tickets in this area use to state their own test tables — including [[bug-p-a-parenless-method-reference-handles-two-of-four-receiver-spellings]], whose table lists TMethod(TSel(TSvc.Pick)).Code as compiling. It does not, and never did on any binary measured; that ticket's arms were really measured through a variable. Anyone writing a gate for a method-pointer ticket will reach for this spelling first and hit this, which is how one ticket's table came to record a shape nobody had run.

(That ticket's row B is separately not a valid FPC program for a different reason: TSvc.Pick on an INSTANCE method is rejected by FPC with "Only class methods ... can be referred with class references". The class-name receiver needs a class method to be exercised honestly.)

Gate

The repro above and its two-step control, plus one non-method record cast (TSomeRec(x).Field) to establish whether the gap is method-pointer-specific or general to record casts — that distinction is not measured yet and should be before the fix is designed. Oracle: FPC.


2026-09-04 (frankA) — REJECTED: the reduction came from the second error in a batch

This ticket's reduction is "a cast whose target is a RECORD type is not accepted as the base of a .field postfix". That is false, and was false when filed. Eight record-cast shapes, all compiling and all matching fpc 3.2.2, on this binary and on pinned:

shape
TRek(q).a cast of a variable ok
TRek(o.inner).a cast of a field ok
TRek(TQ(z)).a cast of a cast ok
TRek(TQ(TZ(z))).a double-nested ok
TRek(G).a cast of a call result ok
TRek(p^).a cast of a deref ok
TRek(arr[0]).a cast of an element ok
TMethod(TSel(@s.Pick)).Code this ticket's own line ok

What actually happened

TMethod was not a builtin type, and on pinned the compiler emitted a batch:

pascal26:12: error: undefined variable (TMethod)      <- the cause
pascal26:12: error: expected ')' before '.'           <- what this ticket quoted

The second line is a consequence of the first: with TMethod unresolved, TMethod(...) is not a cast, so the .Code postfix has no base to attach to. Declaring TMethod = record Code, Data: Pointer; end; in the program made the ticket's exact line print inst TRUE, matching FPC — that was the discriminating experiment, and it was one edit.

The current binary prints only the first error (recovery got shorter since), so the quoted diagnostic no longer reproduces at all, which is itself the tell.

TMethod became a builtin at 31f8b11bf (frankH). Re-measured at HEAD after that landed, with no local declaration: compiles, prints inst TRUE, FPC agrees.

Two claims in the body that do not survive

The gate this ticket asked for — "one non-method record cast (TSomeRec(x).Field) to establish whether the gap is method-pointer-specific or general to record casts — that distinction is not measured yet" — is the measurement that rejected it. It was the right thing to ask for.

Rejected, not moved to known-incompat: nothing here is a divergence we chose. The report was wrong about the mechanism, which is what rejected/ is for.