← board

A cast to a named array type is not recognised

Found while tabulating which primaries can head a postfix chain (refactor-p-three-hand-rolled-postfix-loops). It is not a postfix gap — the cast itself does not resolve — but it turned up as a row in that table and belongs with that group.

Measured — binary f8b9e4394673, oracle fpc 3.2.2

Every kind a named type cast can target, each TName(v) followed by whatever postfix that kind admits:

cast target kind pxx fpc
TRek(r).a record ok ok
TStrA(ss)[1] string alias ok ok
PArr(rawp)^[1] pointer alias ok ok
TDeriv(bb).fV class ok ok
TIntA(ii) integer alias ok ok
TEnum(1) enum ok ok
TSet(st) set ok ok
TProcT(@PZ) procedural ok ok
TArr(aa)[1] static array undefined variable (TArr) 10
TCharA(ca)[1] array of Char undefined variable (TCharA) b
TDyn(dy)[1] dynamic array undefined variable (TDyn) 10

All three array flavours fail, in all four syntactic contexts (assignment RHS, parenthesised, argument, inside and). Eight kinds work and one does not, which is what makes this a gap rather than a design position.

The diagnostic is undefined variable, so the name is not being resolved as a type at all — nothing about a suffix, and var aa: TArr obviously works, so the type exists.

The lead

FindArrayType(name) exists and is already consulted for exactly this reason elsewhere in the same file — pasparser_expr.inc:3900 and :3936 ask it for SizeOf, with a comment saying it "mirrors ParseVarSection's FindArrayType check ... so it must be tried here too, ahead of FindTypeAlias". The cast arms ask FindTypeAlias and then EnsureBuiltinPtrAlias, and never ask FindArrayType.

Do not fix this by adding a sixth door. [[refactor-p-five-dispatch-sites-for-one-named-type-cast]] is exactly this subject: five places decide what SomeName(expr) casts to and "differ only in which names they recognise", and its history is four rounds of one door being taught something the next door still did not know. An array arm added to whichever door is convenient is round five. That refactor says the recognition rules should merge into one resolver — this is a concrete, currently-broken input for it, and it is probably the ticket that justifies doing it.

Note the ORDER constraint that refactor records and that applies here too: FindTypeAlias must be consulted first, because a source declaration outranks a builtin (symtab.inc:6215).

Gate

The table above re-run against fpc 3.2.2, plus a row asserting a cast to an array type of a DIFFERENT extent than the source is either handled or refused deliberately — FPC allows a same-size reinterpret and that boundary is not measured here.


2026-09-04 (frankA) — the route this ticket implies was broken, and fixing that came first

I started this ticket by validating its implied design: treat TArr(aa) as PArr(@aa)^ and let the existing pointer-alias postfix machinery handle the subscript. That is the natural reading of the table above, which records PArr(rawp)^[1] as ok.

The table is right and insufficient. It tested PArr — Integer elements. Adding a CHAR row to the same probe:

PCharA(@ca)^[1]   pxx: 7061644217361130338      fpc: b
Ord(PCharA(...))  pxx: 1644192610               fpc: 98

A pointer alias whose pointee is an array of Char was stamped with the -2 PChar adapter, which overwrites aliasIdx in the same slot — and aliasIdx is the only carrier of the pointee's array row. Fixed and closed as [[bug-p-a-pointer-alias-to-an-array-of-char-takes-the-pchar-adapter]].

Why that matters for THIS ticket rather than being a digression: the acceptance table above demands TCharA(ca)[1] = b. Had I built the array cast on that path first, the char row would have failed at the very last step, for a reason living nowhere near the change — and the natural debugging move would have been to suspect the new arm.

What is now established for whoever implements it

Not implemented. The prerequisite is fixed and the design is measured rather than assumed, which is the part that was missing.


2026-09-04 (frankA) — FIXED, and it cost no sixth postfix walker

EnsureArrayPtrAlias (pasparser_lval.inc) mints a pointer alias whose pointee is the named array's row, exactly as pasparser_decl.inc does for a declared PArr = ^TArr. The cast arm then asks for it as a THIRD fallback, only after FindTypeAlias and EnsureBuiltinPtrAlias have both missed, so no existing spelling changes meaning:

aliasIdx := FindTypeAlias(name);
if aliasIdx < 0 then aliasIdx := EnsureBuiltinPtrAlias(name);
if aliasIdx < 0 then begin aliasIdx := EnsureArrayPtrAlias(name); arrCast := ... end;

TArr(aa) is a VALUE cast where the alias path expects a POINTER, so the two ends are adapted rather than the middle forked: an AN_ADDR goes under the operand and an AN_DEREF over the resulting AN_PTR_CAST. From that node onward the tree is byte-for-byte what PArr(@aa)^ already produced, which is why refactor-p-three-hand-rolled-postfix-loops gains nothing to worry about — this adds no sixth suffix loop, it reuses the second.

LastTypePointerElemArrAi is a global that RegisterGeneralAlias reads; the helper clears it immediately after registering, because leaving it set poisons the next unrelated alias registration (defs.inc:5895).

Two bugs the design validation found before the code was written

Checking that the target path (PArr(@aa)^[i]) actually worked — rather than assuming it, since this ticket's own table recorded it as working — turned up two defects in it, both silent, both present in pin v403:

Neither is caused by this ticket and both would have been inherited by it.

Test

test/test_cast_to_array_type.pas, 13 rows (rec/stra/arr/char/dyn/lo/nd/same/ wrote/rhs/paren/arg/and), .expected is fpc 3.2.2's own output. Positive control: the pinned compiler refuses row 1 outright — pascal26:60: error: undefined variable (TArr).

Log