← board

An unresolved <...> suffix is consumed in var position and dropped in field position

Measured 2026-09-06, probe build at 7ac35fe566ff (behaviourally the pre-narrowing terminus), then confirmed against the narrowed one.

{$mode delphi}
type TRec = record A: TNope<Byte>; end;   { field:  <, Byte, > reach the member-loop terminus }
var  a: TNope<Byte>;                      { var:    consumed, then `unknown type: ` (empty name) }
position <Byte> consumed? diagnostic
record/class field no pre-76efae23e: none, tokens stepped over. After: "this token is not a record member", pointing at <
plain var yes unknown type: empty, because it errors on the trailing >, not on TNope

Neither message names TNope, and the field-position one names the wrong question entirely.

Why it only bites an UNKNOWN template

DelphiRewriteGenericUses (pasparser_generic.inc:1616) sweeps the token stream and rewrites a Delphi-surface TFoo<Byte> into the mangled specialisation when it can resolve TFoo. A declared template is therefore never seen by either type parser in its <...> form:

type
  TBox<T> = record V: T; end;
  TRec    = record A: TBox<Byte>; end;   { compiles and runs — verified }

So the two paths only diverge on the name the rewrite could not resolve, which is exactly the case where a good diagnostic matters most.

Where the corpus hit it

library_candidates/fpc-testsuite/tests/test/trtti12.pp and trtti16.pp, A: TArray<byte>;. TArray is real but lives in lib/rtl/sysutils.pas:79, and neither file has uses SysUtils — FPC reaches it through System. That part is a known, deliberate placement (see the comment at sysutils.pas:87) and is NOT this ticket; it is merely how the corpus produced an unresolvable template name.

The fix

One type-reference path that treats ident < as a specialisation attempt wherever a type may appear, and refuses an unresolvable one with the template NAME in the message. That deletes the divergence rather than teaching the field parser a second copy of the var parser's stumble — devdocs/dev/normalise-dont-special-case.md.

Check while writing it: the var path's empty unknown type: is the same defect wearing a different coat, so fixing only the field side leaves a mislabelled error in place and closes nothing.

Provenance

Catch-all census over the fpc-testsuite corpus, probe logging every token reaching either member-loop terminus: processed=2294 compiled=803 refused=1491 fires=12 across four files. Read that as twelve fires over the 803 files the probe actually reached — 1491 never got there ({ %FAIL } rows and units, which pxx cannot compile standalone), and a construct appearing only in those is invisible to this run. See [[a-file-that-fails-early-is-an-absence-not-a-zero]].

2026-09-06 — RE-MEASURED: the defect is gone in all three positions; what is left is a different ticket

Tree 9341b19ac, compiler 8b2d6f26c7b2, fpc 3.2.2 -Mdelphi. Group 23 took this and measured before reading, which is how the split below was found.

The stated defect does not reproduce. A: TNope<Byte>;:

position pxx now the ticket's claim
record field unknown type: TNope three tokens dropped, silent
var unknown type: TNope unknown type: with an EMPTY name
class field unknown type: TNope (not measured then)

All three refuse by name, which is exactly this ticket's own Done when: "one type-reference path that recognises ident < as a specialisation attempt and refuses it BY NAME in both positions." And a specialisation whose template IS resolvable still compiles and runs (TArr<T> declared locally: field read and written, 9 1).

What is left is NOT this defect, and it needs its own owner

The corpus rows this ticket was found through — trtti12.pp, trtti16.pp — still fail, on a different cause: TArray<T> lives in SysUtils here and in the SYSTEM unit in FPC, so a file naming it without uses SysUtils is refused by name, correctly, for a type that ought to be ambient. Filed as bug-a-tarray-is-not-ambient-so-a-unit-that-names-it-without-uses-sysutils-is-refused (Track A — pxx has no lib/rtl/system.pas and its ambient types are compiler-side, so it is not a lib move).

And the population claim that put TArray in SysUtils is inverted between two corpora, measured while checking: 7 of 7 rtl-generics files naming TArray<> use SysUtils; 0 of 6 fpc-testsuite files do. Structural rather than luck — rtl-generics code uses generic COLLECTIONS, which need SysUtils anyway, so TArray arrives free; testsuite files exercise TArray itself and carry no more uses than the feature requires. The comment in lib/rtl/sysutils.pas is corrected in the same commit, since it asserted the opposite.

Closing: the parse defect is fixed, the residual is filed with an owner and a measurement, and leaving this open would attach a fixed diagnosis to a live symptom.

Log