← board

An array field indexed through a record-pointer cast loses its element type

Measured

Same record, same field, three spellings, at binary 1beb5391ebf6:

spelling WriteLn(... .m[3]:0:2)
r.m[3] (direct) 6.00
pv^.m[3] (pointer VARIABLE) 6.00
PR(raw)^.m[3] (pointer CAST) 4618441417868443648

4618441417868443648 is 0x4018000000000000 — the IEEE-754 bits of 6.0. The bytes were always right; only the TAG was wrong, so :0:2 was silently dropped and the integer path printed the payload. The string face is the same clobber: PR(raw)^.s[2] printed 1869376613 = 0x6F6C6C65 = "ello", where r.s[2] printed e.

Cause

pasparser_expr.inc, the cast chain's else if CurTok.Kind = tkLBrack arm. It asked DerefPtrArrayElem (right answer for TP(raw)^[i]), then the scalar alias table (right answer for PChar(s)[i]), and then:

else begin tk := tyUnknown; recName := REC_NONE; end;

An array FIELD is neither of those two, and the answer was already in hand: UFldTk holds the ELEMENT kind for an array field (defs.inc:4838), so the field builder one loop iteration earlier had left tk = tyDouble. The else threw it away. Now the arm keeps it when the base IsNodeArray, and applies the same string-index rule ParseLValueAST applies — one index rule, stated once.

How it was found, which is the transferable part

Not by a matrix row and not by this ticket's shape. It came out of probing the SIBLING [-arms after fixing the comma-subscript parse in the arm next door ([[bug-a-a-comma-indexed-multi-dim-subscript-is-not-parsed-through-a-cast-or-call-result]]) — normalise-dont-special-case.md's "fixed one arm of a double case? grep for the sibling before closing", run literally.

The first probe was ND, because that is what I had just been fixing, and it looked like fallout of my own change. It is not: the 1-D form fails identically, and stable_linux_amd64/default/pinned (2026-08-27) prints the same wrong value for it. Checking against the pin is what separated "I broke this" from "I made this reachable enough to see" — the two are indistinguishable from the failing output alone, and only one of them is a revert.

Gate

make compiler/pascal26 (converged, 1beb5391ebf6), tools/gate.sh quick GREEN with the FPC seed canary PASS, tools/derefshape_matrix.sh 30/30, test/test_index_through_record_pointer_cast.pas. Fixed in commit 62540cc27.