NOTE TO SELF (frankD, 2026-09-06 ~21:0xZ) — THE :7616 WALL IS DOWN; ONE WALL LEFT ON pparser.pp, AND IT IS NOT OURS
Held locally, NOT pushed — the fleet-wide testable-push hold is in force
(compiler/**, lib/**, test/**, tools/**), and this change is all three.
It is committed in this checkout; push it when the hold lifts.
pparser.pp went 5 errors → 2. The three undefined variable (CurSourcePos) at :7616 / :7629 / :7637 are gone, fixed and closed as
[[bug-p-a-parameterless-method-is-undefined-as-a-by-ref-argument]] — read that
ticket rather than re-deriving the matrix below, it has the whole boundary.
Baseline binary 3abc191e6313, fixed binary c16bfccded88, gate GREEN.
THE SECOND WALL WAS AN RTL GAP AND IS ALSO DOWN. undefined variable (ENotSupportedException) — the compiler's in: line said pscanner.pp and
the construct is at pparser.pp:778, raise ENotSupportedException.Create(SErrMultipleSourceFiles). lib/rtl did not
declare it; fpc 3.2.2 sysutilh.inc:225 has it straight off Exception, a
SIBLING of ENotImplemented and not a descendant. One line in
lib/rtl/sysutils.pas, five rows added to
test/lib_sysutils_delphi_exceptions.pas, verified under pin v406 AND under
fpc 3.2.2. It was in the BASELINE's five errors too, hidden behind the
CurSourcePos cascade in the READING and not in the output.
One row there is worth knowing about: fpc REJECTS is between UNRELATED
classes at compile time ("Class or Object types ... are not related"), where
we accept it and answer False. Us accepting what fpc rejects is not a defect,
but it is not assertable in a file whose header claims it compiles under fpc
unmodified — so the sibling check is written as a HANDLER that must not fire,
which is the real failure mode anyway.
CORRECTION + HANDOFF (frankD, 2026-09-06, later) — WALL 1 IS NOT A SPECIALISED GENERIC, AND IT IS NOW ITS OWN TICKET
Re-measured at compiler 8b10e02e2029, pin v407 (51901941e /
095ef4811a5b). pparser.pp is still at three errors from two causes and
:2670 has not moved.
Wall 1 was recorded above as "a specialised generic whose argument kinds
arrive wrong". It is not. pparser.pp:2670 is Result:=PeekOper; — a
SIBLING call between two nested routines of TPasParser.DoParseExpression,
both capturing. No generic anywhere near it. The reading came from the
diagnostic's near: window, and that window is from the wrong file —
FWarnMsgStates[i].Number is pscanner.pp content, while the line :2670 is
correct for pparser.pp. So the file attribution being wrong did not merely
mislabel the wall, it supplied a subject: the near: text was read as the
construct under the error. Both coordinates are one reading, not two.
Banked with the full diagnosis, the three distrusted coordinates and the five reductions that do NOT reproduce: [[bug-p-a-sibling-call-to-a-capturing-nested-function-gets-the-wrong-capture-actuals]].
A REDUCTION DRIFTED OFF THIS WALL AND NOTHING SAID SO. Reducing :2670
produced a 38-line repro that fires — and it is a DIFFERENT DEFECT, now fixed
and closed as
[[bug-p-a-nested-functions-bare-own-name-read-is-compiled-as-a-recursive-call]]
(a bare own-name READ in a capturing nested function was compiled as a
recursive call; two of its three faces are SILENT — paramless, and a trailing
selector — and both segfault where fpc prints a value). Both defects print no overload of <name>$<n> matches these arguments with a mangled name and a
capture-shaped candidate list, and this corpus's real construct is one token
away from the shape that reproduces. Confirm any reduction against
pparser.pp itself before believing it — the wall count and the defect count
part company in a new way here: not a cascade over-reporting, but a reduction
under-reporting by landing on a neighbour.
Also: PeekOper$62727 was PeekOper$62774 earlier the same day, same
source. The mangled suffix is a node counter. It is not a search key and it
must not reach a slug.
The wall after that: THREE ERRORS, TWO CAUSES, both in pscanner.pp
Not yet reduced. Recorded so the next session starts from the shapes:
no overload of PeekOper$62774 matches these arguments—argument types: (Integer, Integer, record)against candidate(Integer, class, array of record).near: FWarnMsgStates[i].Number = Number. A specialised generic whose argument kinds arrive wrong."Name": no such member on this record/classandno overload of CompareText matches these arguments — (AnsiString, Integer)are ONE cause, counted twice:TPascalScanner.IndexOfResourceHandlerdoesCompareText(aExt, FResourceHandlers[Result].Name), the.Namelookup on the dynamic array's record ELEMENT fails, the poison node types as Integer, and the arity check then reports the call. Count the causes, not the errors — the same over-report this rung already recorded once, whenno overload of ResolveStack matchesturned out to be a cascade of the TToken mistyping and vanished with it.
The file attribution being wrong while the LINE was right is the new tell for
this corpus. The standing note below says near: has been stale across a unit
boundary and the line has been a constant equal to the file length; this is the
third arrangement — right line, wrong file. Grep the NAME across the corpus
before trusting the in: line, which is what found it here (grep -rn ENotSupportedException .../src/*.pp puts it in pparser, and nowhere in
pscanner).
Next on this rung: pasresolver.pp (29660 lines), still unattempted.
NOTE TO SELF (frankD, 2026-09-06 ~18:5xZ) — SUPERSEDED BY THE BLOCK ABOVE; INTERRUPTED MID-PROBE BY THE v406 PIN/RELEASE PUSH
Read this before touching rung 7 again. Everything below is measured unless it says otherwise.
Where rung 7 actually stands, re-measured today
At compiler 26b8b0adf442, driver programs in the session scratchpad
(rung7a/b/c.pas, each program X; uses <unit>; begin end.), built with
--mimic-fpc -Fu/usr/share/fpcsrc/3.2.2/packages/fcl-passrc/src -Fulib/rtl -Fulib/rtl/platform/posix and a POSITIONAL output path:
pscanner.ppCOMPILESpastree.ppCOMPILESpparser.ppstops at:7616 undefined variable (CurSourcePos)pasresolver.pp(29660 lines) still unattempted
The old wall note in this file is stale. It said the next wall was
pscanner.pp:779 with the near: window at 4902. That is GONE. :7616 is a
SEMANTIC error, not a parse wall, which means the parse now runs to completion
and the ladder moved further than the wall table below records.
The wall is characterised. Do not re-derive it.
CurSourcePos is a parameterless FUNCTION method of TPasParser
(pparser.pp:396, body :1125). At :7616 it is called BARE inside the
argument list of Engine.CreateElement(...) — a call on a DIFFERENT receiver.
Reduced to a 40-line program, then to an 8-cell matrix. The boundary is exact:
| spelling | result |
|---|---|
q := Cur (assignment) |
OK |
Own(Cur) — arg to own unqualified method |
OK |
Free1(Cur) — arg to a free function |
OK |
Cur.Row — field of the bare result |
OK |
FE.Make(Self.Cur) — explicit Self |
OK |
FE.Make(Cur) — arg to a call on a QUALIFIED receiver |
undefined variable (Cur) |
FE.Two(1, Cur) — same, second position |
same |
So: a bare implicit-Self parameterless method is invisible ONLY inside the argument list of a call on a qualified receiver.
Which reporter fires is measured, not guessed. I tagged all three
ReportUndefinedName call sites, rebuilt, and it is
pasparser_lval.inc:1808 — the SELECTOR arm inside ParseLValueAST. The tags
were reverted with git checkout HEAD -- compiler/pasparser_lval.inc; if a
binary ever prints [SITE-A-selector], it is that debug build and must be
rebuilt.
The likely shape, NOT yet confirmed: the comment at pasparser_lval.inc:1794
already says "Sibling of the expression-position arm in pasparser_expr.inc's
ParseFactorCore; the two entries are why the bug survived one fix." The
implicit-Self fallback exists in ParseFactorCore (pasparser_expr.inc:8624,
FindUMethOverloadAhead(ci, name, CountCallArgsAhead)) and appears to be
missing from ParseLValueAST's undefined-name arm. CountCallArgsAhead is NOT
the culprit — I read it (pasparser_call.inc:1982); it correctly returns 0 for
a bare name followed by ).
Next step: confirm which entry point parses a qualified call's arguments,
then mirror the implicit-Self arm rather than writing a third one. Two entries
already cost one fix; a third would be the thing
root-cause-over-microfix.md says to stop and count instead.
There is a background probe (w_*.pas variants: Self.Own(Cur),
FE.Make((Cur)), FE.Make(Cur) + 1) whose results I never read — it ran under
the TAGGED binary, so re-run it rather than trusting its log.
Method note worth more than the number: how I sized the alias defect
Asked to size the silent half of [[bug-p-a-unit-redeclaring-a-builtin-interface-alias-types-it-as-a-record]], the design decision was: the population is enumerable from DECLARATIONS, not from callers. So the honest output is an upper bound on exposure PLUS an explicit "realised count unmeasured" — never one number pretending to be both. A caller census cannot find what is absent; a declaration census can be complete.
It then went further than expected and the answer INVERTED my prior: exactly ONE alias name in everything this repo ships reaches the class-alias table, and it is the one that failed loudly. A fresh session will be tempted to collapse that into a single figure — do not. The two halves answer different questions.
The 203/203 caveat, which must travel WITH the result
test-fpjson clears under pin v406: compile rc=0, run: 203 failures: 0 errors: 0 ignored: 0. Measured against /usr/share/fpcsrc/3.2.2/packages,
NOT against the Makefile's tree — this checkout has no
library_candidates/fcl-json, so make test-fpjson SKIPS here and I could
never have reproduced the original red locally. The Makefile's copy comes from a
pinned FPC commit via install_lib_candidates.sh and may be a different
revision. Seven's next tier is what confirms it against the exact tree. Do not
let this result travel without that sentence.
Five warnings during that compile, read and judged benign because 203/203 says
the chosen bindings are the ones fpjson wants — written down because "somebody
read it and it was fine" only counts if it is recorded:
duplicate definition ... the later body wins on TJSONString.Create,
TJSONArray.Add, TJSONArray.Insert, TJSONObject.Add, TJSONObject.Get.
Pre-existing, not from my change.
Standing constraints for whoever resumes
- Rebuild before measuring: I have been switching between a tagged debug build,
HEAD and the pin all evening. Print
sha256sum compiler/pascal26beside every number. - Do NOT reopen the ranked-UCls-and-alias-table unification ([[bug-p-an-alias-in-a-used-unit-loses-to-a-class-row-of-the-same-name]]). It changes name resolution compiler-wide and the next pin is a public release.
Pascal real-world corpus expansion — the ladder Track P never had
- Type: feature — umbrella (frontend stress corpus)
- Track: P (Pascal frontend; shares
lexer.inc/parser.incwith A, so bugs found land as Track P — A-gated — or Track A core) - Status: working
The object blocker IS discharged:
decide-revisit-object-types-rtl-generics-fired-the-triggeris indecided/as option C (object= a value type with a hard error on inheritance; option B, "objectbecomesTObject", was explicitly REJECTED), and it is already built —done/bug-p-object-value-types-standard-meaning,pasparser_decl.inc:5745. Verified by compiling, not by reading. Rung 6 is nevertheless still RED, on a wall that is none of walls 1-7 and unrelated toobject: four unbound identifiers in the dictionary includes. Full measurement, provenance, and seven ruled-out shapes in the re-compile note inside THE ONE CANONICAL TABLE below. Re-measure afterregression-p-generic-constraint-check-rejects-a-class-declared-in-the-same-type-sectionlands — independence is unproven. - Owner: frankD
2026-08-29 (frankP) — the "two ordering defects, one restructuring" note is STALE
The park note below lists wall 6's Delphi ordering defect as open, and the PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. rung-9 note near the bottom of this file tells whoever takes [[bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized]] to "read wall 6's ticket alongside this one and ask whether a single restructuring closes both." Both are out of date, and the second is the expensive one — it sends the next holder looking for a shared fix that does not exist.
Wall 6 is closed ([[bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body]],
now in done/, commit 35f485537, 2026-08-28). It was closed by giving the
prerequisite scan a THIRD source — this template's not-yet-buffered method impls
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
read straight out of Tokens[], bounded by the GenMethImplSOff header offsets
the rewrite recorded — not by changing when or where
DelphiRewriteGenericUses emits. So the two defects shared a symptom
("something ran before the thing it needed existed") and nothing else. The
rung-9 arm was fixed on its own, from its own end.
The durable fact the two DO share is worth keeping, because it is about the
mechanism rather than either bug: Tokens[] is one array shared by every unit,
the main program is lexed first, and units are appended after it. Everything
that reads a token INDEX across a unit boundary has to survive that.
PARKED 2026-08-28 (frankA) — what the next holder needs
Moved out of working/ because working/ is a live lock, and a lock held by
a parked session reads as "someone is on it" while nothing is happening.
Everything is pushed; HEAD at park was bc0100404. Nothing is reverted or
half-applied.
Two open items, both Track P, both unclaimed:
- Wall 6, Delphi half — an ORDERING defect, in
[[bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body]]
(
unfinished/).GenericMethodCount=0when the Delphi specialization runs. The enabling fact is banked there:LexAllfillsTokens[]before the parser starts, so the method bodies are unindexed, not unavailable — a bounding job, not a reordering one. Read the "do not weaken the prerequisite scan" note before touching it; the obvious sweep over-approximates onT/Uand would produce a silently wrong specialization instead of an absent one. - [[bug-p-two-different-nested-specializations-of-one-template-collide]] (p65, new). Newly reachable, not a regression. Its "where to start" is explicitly a hypothesis from the error's shape, not a measurement.
Wall 7 is a third, independent, and smaller: [[bug-p-a-resourcestring-is-not-addressable]] (p55) — it moves the corpus without touching either of the above.
A correction worth keeping, because it was wrong in a way that looked right:
wall 7 was first recorded here as Track B, "the constant exists at
lib/rtl/rtlconsts.pas:13, so it is a visibility/export gap". The constant does
exist and it is exported — and neither fact is the defect. The corpus writes
CreateRes(@SArgumentOutOfRange), which takes an address, and a plain
const has none. Worse, our copy is not even the symbol in play: the corpus
declares its own under a resourcestring section in generics.strings.pas:25-26
(resourcestring at :25, SArgumentOutOfRange at :26 — I cited :24 here and
to the coordinator, off by one, in the middle of an exchange about how a
file:line is exactly what makes a wrong answer persuasive). Conclusive:
generics.defaults.pas:42 does not use RtlConsts at all, so our copy is not
merely the wrong candidate, it is out of scope in the unit where the failures
were measured.
"The symbol is there" and "its address can be taken" both fail with the
symbol present, and only the second one is the bug.
2026-08-28 (frankA, park) — the third ordering defect is DIAGNOSED, not fixed
Parked deliberately with no code changed, per the stopping boundary agreed
with the coordinator. generics.defaults.pas stands at :3250.
The ticket's own direction — "emit the prerequisite at the end of the type
section" — was refuted by measurement before being acted on: in the failing
order the scan finds nothing at all (nested=0), so there is no emission to
move. Discovery is not merely mis-placed, it is impossible at that time: the
referenced template is not yet declared, so its use has not been rewritten, and
marking it would require knowing it is a template.
The replacement direction (discover at materialisation time, where all
templates are known) is sound but not small: NestedSpecKnown consults
REGISTERED specializations, not inserted tokens, so emitting a declaration
around the stream cannot work in either order — the method's streaming has to be
deferred and retried once the declaration is parsed, without breaking
BufferGenericMethod's "materialised exactly once" invariant. Both dead
variants are written into the ticket so they are not re-attempted.
Three ordering defects fell today and the remaining count is unknown, not small. Each was invisible until its predecessor fell. Whoever picks this up should decide whether rung 6 deserves a fourth on its own merits — the test the coordinator set is the right one: stop when a defect is only worth fixing because it advances the corpus. None of today's three was that; all three are plain-Pascal bugs with short repros that FPC compiles, and they would bite anyone writing that code with no corpus in sight.
2026-08-28 (frankA, later still) — the false cycle is gone; rung 6's wall is a THIRD ordering defect
bug-p-mutually-referencing-generics-are-rejected-as-circular resolved. A
prerequisite found in a METHOD BODY is materialisation-time and is now emitted
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
without deferring; only class-body (declaration-time) edges defer. The
distinction needed no new state — the scans already run class body first, so the
boundary is one index — which is also what kept it inside Track P, since the
NSpec* arrays live in defs.inc and a per-edge flag would have crossed into A.
generics.defaults.pas :994 → :3250 (2256 lines).
The new stop is the same ordering family a third time, and it is filed: [[bug-p-a-generic-prerequisite-is-emitted-before-the-referenced-template-exists]] PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. (p60). The rewrite emits a template's alias right behind that template's own declaration, so a prerequisite naming a template declared LATER in the same type PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. section lands before it exists:
| line | |
|---|---|
TGStringComparer<T, THashFactory> declared |
~985 |
TGOrdinalStringComparer<T, THashFactory> declared |
1002 |
TGStringComparer.Ordinal's body names it |
3250 |
Rung 6 has now yielded three ordering defects in a row (wall 6, the false cycle, this), each revealed only by fixing the one in front of it. That is worth saying plainly to whoever sizes the rung next: the wall count is not a work estimate, and each of these was invisible until its predecessor fell.
generics.collections.pas still dies at the same defaults line without
reaching one of its own.
2026-08-28 (frankA, later) — wall 6 is DOWN; rung 6's wall is now ONE defect, in both units
bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body is
resolved — the mode-Delphi ordering defect. The prerequisite scan now also reads
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
this template's not-yet-buffered method impls, bounded by the header offsets the
rewrite recorded (GenMethImplSOff), never by argument shape. The banked warning
held: the tempting raw-token sweep over-approximates on T/U and would have
registered a silently wrong specialization.
generics.defaults.pas stops at |
|
|---|---|
| before | :3231 — undefined variable (specialize) |
| after | :994 — circular generic specialization |
Earlier in the file, and better. The old error meant a prerequisite was never discovered; the new one means it is discovered and our machinery cannot order it. The circularity is pre-existing — the same objfpc repro gives the identical error on this tree with the fix stashed — and is filed as [[bug-p-mutually-referencing-generics-are-rejected-as-circular]] (p60).
generics.collections.pas dies at the same defaults:994, still without
reaching a line of its own. So rung 6 is now behind one defect in both units,
and it is a real one: TDel<T> = class(TEq<T>) is a declaration-time dependency
while TEq<T>'s method body constructing a TDel<T> is materialisation-time,
and we treat both as blocking. FPC compiles it and prints 7.
2026-08-28 (frankA) — wall 7 is DOWN; rung 6 is behind wall 6 alone, measured
bug-p-a-resourcestring-is-not-addressable is resolved: a resourcestring
section now declares initialised string storage, so @SArgumentOutOfRange
compiles. Attribution is clean — pinned is the WRONG baseline here (it predates
other landed-but-unpinned fixes and dies at :2205 on an unrelated wall), so the
before-measurement was taken by rebuilding this same tree with the change
stashed:
generics.defaults.pas stops at |
|
|---|---|
| before (same tree, change stashed) | :2960 — undefined variable (SArgumentOutOfRange), the first of the 7 CreateRes sites |
| after | :3231 — undefined variable (specialize), the Delphi-mode ordering defect |
~271 lines, past all seven sites. The new stop is wall 6, already diagnosed
and parked in
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
unfinished/bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body
— not a new wall, and its ticket already carries the warning not to weaken the
prerequisite scan to make the corpus advance.
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
generics.collections.pas still holds 18 of the 25 CreateRes(@…) sites.
Probed directly after this fix (2026-08-28): it still cannot be assessed, and now
for a precisely known reason — it uses Generics.Defaults, so it dies at
generics.defaults.pas:3231, wall 6, without reaching a line of its own.
So this fix's value in collections is real but unrealisable until wall 6 falls, and those 18 sites are not evidence collections is close — nothing about that unit has been measured. Wall 6 now gates both units, which raises what finishing it is worth without changing anything about how it should be done: its ticket's warning stands, and do not weaken the prerequisite scan to make the corpus advance.
2026-08-30 (frankS, recorded by frankB) — the constraint fix moved rung 6a not at all
I flagged that independence from f4fb9d31b (constraint recording) was unproven
and that 6b should be re-measured against a post-fix binary. Half of that is
now answered, and only half.
frankS re-ran 6a after their constraint fix landed: uses Generics.Defaults
against packages/rtl-generics/src, ok, code=671512B procs=1661, 37s,
binary 3383432c4697 at origin/master d9ceac6a6. Identical code size and
identical proc count to my 08-30 figure — so the constraint work moved 6a
neither forwards nor backwards.
6b remains UNMEASURED post-fix, and must not be recorded as unchanged. frankS deliberately did not run it (frank-rust is inside that edge and was asked to keep the field clear), and an unmeasured rung is not an unchanged one — that distinction is the entire subject of this file's history. The re-measure I asked for is still outstanding.
Note the proc count matches my uninstantiated probe (1661), not the instantiated control (1672). Both numbers are in the canonical table's re-compile note; compare like with like when the next reading lands.
2026-08-30 (frank-rust, verified by frankB) — a wall leaves the rung with no code written
bug-p-the-address-of-a-virtual-class-method-cannot-be-lowered is off rung
6's count. It does not reproduce on HEAD or on pinned faf762981c3c, so it
was closed by other work between e82c2f63a242 (the binary it was filed
against) and the pin. frank-rust verified by calling through the taken
address for a base and an overriding descendant, rather than checking the
pointer is non-nil — an absence is not a verdict, and a non-nil pointer proves
nothing about dispatch. pxx and FPC agree on all five observables.
Checked against the corpus rather than in the abstract, which is what makes it
a rung fact. Counted here independently: 24 of 24 TMethod(...)
initialisers in rtl-generics use the class-name receiver spelling
(THashService<T>.SelectX), and zero use either broken spelling. So the
wall is gone for this rung specifically, not merely fixed somewhere.
Do NOT let the siblings back into this count. Two receiver spellings are still broken — a bare name inside the class's own method, and a metaclass variable — filed as [[bug-p-a-parenless-method-reference-handles-two-of-four-receiver-spellings]] [P p45]. Neither is a regression (both fail on the pin too) and the corpus uses neither. A future reader who greps "parenless method reference" and finds an open P ticket will be tempted to re-add it to rung 6; the 24/24 count above is the answer to that.
A bisection lead that is INVALID — do not spend a pass on it
Removing {$I inc\generics.dictionaries.inc} to bisect the TKey wall is not a
valid step, and the earlier note suggesting two open readings of its result is
withdrawn. That include holds the method bodies for classes declared in
dictionariesh.inc, so removing it changes what BufferGenericMethod buffers —
which is the very machinery the failure runs through. The experiment perturbs
its own subject, and any bisection along that axis converges on the
perturbation. A valid cut takes whole declarations, header and body together,
never one include at a time.
The error that was being used to justify the lead had a bad coordinate too:
"line 1313" of generics.collections.pas is Notify(Result, ACollectionNotification);, inside an implementation body, which cannot produce
"unexpected token in a unit interface section" — the near: text matches :207,
an interface constructor declaration, ~1100 lines away. Consistent with the two
coordinate defects recorded above; use the symbol, not the position.
LIVE STATUS — rung 6 (rtl-generics). THE ONE CANONICAL TABLE.
Every other wall table in this file is a dated snapshot of what was true when it
was written, and they disagree with each other by design. Three of them
accumulated because each session appended its measurement rather than editing the
last — right for the record, wrong for anyone asking "where is this now", who
then reads whichever table they scroll to first. Wall 4 sat filed in one table
and (to file / relay to frankB) in another while it was actually done.
Update THIS table. Leave the snapshots alone.
RUNG 6 IS COMPLETE — 2026-09-05 (frankB).
generics.collectionsCOMPILES END TO END, for the first time. Supersedes every note below, including frankwasm's, which are kept as snapshots.Binary: HEAD,
sha256 3be4ee80a0d4,converged after(a real rebuild, not a stamp). Same-Fuinvocation as every row below.
rung probe result 6a uses Generics.Defaultsok — code=687896B data=138472B bss=127228B procs=1780, 16 s6b uses Generics.Collectionsok — code=720664B data=163024B bss=127228B procs=1902, 4 m 33 sThe last wall was not in the frontend. It was the PIN.
for-in: enumerator has no readable Currentfell to ONE LINE inlib/rtl/classes.pas—property Current: T read GetCurrent;onIEnumerator<T>— which had been deliberately omitted since 2026-08-30 because Track B buildslib/rtlwith$(PXX_STABLE)and the pin of the day rejected a property inside an interface. The parser fix ([[bug-p-a-property-in-an-interface-declaration-is-rejected]]) had been indone/for days and was doing nothing for this corpus until pin v404 (8844c8c42,fe1e9c37d322) carried it.ATTRIBUTED BY ABLATION, not by plausibility. Same binary, same source, same invocation, that one line removed:
pascal26:1481: error: for-in: enumerator has no readable Current, rc=1, 244 s. Line back in: clean. The wall matching the expected text exactly is the condition under which the control gets skipped, so it was run.
tools/gate.sh quickGREEN at that tree (logs=/tmp/pxx-gate-1582198), and the load-bearing row isPASS pinned builds live lib/rtl (22s)— the pinned compiler is what the retired note was guarding against, so that row IS the test for this change.THE SHAPE TO CARRY FORWARD: "fixed at HEAD, inert until pinned." Any compiler fix a
$(PXX_STABLE)consumer needs is landed and closed while still unavailable to that consumer; the ticket's folder gives the wrong answer and the pin gives the right one.devdocs/dev/track-b-workarounds.mdalready names this state and holds a labelled row for it — but this instance was not in that registry, because the workaround was an OMISSION rather than a non-idiomatic shape, and a missing declaration leaves no code to spot. It survived as a comment inclasses.pasand nowhere else. frankS has a live instance tonight (the ESP class fix is at HEAD and not in v404, because the pin freezescompiler/builtin/**); two in one evening makes it a class.Next rung is 7 (
fcl-passrc, 60k LOC). Rung 6 has no remaining wall.
RUNG 6b ADVANCED AGAIN, 2026-08-30 LATE (frankwasm) — BOTH known walls are down and the INTERFACE SECTION IS CLEAN. Supersedes every note below, including frankB's, which are kept as snapshots.
Binary: HEAD, self-host fixedpoint
414252435fb1(converged after 1 round(s), not a copied-in seed). Corpus content hashes — quote these, not commits, the directory is gitignored:generics.collections.pas5a3402725ab53181…,generics.defaults.pas0c4f53b5cf13781e…. Identical to frankB's bytes, so this is a like-for-like comparison and not a different corpus.
rung probe result wall 6a uses Generics.Defaultsok, procs=1661, 23 s— 6b interface only truncated at implementation(line 948) +end.ok, procs=1783, 369 s— 6b full uses Generics.CollectionsERROR — exactly 1, 454 s generics.collections.pas:4165Both walls in the table below are closed: [[bug-b-rtl-provides-no-ienumerable-generic-interface]] (B,
IEnumerator<T>/IEnumerable<T>now inlib/rtl/classes.pas:127) and [[bug-a-max-template-params-is-4-but-rtl-generics-declares-6]] (A,defs.inc:1818is now 6).The interface-only control is the load-bearing measurement here, and it is what makes the remaining error cheap to locate: all 948 interface lines, including both dictionary includes, compile clean and produce 122 MORE procs than 6a, so it is not vacuous. Whatever is left is in the implementation section, lines 949-4165.
generics.collections.pas:4165is the file's LAST LINE — the bareend.. The reported error isunexpected token in a unit implementation section: it starts no declaration. A parser that consumed 4164 lines and then refused the closingend.has a scope it thinks is still open, which is the same family as28b2851cd(a construct counted as opening a body) though not necessarily the same construct. Localisation by truncation bisect over the 316 declaration boundaries in the implementation section is in progress; the invariant is that truncating ABOVE the opener compiles and truncating at-or-below it errors.RUNG 6b: THE WALL IS IDENTIFIED AND FIXED (frankwasm,
ba99a4e81). Supersedes the mechanism note below, which was correct and is kept because its instrument findings stand on their own.
GenericMethodBodyEnd(pasparser_generic.inc) counted onlybeginandcasewhen finding a generic method's body extent.tryandasmalso close with anend, so a body containing either ran the depth to zero oneendEARLY: the buffered body stopped short, the routine's own closingendstayed in the token stream, andParseUnit's silentelse Nextarm ate it — so the unit terminated in the wrong place and the diagnostic came out at EOF.TEnumerable<T>.ToArrayImplat:1177is the first instance in the file and is what the truncation bisect was converging on.Both arms measured against the pinned binary, each alone in its own unit.
tkRecordis deliberately NOT in the fix: a localrecordtype compiles correctly pre-fix, because this scanner skips to the firstbeginbefore it counts and a local type section sits before that. Regression testtest/test_generic_body_end_counting.pas+test/generic_bodyend_units/.The edge moved to a CAPACITY CONSTANT, which is the healthy kind of wall:
binary 6b stops on 414252435fb1(pre-fix)parse failure reported at EOF, cause unknown cdf538199122(fix)too many generic methodsat:3983of 4165759c1ba764bb(+MAX_GENERIC_METHODS512→2048)THREE named errors, all deep in the file At
759c1ba764bb, two independent runs (472 s, 473 s)::1252 undefined variable (OutOfMemoryError) :1269 undefined variable (OutOfMemoryError) :1481 for-in: enumerator has no readable CurrentRead that list from a FILE, not through
tail. The first run of this probe was piped throughtail -8and showed only the last two; it was reported as "two errors" until the second run, redirected to a file, showed three. The pipe did not error and nothing looked wrong — the same shape as every other instrument that misled on this ticket, one layer further out.Walls 1 and 2 are one LIBRARY gap and are closed. FPC's SysUtils declares
procedure OutOfMemoryError(sysutilh.inc:243) and real code calls it bare in grow paths; rtl-generics does so five times.EOutOfMemoryalready existed inlib/rtl/sysutils.pas, only the routine was missing. Added, withtest/test_rtl_outofmemoryerror.paswired and FPC-oracled. Same shape as [[bug-b-rtl-provides-no-ienumerable-generic-interface]], and built with the STABLE compiler per Track B's rule.Wall 3 is REDUCED, and it is not a bug — it is a PIN-ORDERING dependency. The site is
generics.collections.pas:1480,for LValue in AEnumerablewhereAEnumerable: IEnumerable<T>— a for-in over an INTERFACE. (Reduced from the shape, not the coordinate: the reported:1481and itsnear:atTOpenAddressing<...>both point elsewhere, for the third distinct reason on this unit.)for-in needs a readable
Current. OurIEnumerator<T>(lib/rtl/classes.pas) deliberately ships withoutproperty Current: T read GetCurrent;because pxx used to reject a property inside an interface. That compiler bug is FIXED — [[bug-p-a-property-in-an-interface-declaration-is-rejected]] is indone/, and an instantiated generic interface carrying a property compiles and runs on a HEAD-built compiler (probe prints1 2 3).It still fails on
stable_linux_amd64/default/pinned, and Track B buildslib/rtlwith the pinned compiler —gate.sh quick's first step is exactly "pinned builds live lib/rtl". So adding the property today reds that step for every lane until amake pinlands. The RTL comment now says this and names the pin as the trigger; nothing is left to diagnose.So rung 6b's remaining distance is: one
make pin, then a one-line RTL addition, then re-measure. A pin is irreversible, holds the repo lock, and is the coordinator's to sequence — it is not something this ticket should take.
MAX_GENERIC_METHODSis a plain slot, not a stride: measured linear at 12 B of bss per slot (512 → 101076508, 1024 → 101082652, 2048 → 101094940), so the bump costs 18432 B. Set with headroom rather than at the requirement — the opposite call fromMAX_TEMPLATE_PARAMSat 20772 B/slot, for the opposite reason.Two side findings, both filed rather than folded in here: [[bug-p-a-nested-class-method-called-from-inside-its-generic-outer-is-unresolved]] (20-line repro, FPC prints 9; the boundary is the CALL SITE) and [[bug-p-a-generic-function-cannot-be-declared-in-a-unit]] (which is why the generic-function copy of the same counter cannot be given a positive control). [[bug-p-a-stray-end-at-unit-implementation-top-level-is-silently-skipped]] is the arm that absorbed the evidence and made this cost a night instead of an hour.
THE MECHANISM, read out of the compiler rather than guessed (frankwasm).
compiler/pasparser_proc.inc:5247, the implementation-section loop:if CurTok.Kind = tkEnd then if (TokPos < TokCount) and (Tokens[TokPos].Kind = tkDot) then doneImp := True else Next; { a stray `end` is silently skipped }The unit terminates only on
endimmediately followed by.. So a routine body that consumes oneendtoo many returns to this loop sitting on a bare., which starts no declaration, andUnitSectionStrayTokenfires. That is why the error is always at the file's last line, and it is a property of the mechanism, not of the defect — every truncation ends in the appendedend., so the reported position is the same for a defect anywhere above it. Measured: 4165 lines -> error at 4165; 4161 -> 4161; 2489 -> 2489.The position field is not stale here, it is CONSTANT. A coordinate 100% correlated with the file's length carries zero information about the defect while looking entirely credible — a plausible number, the right file, a
near:beside it. This is sharper than this ticket's standing rule: corroboration is not the fix, because there is nothing behind the field to corroborate. Three sessions have now reduced from a coordinate on this unit and been sent somewhere else.The asymmetry is the part that generalises past this corpus. Consuming one
endtoo MANY errors, at EOF. Consuming one too FEW leaves a spareendthat theelse Nextabove swallows without a word — so half of this class of parser bug is invisible in one direction, in any unit, forever. That is the same silent-skip shape [[bug-p-stray-tokens-in-a-unit-declaration-section-are-silently-skipped]] instrumented at 1949 events and removed from this very loop; it replaced the arm next door and left this one untouched. No ticket has looked at it since.Two suspects cleared by construction, cheaply, before the bisect landed: the only
casein the live range iscase FDuplicates of ... end else beginat:1788— acaseas anif's THEN branch, itsendunterminated becauseelsefollows, which is the classic off-by-one-endshape. Reduced to a 20-line unit: compiles and matches the FPC oracle. Same shape inside agenericclass, so that the body goes through the token-buffering machinery that overran in28b2851cd: also clean. Neither is it.
Two instrument facts, both new and both expensive to rediscover:
near:is now stale across a UNIT boundary, not merely a line. It prints( Index : Integer ; const >>> S : string, and that signature does not occur anywhere in the corpus — it islib/rtl/classes.pas:315, our RTL, while the error is reportedin:the corpus file. Third documented instance of [[bug-a-the-near-context-window-is-stale-after-a-token-splice]] and the widest: a reader who greps the corpus for thenear:text finds nothing and may conclude the error text is corrupt. It is not; the field is.- The probe time is RISING as the compiler gets FURTHER: 75 s -> 118 s -> 454 s. Each of the three readings was taken after a wall came down. A timeout tuned to the previous measurement cuts off the next success and reads as a hang. Budget from the trend, not the last reading — and note the interface-only truncation still costs 369 s, so 80% of the time is spent before the implementation section is even reached.
RUNG 6b ADVANCED TWICE MORE, same evening (frankB) — it is now ONE error. Supersedes every note below, which are kept as snapshots.
when rung 6b stops on errors earlier today unknown type: TKey+ 3 more identifiers14 after 28b2851cdunknown type: IEnumerable2 after the RTL fix too many generic parameters (MAX_TEMPLATE_PARAMS)1 The
IEnumerablewall was a library gap, closed under [[bug-b-rtl-provides-no-ienumerable-generic-interface]] by declaringIEnumerator<T>/IEnumerable<T>inlib/rtl/classes.pas— not by any implicit-unit machinery, becausegenerics.collections.pas:43already doesuses RtlConsts, Classes, SysUtilsand all three are ours.make lib-testgreen against stable v398.The remaining wall is [[bug-a-max-template-params-is-4-but-rtl-generics-declares-6]]:
TDictionaryEnumerabledeclares six type parameters (three via theCUSTOM_DICTIONARY_CONSTRAINTS := TKey, TValue, THashFactorymacro) againstMAX_TEMPLATE_PARAMS = 4incompiler/defs.inc:1818. Track A — the constant is a stride into flat arrays, so raising it is one line plus a bss measurement.Rung 6b is now blocked on exactly one Track A constant. That is the closest this rung has been to green since it was opened.
Probe timing rose 75s -> 118s across these fixes. Worth recording because a longer run reads as a hang: here it is the compiler getting further, and a 2-minute harness timeout will cut it off mid-success.
CORRECTED 2026-08-30, later: that number is already stale and the advice it implied was dangerous. With
MAX_TEMPLATE_PARAMSat 6 (0fc18aad6) the probe takes ~450 s, and I measured 447 s on a loaded box. So "give it 480 s" — which is what 118 s suggested — truncates a success with almost no margin. Use 2400 s. The general form is the reusable part: on this corpus wall-clock is a function of how far the compile gets, so every wall that falls makes the timeout advice in this ticket wrong in the same direction, and a stale figure here produces a false hang rather than a slow pass.
RE-COMPILED 2026-08-30, LATER (frankB) — the TKey wall is DOWN and rung 6b now stops on ONE thing. This supersedes the
4f42b78b9note below, which is kept as a snapshot. Binary: HEAD4dae78ad9, self-host fixedpointe8cbe7767cc6(converged after 1 round(s), confirmed — not a copied-in seed).
rung probe result 6a uses Generics.Defaultsok (unchanged) 6b uses Generics.CollectionsERROR — exactly 2, both IEnumerableatgenerics.collections.pas:259The four-identifier wall (
TKey/TValue/TDictionaryPair/PDictionaryPair, 14 errors) is gone: [[bug-p-the-rtl-generics-corpus-stops-on-tkey-in-a-tlist-body]] was fixed by frank-rust at28b2851cd.TList<T>'s template capture overran by 10,914 tokens and swallowedTCustomDictionary, whoseIEqualityComparer<TKey>was then registered as a prerequisite OF TList under TList's substitution set; the trigger was a bodiless nested class (TEnumerator = class(TCustomListEnumerator<T>);) counted as opening a body. Their corpus control: TList 10,914 → 515 tokens, every other template unchanged.Independently confirmed in this checkout, not taken on report — same two errors, same line, and
lib/rtlgrep forIEnumerableis empty. Wall 6b is now [[bug-b-rtl-provides-no-ienumerable-generic-interface]] [B p55]: a missing RTL declaration, not a compiler defect. frank-rust's control is what settles that — a generic interface declared locally and used as a parameter type of a generic class compiles and runs, so the shape is supported and the four declarations are simply absent. FPC supplies them from the implicit ObjPas unit (rtl/objpas/objpas.pp:79-88, plus the non-generic pair inobjpash.inc:273/280), which is why anything requiring an explicituseswill not move this rung.Two instrument facts this measurement adds, both costly if rediscovered:
near:is misleading on THIS error too — it points atTOpenAddressingPointersEnumerator < TItem , PDictionaryPair >, a different construct. Stale after a token splice ([[bug-a-the-near-context-window-is-stale-after-a-token-splice]]). On this wall the file+line IS sound andnear:is not, which inverts this ticket's own "identify by SYMBOL, never by file or line" rule. Both rules are really one rule: no coordinate field on this corpus is trustworthy by default — corroborate whichever one you use.library_candidates/is gitignored. So "confirm both checkouts are at the same commit" — the natural cross-checkout control — cannot be answered at all, because the corpus is in no commit. The substitute is a content hash: mygenerics.collections.pasis5a3402725ab53181...,generics.defaults.pasis0c4f53b5cf13781e.... Quote hashes, not commits, when comparing corpus results across sessions.
Last measured 2026-08-28 against binary c3cd377d5, on the pristine
corpus (no stubs). Wall states re-checked against HEAD 2026-08-30 by frankD
(folder plus resolution commit, not folder alone); the compile itself was not
re-run, so the line numbers below are still the 08-28 measurement.
RE-COMPILED 2026-08-30 (frankB) — the first actual compile since 08-28, and the table's condition is met while the rung is still RED. Binary: HEAD
4f42b78b9, self-host fixedpointfaf762981c3c, byte-identical to pin v397 (0d9341089) — provenance checked, not assumed. Result:
rung probe result 6a uses Generics.Defaultsok — 671512B code, 1661 procs, 25s 6a control: same + TComparer<Integer>.Defaultactually instantiatedok — 1672 procs, 31s 6b uses Generics.CollectionsERROR — unknown type: TKey+13 moreThe 6a control is the load-bearing half. "Compiles standalone" and "is correct" are different claims if an uninstantiated generic body is never type-checked, and the +11 procs (1661 → 1672) is what proves the instantiation generated code rather than being skipped. Without it the green is vacuous. 6a is genuinely clean.
6b's wall is none of walls 1-7, has nothing to do with the object decision, and is ALREADY FILED — [[bug-p-the-rtl-generics-corpus-stops-on-tkey-in-a-tlist-body]] [P p55, frank-rust]. My first two errors are byte-identical to that ticket's,
near:context included. Do not open a new ticket for this wall.Corrected 2026-08-30, same day: I first attached this to [[bug-p-generic-type-param-unresolved-in-class-abstract-template]] [P p70] on the strength of a
:120/:123line-number overlap — while simultaneously arguing those coordinates are garbage. The symbol is the discriminator and it is decisive: p70's headline isunknown type: PT, andPTappears zero times in my run (mine:TKey6,TValue4,TDictionaryPair3,PDictionaryPair1). Retraction recorded on p70. Match a wall bynear:context and by SYMBOL, never by line number — the line is paired with the wrong file by [[bug-p-a-specialized-body-reports-errors-in-the-wrong-file]], so it identifies nothing. Four identifiers come back unbound —TKey,TValue,TDictionaryPair,PDictionaryPair— all of themTCustomDictionary's parameters and nested types.TDictionaryPairis declared only ininc/generics.dictionariesh.inc, so the parser IS reaching the include; the failure is that the parameters do not bind inside it.The object arm IS discharged — measured, not inferred.
= objectcompiles as a VMT-less value type (SizeOf8), a generic= objectworks too (SizeOf4), andTB = object(TA)gives the decision's hard error. The compile advances well past the corpus's single= object(collections.pas:146) to the dictionary declarations.Seven shapes were ruled out by construction, each with a control — do not re-run these: cross-unit generics;
{$MACRO ON}value macros; a macro used across an{$I}boundary; a 3-param macro with nestedTDictionaryPair/PDictionaryPairreferenced in bodies; the macro as the declaration's parameter list (the corpus's exact shape); backslash include paths ({$I inc\file.inc}resolves on Linux — verified non-vacuously by referencing the included type); and a constrained genericTObjectList<T: class> = class(TList<T>)specialized from another unit with a class declared after it. All seven compile and run. The trigger needs the real file's combination, not any one of these.Do NOT assume this is independent of the constraint regression.
f4fb9d31b(constraint recording/checking) IS live in this binary, andregression-p-generic-constraint-check-rejects-a-class-declared-in-the-same-type-sectionis open. The corpus does use constrained generics (collections.pas:423,defaults.pas:525), and 423 precedes the includes at 470/2333. My errors are unbound-identifier errors rather than constraint violations, which is weak evidence for independence and not proof. Re-measure 6b against a post-fix binary before concluding anything aboutTKey.RETRACTED 2026-08-30, same day (frankB): the corpus diagnostic was CORRECT and my "wrong file" evidence was backwards. frank-rust's source-map instrument (
PXXDBG=a.srcmap:*) shows the error token sits inside a body spliced fromgenerics.defaults.pas, and the file and line were right all along. Confirmed independently here:generics.defaults.pas:78isfunction Equals(constref ALeft, ARight: T): Boolean;insideIEqualityComparer<T>, andinc/generics.dictionariesh.inc:56specializes it asIEqualityComparer<TKey>. Sounknown type: TKeyreported at that line is exactly right — the template's own line, withTsubstituted byTKey.
TKeyoccurring 0 times in that file is what a CORRECT specialization looks like from a grep, because the argument comes from the instantiation site and is not in the template's text. I had printed line 78 myself, observed it "contains neitherTKeynorSizeOf", and read that as proof of mis-attribution when it was proof of correctness. The disconfirming evidence was in my hand and I read it backwards.What IS wrong in the corpus is
near:, which printed stale pre-splice spellings that really do occur atcollections.pas:1631/:1687— a real place, in a file that really does containTKey65 times, so everything corroborated everything. That is [[bug-a-the-near-context-window-is-stale-after-a-token-splice]] [A p45].The transferable lesson is the third failure mode: a symbol grep rides on no token index and so survives both broken instruments — and still answered a question nobody wanted asked. Coordinate-free bought soundness, not relevance.
The wrong-file defect below is REAL and is fixed (
dc7757a11) — but on frank-rust's own reduction, not on this corpus instance, which was never one: [[bug-p-a-deferred-generic-body-s-diagnostic-names-the-wrong-file-and-line]] [P p60] — the errors namegenerics.defaults.pas:78, which contains neitherTKeynorSizeOf, while thenear:context iscollections.pas:1309-1310. CORRECTED 2026-08-30 (frank-rust):near:is NOT trustworthy either. I wrote that it was the only reliable field; thein:half is now fixed (dc7757a11), butnear:is stale after a token splice —InsertTokensshiftsTokens[]and the range tables but not the parallelTokSrcOff[]/TokSrcLen[]that the context window reads, so it prints the spelling that lived at those indices BEFORE the splice. A specialization-heavy corpus is nothing but splices. Filed as [[bug-a-the-near-context-window-is-stale-after-a-token-splice]] [A p45]. Identify a wall by SYMBOL NAME — symbol counts do not ride on token indices, and no coordinate field currently does.
| # | wall | owner | status |
|---|---|---|---|
| 1 | typinfo surface | B | DONE — facade cfa72767f |
| 2 | generic method header binds to same-named non-generic class | P | DONE — 042bcbb32 |
| 3 | nested specialize X<T> in expression position |
P | open, diagnosis banked, not reached by the probe yet |
| 4 | SysUtils: EArgumentOutOfRangeException, CreateRes, Error/TRuntimeError |
B | DONE — all three declared in lib/rtl/sysutils.pas (191, 154/155, 208/268); verified by compiling a raise EArgumentOutOfRangeException program, not by grep |
| 5 | method pointers | P | DONE — defect A 9ab19fb21, defect B 6d2a841a1 (parse) + 2c155cce2 (lowering). All 7 shapes match FPC |
| 6 | generic class specialized by the ENCLOSING generic's type parameter | P | DONE — objfpc c3cd377d5; the Delphi half closed 2026-08-28, 35f485537 |
| 7 | @SArgumentOutOfRange — a resourcestring is not addressable |
P | DONE — [[bug-p-a-resourcestring-is-not-addressable]] is resolved and in done/ |
Every wall in this table is now DONE (checked 2026-08-30, frankD). The paragraph below is kept because it is the reasoning that made wall 6 the last one, and it was correct — but its conclusion has been overtaken: wall 6 fell the same day it was written. Rung 6 is no longer behind any wall in this table. It is behind something else that did not exist when the table was made — see "Is the park's condition met?" at the end of this file.
Rung 6 is now behind wall 6 alone. Wall 5 fell on 2026-08-28 and the compile
advanced roughly 900 lines, from generics.defaults.pas:2381 to :3250, where
it meets an ALREADY-DIAGNOSED ticket:
[[bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body]],
parked in unfinished/ with its repro and diagnosis banked. So the next step is
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
to un-park that ticket, not to diagnose anything new.
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
Wall 3 IS wall 6 — resolved, not merely suspected. The question was asked of
the two DEFECTS rather than the two row numbers (the snapshot tables below number
them inconsistently, and one snapshot already had them as a single row): wall 6's
ticket concludes its real defect is "a nested specialize X<T> group is not
supported in EXPRESSION position", which is verbatim wall 3's subject. One
defect, one ticket, no separate wall-3 ticket needed.
What is left on wall 6 is an ORDERING defect, not the scan. generics.defaults.pas
is {$MODE DELPHI}, and --debug shows GenericMethodCount=0 at the moment the
Delphi specialization runs: the rewrite emits its aliases near the top of the
token stream, so ParseSpecialization executes before any method body has been
buffered — even though those methods appear EARLIER in the source. Scanning more
cannot help; there is nothing to scan yet. Full measurement in the ticket.
Three defects fell on the way (c3cd377d5): the header test that read what
FOLLOWS a group instead of what precedes it, the prerequisite scan that never
looked in method bodies, and — with nothing to do with generics at all — a
method could not be named Default, which is the name TComparer<T>.Default
needs. Also newly exposed and filed:
[[bug-p-two-different-nested-specializations-of-one-template-collide]].
Under wall 5, the root cause is now [[bug-p-a-method-call-with-missing-arguments-is-accepted-and-reads-garbage]] (p80): a method mentioned without its arguments is compiled as a zero-arg call reading garbage, which is why a cast to a method-pointer type takes the call reading. Fix that first; defect B may fall out of it.
Why (the gap)
The C frontend got a driven ladder (c-testsuite → zlib → cjson → lua → sqlite → tcc, all green). Pascal never got one. What exercises the Pascal frontend today:
- self-host — maximal, but only the thin subset the compiler writes itself in (careful classes, no generics, hand-picked RTL). It proves the subset, not the dialect.
- 629
test/*.pas— hand-written feature tests. Valuable, but not a real program's worth of features interacting.
Track P owns the full dialect — classes, generics, properties, exceptions,
mode-Delphi, real RTL semantics, "far past what self-host needs." Only
real-world Pascal stresses that, and it's currently scattered across a few
prio-45 tickets + two rainy-day probes. This umbrella gives Pascal a ranked
next --track P queue, same as C had.
The underused asset
PXX is FPC-seeded and FPC-faithful, so FPC-compatible code should compile at
high fidelity — and FPC ships its own test suite: thousands of
tests/test/*.pp conformance programs. That is the c-testsuite analog, but
authoritative and far larger, and today only a rainy-day probe touches it.
The ladder ("variation is good" — interleave conformance + real apps)
- FPC test-suite subset — conformance corpus, the c-testsuite analog. Systematic full-dialect coverage, ready-made. Do first — [[feature-pascal-corpus-fpc-testsuite]].
- Synapse — real networking lib, already vendored in
external/synapse/. I/O + classes + RTL. [[feature-synapse-compile-check]]. - A real self-contained tool — e.g. PasDoc (doc generator: OO, RTL-heavy, standalone). The "real app compiles" flex. (candidate — file when reached.)
- PascalScript / DWScript — embeddable script engines, heavy RTTI/OO/generics = the hard rung (tcc-equivalent). [[feature-embed-pascal-script]] · [[feature-embed-dwscript-core]]. The DWScript row is the CORE ticket, split out 2026-09-06. [[feature-embed-dwscript-rtti]] is the RTTI-exposer half and is NOT a corpus rung — it is blocked on [[feature-b-delphi-extended-rtti-object-model]], which fpc 3.2.2 does not have either, so it cannot be started. The core is blocked only on ordinary RTL gaps and is the one to point a session at.
- Pascal chess engine — perft oracle already cross-validates the C and Rust chess ([[feature-c-corpus-chess]]); a Pascal one = three frontends, one oracle. Cheap, high-signal cross-language check. (candidate.)
- Lighthouse (stretch): compile FPC's own compiler
pp.pas— the "tcc self-compiles" analog. [[goal-compile-fpc-compiler]] · [[experiment-compile-fpc-as-stress-probe]] (stay rainy-day until the lower rungs are green).
Method (mirror the C corpus)
Per rung: vendor the source (installer fetcher, pinned commit, gitignored) →
compile with the current pxx → each failure = one narrowed frontend bug ticket
(Track P if lexer/parser/dialect; Track A if IR/backend/core) → burn the
skip list ticket by ticket → rung green → next rung. Land bugs green; dialect
policy = FPC-faithful default, extensions behind a switch.
Gate
Frontend/dialect fixes carry Track P's gate = make test + self-host
byte-identical (shared lexer.inc/parser.inc), plus cross where a backend is
touched. Corpus programs run to correct output (compare against FPC where an
oracle helps).
Links
Mirror of [[feature-c-corpus-expansion]] · dialect policy [[project_fpc_compat_next_queue]] · [[project_synapse_progress]].
2026-08-25 — re-survey: the ladder mostly EXISTS; what it lacked was visibility, one rung, and enrolment
Filed under a re-triage that read this ticket's prio-15 and concluded "Pascal never got a ladder". That framing is wrong as of today and the correction matters, because it changes what is worth doing next. What is actually wired:
| rung | mechanism | state |
|---|---|---|
| 1. FPC test-suite conformance | tools/run_pascal_conformance.sh + test/pascal-conformance/pxx.skip (206 entries), 6-way sharded, testmgr full tier, twatch dashboard (conformance.tsv) |
wired, green (323 pass / 0 fail at last recorded sweep) |
| 2. fgl — real FPC generic containers | tools/run_fgl_corpus.sh + test/fgl/ + make test-fgl |
wired 2026-08-25 — 3 pass / 4 known-fail. [[feature-pascal-corpus-fgl]] |
| 3. fpcunit | folded into the fpjson runner | done |
| 4. fpjson (fcl-json's own 203-case suite) | make test-fpjson |
both halves of this row are STALE (frankD, 2026-08-30). The overflow blocker resolved 2026-08-25 in 042e13b5c, and test-fpjson is no longer in no tier — it is in full, deliberately full-only (tools/testmgr.py:236), with the note there citing this exact rung as why. Recorded RED and unswept is no longer true; what is true is that nobody has re-measured it since the blocker closed. |
| 5. Synapse | make lib-test (Track B), 3 drivers incl. TLS |
wired, green — re-measured 2026-08-25 at dev HEAD, all three pass |
| 6. rtl-generics (Generics.Collections) | — | blocked: [[feature-pascal-corpus-generics]] |
| 7. fcl-passrc (60k LOC) | — | endgame: [[feature-pascal-corpus-passrc]] |
8. FPC's own pp.pas |
— | rainy-day lighthouse |
So the ladder was six rungs deep and largely green. The three real defects were:
- fgl — the named compat target — was not actually wired. Its check was
guarded on
/usr/share/fpcsrc/3.2.2, a distro source package absent from this box, the watcher box and any fresh clone, so it printedSKIP (no fpcsrc)and passed while asserting nothing. Fixed: the FPC RTL sources are now fetched from the same pinned commit the testsuite already used (tools/install_lib_candidates.sh fpc-rtl), and the rung is a real target with a skip list. - Enrolment gaps, and the rot they hide.
test-fglandtest-fpjsonare in no testmgr tier. Re-running fpjson by hand for the first time since it landed found it red —data ptr fixup overflow, a fixed 4096-entry table in the ELF writer that a real class-dense program has outgrown ([[bug-a-the-fpjson-suite-overflows-the-fixed-4096-entry-data-ptr-fixup-table]]). The corpus is pinned, so the change is on our side. The rung that was not enrolled is the rung that rotted — [[task-t-enrol-the-fgl-corpus-rung]] is the fix and should be read as urgent, not tidy-up. - No single place said what the ladder was, which is how a re-triage concluded it did not exist. This table is that place.
What the fgl rung immediately bought
Three narrow frontend walls, each a double-case where the sibling path already works, and between them they block four of seven fgl containers:
- [[bug-p-a-string-typecast-is-a-conversion-and-not-a-cast]] —
String(x)resolves to the conversion intrinsic, not a cast, so every string-keyed container is out. - [[bug-p-inherited-ignores-the-parents-default-parameter-values]] — the standard owning-container constructor idiom.
- [[bug-p-a-cast-as-lvalue-does-not-accept-a-builtin-type-name]].
Plus two found in passing: [[bug-p-stray-tokens-in-a-unit-declaration-section-are-silently-skipped]] (a typo'd section header discards declarations with no diagnostic) and [[bug-p-a-diagnostic-in-a-used-unit-names-the-wrong-source-file]].
That is a good yield for one rung, and it argues for the ladder rather than
against it. Recommended next rungs, by real-language-surface per unit of
work: (a) the fpjson data ptr fixup overflow — a real program the compiler
cannot build at all, which outranks everything else here; (b) enrol what exists,
so the next one does not rot unseen; (c) burn the three fgl walls — cheap, and
each turns on more than its own driver; (d) then rung 6 (rtl-generics), which is
already scoped and only blocked on one Track B typinfo gap.
Two facts about unit resolution, measured, worth not re-deriving
- pxx ships
math,types,typinfo,sysutils,classes,rtlconsts, and a shipped unit beats an-Fupath of the same name. So the corpus cannot exercise FPC's realsysutils/classessources by putting them on the search path; only units pxx does not ship (fgl,character, …) actually compile from vendored source. Whether that precedence is intended is a question for Track U if it ever blocks a rung. - Of the FPC
rtl/objpasunits pxx does not ship,fglcompiles;character.pasis rejected at line 1 (unexpected character) andfpwidestringneedsrtl/incon the include path. Neither was pursued — low value next to the fgl walls.
2026-08-27 (frankA) — re-survey #2: the recommendations were all stale, and rung 6's real wall is three defects deep
The 2026-08-25 entry above corrected a re-triage that said the ladder did not
exist. Its own "recommended next rungs" list has since gone stale in exactly
the same way — every one of (a), (b) and (c) is now done/:
| 2026-08-25 recommendation | state today |
|---|---|
(a) the fpjson data ptr fixup overflow — "outranks everything else here" |
done — bug-a-…-4096-entry-data-ptr-fixup-table is in done/ |
| (b) enrol what exists | done — test-fgl is in testmgr's limited + full, fpc-rtl is in twatch's CORPUS_EXPECTED |
| (c) burn the three fgl walls | done — all three bug-p-* are in done/ |
| (d) then rung 6 (rtl-generics) | still the next rung, and now measured — see below |
That is twice this table has aged into being actively misleading, which is the recurring defect rather than an accident. The state belongs where it cannot rot: in the runner's own output. Ticket prose is a snapshot; a rung either passes today or it does not.
Measured, not inferred — rung 2 is genuinely green
tools/run_fgl_corpus.sh against the compiler at this commit: 7 pass / 0 fail
/ 0 skip, real FPC 3.2.2 fgl.pp, pxx.skip empty. The corpus tree was absent
on this box and was fetched with tools/install_lib_candidates.sh fpc-rtl.
One caveat found and NOT filed, deliberately. With the tree absent the runner
prints SKIP and exits 0 — the vacuous pass this ticket already records as
defect #1. The 2026-08-25 fix moved which path it checks, not whether absence is
silent, so a fresh clone still gets a green that asserts nothing. It is not filed
because the enrolment work closed it one level up: twatch's CORPUS_EXPECTED
warns when the tree is missing, which is the layer that can tell "absent because
unprovisioned" from "absent because broken". Recorded here so the next reader who
notices the exit 0 does not re-file it.
Rung 6 (rtl-generics) — most of the predicted walls are already gone
The generics ticket predicts walls at "generic class header syntax, specialize,
nested generic types, interface constraints, TArray<T>", and its own
next-wall inventory names type-keyword method names and untyped constref.
Probed individually against this compiler:
| predicted wall | actual |
|---|---|
methods named after type keywords (class function Integer(...)) |
works |
untyped constref params |
works |
| generic class across UNITS, two specializations | works, matches FPC |
interface constraint generic THolder<T: IThing> |
works |
class constraint generic TWrap<T: TBase> |
works |
generic TArr<T> = array of T |
works |
nested type inside a generic (TPair) |
three separate defects |
So the rung's remaining cost is not spread across the feature surface — it is
concentrated in nested types, which is precisely what
TDictionary<K,V>.TPair is:
- A nested type's field named after an enclosing type parameter — fixed this
session (
83468c546), test + FPC oracle. - [[bug-p-two-generic-templates-cannot-share-a-nested-type-name]] — the second
template's
TPairresolves to the first's. - [[bug-p-a-second-specialization-of-a-generic-with-a-nested-type-segfaults]] — compiles clean, first specialization runs, second SIGSEGVs.
2 and 3 are pre-existing (both reproduce on the pinned binary) and both look like one cause: a nested type's identity is not per-specialization. They should be taken together, and taking them is what unblocks rung 6 — more than any of the walls the ticket currently predicts.
Method note
Every row above is a compile-and-run against fpc -Mobjfpc as oracle, one file
at a time — the rung suites themselves (make test-fgl, test-fpjson) are
hook-refused per CLAUDE.md and are Track T's to sweep. One file at a time is
enough to survey a rung and is what found all three defects.
2026-08-27, later — all three nested-type defects are closed; rung 6's named blocker is gone
The three defects the re-survey above concentrated the rung's cost into are now
fixed, and the second and third turned out to be one cause, one change
(7ee75329e), as predicted:
| defect | outcome |
|---|---|
| nested type's field named after an enclosing type parameter | fixed 83468c546 |
| two templates cannot share a nested type name | fixed 7ee75329e |
| second specialization of a generic with a nested type segfaults | fixed 7ee75329e |
The shared cause is worth keeping because it is this repo's documented
double-case shape: AddClassLikeType registers a nested type under its
QUALIFIED name once the bare name is taken, and its own comment says the
qualified spelling and a bare one inside the owner's body should "find the same
entry" — but only the qualified path was ever wired, because every
FindNestedType call site keys on a .. The bare reference fell through to the
flat unit table and found whichever was registered first, so a generic's nested
type — re-materialised per instantiation on purpose — was shared by every
instantiation after the first.
What identified it was ordering, not reading: whichever template was specialized second broke, and swapping the order moved the error to the other one.
Status of rung 6 (rtl-generics) after this: every wall its ticket names or
predicts is now cleared locally — type-keyword method names, untyped constref,
generics across units, interface and class constraints, TArray<T>, and nested
types. Its blocked-by is still the Track B typinfo/PTypeData gap, and the
actual corpus (packages/rtl-generics) is not among the trees
tools/install_lib_candidates.sh fetches, so the next step for that rung is
vendoring it and compiling — not another wall hunt. Nothing local predicts a
further blocker, which is exactly the point at which the real corpus is the only
thing that will tell the truth.
Rung 6 — rtl-generics: the one-shot diagnostic (2026-08-28)
Fetcher entry landed as 4bb5fd66f (fetch_rtl_generics, same pinned
$FPC_COMMIT as the other FPC-sourced corpora). CORPUS_EXPECTED in
twatch.py is deliberately untouched — this rung is a one-shot diagnostic,
not a gated job. Gate what can be green; diagnose what cannot.
Binary: 2c4e727d4b63, verified self-host fixedpoint at 4bb5fd66f. Every
number below came from that binary; both new defects reproduce on pinned
too, so none of them is fallout from the nested-type fixes.
What compiles today
| unit | lines | status |
|---|---|---|
generics.strings.pas |
37 | clean |
generics.helpers.pas |
146 | clean |
generics.memoryexpanders.pas |
227 | clean |
generics.hashes.pas |
1,617 | clean |
generics.defaults.pas |
3,358 | blocked |
generics.collections.pas |
4,165 | blocked only through defaults |
2,027 of 9,550 lines compile clean. All blockage is concentrated in
generics.defaults.pas; collections was never independently assessed because
it cannot get past its uses.
The answer to the question that motivated this: it is NOT typinfo all the way down
The rung's dependency graph was wrong. Typinfo is the largest wall but not
the only one, and two of the four walls are Track P defects in our own
generics implementation. Method: stub each wall out in a throwaway copy
($SCRATCH/rgsrc, never the vendored tree) and re-probe, so each subsequent
wall becomes visible. Stubs are labelled {PROBE: ...} in that copy and exist
only to see past a wall — nothing here is a claim that the unit compiles.
(SNAPSHOT 2026-08-28, stubbed copy — superseded. Live table at the top.)
| # | wall | owner | sites | ticket |
|---|---|---|---|---|
| 1 | typinfo: PTypeData fields, PTypeInfo, GetTypeData, TTypeKind, TOrdType, TFloatType |
B | see list below | feature-typinfo-facade-unit (p72) |
| 2 | generic method header binds to same-named non-generic class | P | ~700 lines gated | bug-p-a-generic-methods-out-of-line-header-binds-to-a-same-named-non-generic-class |
| 3 | TGeneric<T>.ClassMethod inside another generic's body |
P | 13 in defaults, ~357-ish shape count in collections |
bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body |
| 4 | SysUtils gaps: EArgumentOutOfRangeException, Exception.CreateRes, System.Error/TRuntimeError |
B | (to file / relay to frankB) |
The two
3s in row 4 were one wrong measurement reported twice. Re-measured 2026-08-28:generics.defaults.pashas 7EArgumentOutOfRangeExceptionsites and 7CreateRes(@SArgumentOutOfRange)sites — and they are the same seven lines (2960, 3049, 3075, 3078, 3182, 3218, 3221), because each line spells both. So the two symbols never had independent counts to agree on; one figure was recorded under two headings, which is what made it look corroborated. A count that stops at 3 where the truth is 7 is the shape of an error-limited compile, not of a grep. Corpus-wide the real figure is 28CreateRes(@…)sites — 18 ingenerics.collections.pas, 7 here, plus one each ofSDuplicatesNotAllowed/SDictionaryKeyDoesNotExist/SArgumentNilNode— so this blocks collections harder than defaults, and collections has not been probed past its earlier walls.
After stubbing all four, generics.defaults.pas produced no further distinct
errors — the walls above are the complete set for that unit, not a prefix of
an unknown-length list.
The named typinfo list frankB asked for
Not "generics.defaults needs typinfo" but exactly this surface:
PTypeData— 39 references. Fields actually touched:OrdType(×3),FloatType(×3),elSize(×3),MinInt64Value(×1),MaxInt64Value(×1). Five fields, nine sites — a facade does not need the whole record.PTypeInfo— 14 references (passed through, rarely dereferenced).TypeInfo(...)— 6;GetTypeData(...)— 6.- Enums needed whole:
TTypeKind,TOrdType(otSByte/otUByte/otSWord/otUWord/otSLong/otULong),TFloatType.
OrdType and FloatType are used only as case selectors dispatching to a
comparer, and MinInt64Value/MaxInt64Value only in a single range test — so
the facade's hard requirement is a correct OrdType/FloatType for ordinal and
real types. That is a much smaller target than 39 references suggests.
Caveat, stated plainly
Walls 2 and 3 gate the parts of defaults that walls 1 and 4 do not, but the
four sets are not proven disjoint: stubbing is not compiling, and code behind a
stub was type-checked against the stub, not against the real thing. The
partition above is sound as a list of what must be built; it is not a
prediction that fixing all four makes the unit compile on the first try. Re-run
this diagnostic — it is one probe.sh — after the typinfo facade lands.
Rung 6 partition superseded — five walls, not four (2026-08-28, end of session)
The four-wall table above was correct for that unit at that moment, and both halves of that qualifier have since moved. Recording it here rather than editing the table, so the earlier measurement stays readable as what it was.
What changed: 042bcbb32 fixed wall 2, which moved generics.defaults.pas
from failing at line 2173 to 2351 and exposed a fifth wall the partition did
not predict — AN_CLASS_VIRTUAL_CALL failing to lower when a virtual class
method's address is taken as a value
([[bug-p-the-address-of-a-virtual-class-method-cannot-be-lowered]]). It is not
typinfo and not generic-specific: the repro has no generics in it at all,
which is worth knowing before anyone searches the generics code for it.
That is the concrete instance of the hedge the original partition carried: code
behind a stub is type-checked against the stub, so clearing one wall can reveal
another. Do not treat the wall list as a work estimate. Re-run the probe
($SCRATCH/rg/probe.sh, one command) after each wall lands; it is cheap and it
is the only thing that keeps this section honest.
Current state of the five walls
(SNAPSHOT — superseded. Live table at the top.)
| # | wall | owner | status |
|---|---|---|---|
| 1 | typinfo surface | B | facade landed cfa72767f; type-level API only — its instance-taking overloads are still unreachable, see below |
| 2 | generic method header binds to same-named non-generic class | P | fixed, 042bcbb32 |
| 3 | nested specialize X<T> in expression position |
P | parked, diagnosis banked, reclassified — not a Delphi bug, fails in objfpc on pinned |
| 4 | SysUtils: EArgumentOutOfRangeException, CreateRes, System.Error |
B | filed |
| 5 | address of a virtual class method | P | filed |
A second chain now runs alongside this one
The typinfo facade shipped, but GetPropInfo(AnObject, 'Caption') — the spelling
every FPC consumer uses — still binds the wrong overload. Two defects were
stacked there: the implicit class-to-typed-pointer conversion (fixed,
8b75fcabd, which also closed a silent memory-safety hole) and, underneath it,
[[bug-p-a-parameters-pointer-element-type-is-lost-between-registration-and-overload-matching]],
which is what still gates the facade's consumer path.
That chain does not block this rung — rung 6 needs the type-level API
(GetTypeData(PTypeInfo)), not the instance overloads. Stated explicitly
because the two are easy to conflate now that they are open at the same time,
and a blocked-by: edge between them would rank correctly for the wrong reason.
Standing warning for anyone verifying work on this chain
A repro that passes on the fix and on pinned is not a verification. Two
synthetic overload repros written during this session passed on both and were
never broken; they were a step away from being recorded as proof that the
overload half was fixed. Run the "before". It is the only thing that catches it.
Rung 6 re-probed on the PRISTINE corpus (2026-08-28, later)
Re-ran the diagnostic as this section said to, now that the typinfo facade
(cfa72767f) has landed and wall 2 is fixed. This time with no stubs at all —
the earlier partition was measured through a stubbed copy, and stubs are what
made it a partition rather than a compile.
Binary ea689da902bb. generics.defaults.pas now reaches line 2411.
(SNAPSHOT — superseded. Live table at the top.)
| wall | status now |
|---|---|
| 1 — typinfo | gone: the facade covers it; no stub needed anywhere |
| 2 — generic method header binding | gone: fixed 042bcbb32 |
3 — nested specialize in expression position |
not reached yet |
| 4 — SysUtils gaps | not reached yet |
| 5 — method pointers | the current blocker, 28 sites |
So two of the five walls are genuinely down, and rung 6 is now behind one defect rather than a list. That is a materially better position than the partition described, and it was worth re-measuring rather than assuming.
Wall 5 turned out to be bigger than filed, and the first boundary was wrong
Filed as "the address of a virtual class method cannot be lowered". Measuring
eight shapes instead of four shows it is two independent defects, and
neither is really about virtual:
- a CLASS method cannot become a method pointer at all —
m := TSvc.CPick; m(5)segfaults with no cast anywhere, while the instance twin works; - an inline cast of a method reference fails even for an instance method — going through a variable works, casting in expression position does not.
Full table, repro and direction:
[[bug-p-a-class-method-cast-to-a-method-pointer-inline-segfaults]] (p70). The
virtual ticket is its honest-exit sibling — same shape, IR_UNSUPPORTED
instead of a bad pointer.
Worth recording how the first table went wrong, since this section is where
the method gets described: four shapes looked like a clean boundary
("everything adjacent works, only the inline cast is broken") and the two rows
that separate the defects — class → var, and instance with an inline cast —
had not been tried. The fix that boundary implied would have left half the bug
in place. Vary the shape until the table has no untested neighbours, not until
it looks tidy.
Rung 6 progress, 2026-08-28 (frankA) — :3250 → :3341, and the wall list keeps growing
Five defects landed today, four of them walls this unit was standing behind:
resourcestring addressability, wall 6 (a generic class method call inside
another generic's body), mutually-referencing generics rejected as circular, and
— in one fix closing two tickets — a generic specialized before its declaration
(bug-p-a-generic-specialized-before-its-declaration-is-unresolvable +
bug-p-a-generic-prerequisite-is-emitted-before-the-referenced-template-exists).
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
generics.defaults.pas moved :2960 → :3231 → :994 → :3250 → :3341 across the
day. Note the :994 — the third fix moved the failure backwards, because a
construct that had been rejected outright started compiling and reached a new
error earlier in the file. A line number going down is not a regression here,
and reading it as one would have wasted a session.
The new stop is a different class of error — "LookupEqualityComparer": a pointer has no members — not another specialize. That is the first time today
the unit has failed on something other than generic prerequisite resolution, so
the next wall is probably not more of the same machinery.
The rung-6 partition is still superseded, and today is more evidence for the
warning above, not less. Four walls cleared, and the unit is still not through.
Re-run $SCRATCH/rg/probe.sh before estimating anything; do not read the wall
list as a work estimate.
CORPUS_EXPECTED untouched throughout, and the prerequisite scan was not
weakened to make the corpus advance — the fix adds a second run of the same
scan at a later time; both original scans stay, and test_generic_cycle_fail
still correctly refuses.
2026-08-29 (frankA) — rung 6 CLEARED: generics.defaults.pas compiles end to end
First: the park note at the top of this ticket is STALE and should be read with this section. It lists three open items; two were already closed before this session started:
| park note said | actual state on 2026-08-29 |
| PARK CONDITION SUPERSEDED — dated ladder history, not a live gate | The only live resume condition is the one in the LIVE STATUS blockquote at the top; every wall named here was cleared on the date its section carries. |
|---|---|
wall 6 Delphi half, bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body — open in unfinished/ |
done |
wall 7, bug-p-a-resourcestring-is-not-addressable (p55) |
done, c9cf8c457 |
bug-p-two-different-nested-specializations-of-one-template-collide (p65) |
still open, in backlog/ |
A park note is a snapshot of the moment it was written and the tickets it names PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. keep moving without it. Re-measure before reading it as a plan — that is what this session did, and the corpus had advanced past everything the note describes.
The new wall, found and closed
Re-running the compile put generics.defaults.pas at a wall the note does not
mention:
error: @TEquals.Class: the address of a routine with no body was taken
That is [[bug-p-a-forward-declaration-does-not-bind-a-differently-cased-body]],
filed and fixed this session: Pascal is case-insensitive, so
TEquals.&Class (declared, :186) and TEquals.&class (implemented, :1566)
are one routine, and FindProcOverloadRec compared names exactly — so the body
registered a second proc and the declared one stayed bodiless.
The & escape is a red herring and was the first hypothesis: it is refuted
by the pair of controls — escaped-with-matching-case works, unescaped-with-
mismatched-case fails. A plain function Bar; forward; + function bar;
reproduces it with no class and no escape, and says unresolved forward: Bar,
which names the defect outright. The minimal repro was worth more than every
hypothesis formed from the corpus error.
Rung 6 result
| unit | lines | before | after |
|---|---|---|---|
generics.defaults.pas |
3,358 | blocked in the VMT const block | compiles end to end |
generics.collections.pas |
— | never independently assessed | reaches a NEW wall |
generics.collections.pas's wall is unknown type: TKey at
generics.defaults.pas:790 — a generic type parameter not in scope at the point
the specialization is materialised. Independent of everything above and of the
one remaining park item; it is the next rung's subject.
Wall table, updated
| # | wall | owner | status |
|---|---|---|---|
| 6 | generic class specialized by the enclosing generic's type parameter | P | DONE |
| 7 | @SArgumentOutOfRange — resourcestring not addressable |
P | DONE c9cf8c457 |
| 8 | forward decl does not bind a differently-cased body | P | DONE (this session) |
| 9 | unknown type: TKey — generic type param out of scope in a materialised specialization |
P | open, new |
Still open and untouched: [[bug-p-two-different-nested-specializations-of-one-template-collide]].
PARKED again 2026-08-29 (frankA) — rung 6 done, rung 9 open and unclaimed
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
Out of working/ because it is a live lock and nothing is happening on it.
Everything is pushed. Nothing is reverted or half-applied; no code is mid-edit.
State: generics.defaults.pas compiles end to end.
generics.collections.pas is the next unit and its wall is
[[bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized]]
(rung 9), which carries two refuted hypotheses with the measurements that
refuted them — the macros are fine, and it is not the forward-decl case bug.
Its first job is a two-unit reduction, which has not been built.
Also still open: [[bug-p-two-different-nested-specializations-of-one-template-collide]].
Read the 2026-08-29 section above before the park note at the TOP of this PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. ticket — that older note was stale on two of its three items by the time it was read, which is the failure mode this one will have too. Re-measure the corpus first; it takes one compile.
Rung 9 RE-DIAGNOSED 2026-08-29 (frankA) — and the first framing was wrong
The wall behind generics.defaults.pas was filed as "unknown type: TKey — a
generic type parameter is out of scope", marked observed, not diagnosed.
Good thing: reduced, it is not about type parameters, not about TKey, and not
about the macros. It is
in
{$MODE DELPHI}, a generic declared in a USED UNIT cannot be specialized at all —var o: TOne<Integer>;answersunknown type: TOne.
Same-unit works. The objfpc specialize TOne<Integer> spelling works
cross-unit. Only the Delphi angle-bracket surface fails, and a ONE-parameter
generic fails, so arity is irrelevant too. Eleven lines reproduce it.
Cause, measured: DelphiRewriteGenericUses sweeps the shared Tokens[]
starting at insertAt — just past the template's own declaration — and the main
program is lexed BEFORE the unit it uses, so the program's use sits below that
index and is never rewritten. Renamed to
[[bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized]] and
banked, not microfixed: the obvious "start at 0" is wrong on where the minted
alias must live and re-widens the surface of the already-recorded
bug-a-the-delphi-generic-rewrite-is-not-idempotent.
Note for the ladder — and see the 2026-08-29 correction at the top of this
file. This was recorded as the SECOND ordering defect in that one rewrite,
alongside wall 6's GenericMethodCount=0, with the suggestion that one
restructuring might close both. It does not: wall 6 was closed on 2026-08-28 by
extending the prerequisite SCAN, leaving this arm untouched, and this arm was
then closed by moving the SWEEP to the uses clause. The question was worth
asking; the answer is no.
2026-08-30 (frankA) — re-probed at HEAD 66b068019. Rung 9 is CLEAR; the wall MOVED
Re-measured rather than read. Six rtl-generics src units, each through a
driver program, -Fu/-Fi at the src root:
| unit | at HEAD |
|---|---|
generics.defaults |
OK |
generics.hashes |
OK |
generics.helpers |
OK |
generics.memoryexpanders |
OK |
generics.strings |
OK |
generics.collections |
unknown type: TKey |
Five of six now compile. generics.defaults compiling standalone is rung 6
holding. Rung 9's blocker
([[bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized]]) closed
at 625991d20 and its own repro no longer fails.
The remaining wall is NOT rung 9's, despite wearing its error text
unknown type: TKey is the string rung 9 was originally filed under and then
explicitly re-diagnosed away from. Reading it as "rung 9 is still open" would be
wrong. Two facts separate them:
- The error is raised while parsing
generics.defaults.pas, atIComparer<T>'sCompare(constref Left, Right: T)— a unit that compiles perfectly on its own. It only fails when reached throughgenerics.collections, which specializesIComparer<TKey>with a name that is itself still a type parameter. - The pinned binary produces the identical message, so this is not a regression from anything that landed tonight.
Narrowed by bisection to one include, and four hypotheses died first
Truncating the interface at declaration boundaries: cut@438 clean,
cut@474 reproduces. The only thing in between is
{$I inc\generics.dictionariesh.inc}
which declares TPair<TKey, TValue> and specializes IEqualityComparer<TKey>
across the unit boundary.
Recorded because they cost the time and would otherwise be re-tried: four
hand-built reductions did not reproduce — plain cross-unit
IComparer<TKey>; plus constref; plus a method implementation taking it;
plus a macro-supplied parameter list ({$DEFINE MAP_CONSTRAINTS := TKey, TValue}); plus a nested public type/private var section. All five compile
clean. The obvious mechanism ("specializing a cross-unit generic with a
still-generic argument") is therefore not sufficient, and anyone starting
from it will burn the same hour.
Moving those declarations into an {$I} include DOES break — differently
Eleven lines, and it separates the two binaries:
PINNED : pascal26:6: error: unknown type: TKey
HEAD : pascal26:1: error: unexpected token
near: interface uses u_tmpl type specialize >>> TPair TKey
specialize has been injected before a generic DECLARATION, which is the
Delphi rewrite treating TPair<TKey, TValue> = record as a use. That is the
sweep 625991d20 moved to the uses clause. Both binaries fail, so this is a
changed failure mode on already-failing code, not a working case broken —
but the wall was re-walled one layer down, exactly the shape this ladder keeps
producing. Filed as
[[bug-p-the-delphi-generic-rewrite-rewrites-a-shadowing-declaration-as-a-use]].
Next holder: the remaining corpus wall is the include, not the specialization. Start from the bisection above, not from the error text.
PARKED 2026-08-30 (frankA) — released from working/, probe complete
The re-probe above is finished and the ladder's next step is blocked on
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
[[bug-p-the-delphi-generic-rewrite-rewrites-a-shadowing-declaration-as-a-use]].
Released from working/ rather than held, because a lock held by a session
that is not working the ticket reads as "someone is on it" while nothing
happens — which is the exact failure measured fleet-wide tonight and filed as
decide-the-ticket-lock-is-too-heavy-for-a-per-minute-commit-loop.
Everything is pushed. Nothing is half-applied. Re-measure before trusting the table above — that is the rule this ladder keeps re-teaching, and this section is now itself a snapshot.
CORRECTION 2026-08-30 (frankA) — the include was NOT the trigger, and the repro was a DIFFERENT bug
The section above says the corpus wall was narrowed to
{$I inc\generics.dictionariesh.inc} and that an 11-line include repro
reproduces it. Both halves are wrong, and this correction is the reliable
part of that entry.
The repro was a different defect. Its error is unexpected token; the
corpus wall's is unknown type: TKey. I let "both fail near generics after the
same bisect" stand in for "same bug". The repro's real trigger was a name
reuse I had left in the support unit from an earlier experiment — the include
and the reuse varied together and I named the wrong one. Isolated four ways:
include without the reuse is clean; the reuse without any include
fails. That defect is real, is fixed, and is
[[bug-p-the-delphi-generic-rewrite-rewrites-a-shadowing-declaration-as-a-use]].
The corpus wall is untouched by that fix — re-probed after it landed,
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
generics.collections still stops at unknown type: TKey in
generics.defaults.pas, same line, same message.
What survives: the bisection itself. cut@438 clean, cut@474 reproduces,
and the include line sits between them. That is a measurement and it still
stands — but it locates the trigger's neighbourhood, not its mechanism, and I
wrote it up as though it were the mechanism.
For the next holder: the corpus wall is UNREDUCED. There is no repro for it.
Do not start from the include hypothesis and do not reuse the shadowing repro —
that is a different bug. Start by asking why generics.defaults parses cleanly
alone and fails when reached through generics.collections, and get a
reduction before believing any mechanism, including this paragraph's framing.
2026-08-30 (frankA) — the wall is REDUCED, and the reported line was never the defect
Read this section instead of the "LIVE STATUS" table above. That table's
wall (unknown type: TKey in generics.defaults.pas) is gone, and the
CORRECTION section above it — which says the wall is unreduced and warns against
the include hypothesis — is now itself stale. Third time this ticket has gone
stale on the same field. See the note at the end about recording the recipe
rather than the answer.
Census, and the attribution is deliberately unlocated
| binary | first error | total |
|---|---|---|
pinned |
defaults:46 unknown type: TKey |
20 |
a60f92ba830a (HEAD minus the shard0-6-2 whitelist) |
collections:120 unknown type: PT |
4 |
22c67e5ea61e (HEAD, shard0-6-2 landed) |
collections:120 unknown type: PT |
4 |
3309b9ba6609 (HEAD + the fix below) |
collections:146 (a new wall, see below) |
1 |
Rows two and three are identical, so the TKey wall did not move because of
tonight's whitelist — consistent with the bound-name harvest being unchanged
at names=293 cap=512 overflow=0. It moved for something already in HEAD that I
have not identified. Recorded as unattributed on purpose: a fix landing shortly
before an improvement is the cheapest wrong attribution available, and one extra
A/B run against the pre-fix binary was the whole cost of not making it. There
is something in HEAD nobody has accounted for.
Root cause of the 4-error wall — and it is 24 lines away from where it is reported
generics.collections.pas:144:
TCustomPointersEnumerator<T, PT> = class abstract(TEnumerator<PT>);
A generic class, declared bodyless, carrying a modifier.
ParseGenericTemplateNamed detects bodyless forms up front because there is no
end for its depth loop to count down to — but its test looked at the token
right after class for ( or ; and never skipped abstract / sealed. It
saw abstract, concluded the declaration had a body, and let the depth loop
swallow the following declarations until it found somebody else's end.
The damage was reported at line 120, on function DoGetCurrent: T — a line
with nothing wrong with it, in a class that compiles fine alone. That is why
every reduction aimed at line 120's text failed, including the four this ticket
already records and one more I wrote tonight. The reported line was not the
defect and never had been.
Confirmed by truncation at real declaration boundaries:
| cut | verdict |
|---|---|
cut@125 (TEnumerator only) |
clean |
cut@141 (adds TEnumerable, PT = ^T, TEnumerator<PT>) |
clean |
cut@144 (adds the bodyless generic) |
fails |
Ruled out first, by measurement rather than argument: the Delphi rewrite (p.dgen
shows injections on the uses at 133/137/139/144/152 and none at 120 or
135), and harvest overflow (names=293 cap=512 overflow=0).
Fix, and it is a sibling I should have found last commit
compiler/pasparser_generic.inc — skip abstract/sealed before the bodyless
test. The identical omission was fixed in CollectNestedTypeNames earlier
tonight, in this same file, and I did not grep for the second copy.
devdocs/dev/normalise-dont-special-case.md says in as many words: if you fix a
bug on one arm of a double case, grep for the sibling before closing the
ticket. One decision, two hand-rolled copies, one of them fixed.
Needs all three of generic + modifier + bodyless, which is why it survived: the
non-generic path consumes the modifiers itself (pasparser_decl.inc ~4449) and
was always right, and a real body ends at its own end.
Regression test test/test_generic_bodiless_class_modifier.pas, in test-core,
asserting bodiless 7 3 1. Per-form, one form per program, against
22c67e5ea61e:
| form | before | after |
|---|---|---|
TAbs<T> = class abstract; |
FAIL | ok |
TDerived<T> = class abstract(TBase<T>); |
FAIL | ok |
TSealedB<T> = class sealed(TBase<T>); |
FAIL | ok |
TFwd<T> = class; (no modifier — the control) |
ok | ok |
The control is what isolates the modifier as the variable. It is not in the
test program: FPC rejects TFwd<T> = class; as "Type TFwd$1 is not completely
defined", so including it would have cost the oracle — pxx accepting it is the
ordinary accept-more divergence, not a defect. I found that by running FPC on
the finished file, which is the only reason the header does not now carry a
false "FPC agrees" claim.
The new wall, one error
generics.collections.pas:146: generic templates must be class, record,
interface, array or procedure declarations
near: T PT >>> object strict private
TCustomPointersCollection<T, PT> = object — a generic object type, which
ParseGenericTemplateNamed does not accept. Distinct from everything above; not
started.
For the next holder — and for this ticket's own health
This ticket has now gone stale three times on the same field, each time because it recorded what the wall was rather than how to re-derive it. The wall is a function of the whole compiler and moves faster than the ticket is read. The re-derivation is four commands:
R=library_candidates/rtl-generics/packages/rtl-generics/src
printf 'program d;\nuses generics.collections;\nbegin\nend.\n' > /tmp/d.pas
./compiler/pascal26 -Fu$R -Fi$R /tmp/d.pas /tmp/d 2>&1 | grep -c 'error'
# then: truncate at declaration boundaries to find the FIRST bad line,
# because the reported line has twice now not been the defect.
Do not trust any error line in this unit without a truncation bisect.
The last error is a DECIDED NON-GOAL, so rung 6 is finished as far as it goes
generics.collections.pas:146, TCustomPointersCollection<T, PT> = object,
is not a small gap in the generic path. pxx has no object type at all —
measured, not assumed: a plain non-generic
type TObj = object F: Integer; end;
fails identically (Expected: begin, but got: F). So adding tkObject to
ParseGenericTemplateNamed's accepted set would buy nothing; the type itself
does not exist.
That is already tracked and already answered:
- [[feature-p-legacy-value-object-types]] [P p15] — the feature,
gated-by↓ - [[decide-old-style-object-types]] — DECIDED 2026-08-25, option A: we do
not implement
objecttypes. Not now. On the principle that a corpus is a measuring instrument, not a dependency.
So rtl-generics will not compile fully, by design, and rung 6 should not be read as blocked. It went 20 errors → 1, and the 1 is a construct we have decided not to support. Anyone who picks this up to "finish rtl-generics" would be reversing a standing decision, at prio 15, to make a measuring instrument read zero — which is the exact thing that decision refuses.
Do not file the remaining error as a corpus bug. If the object decision is
ever revisited, it is revisited on its own ticket, not because a corpus wants it.
PARKED 2026-08-30 (frankA) — rung 6 done, lock released, nothing half-applied
Released from working/ because rung 6 is finished as far as it goes and this
is a multi-rung ladder: holding it open on one completed rung blocks the file
for no gain. Everything is committed and pushed; there is no in-flight edit.
Rung 6 (rtl-generics) this session: 20 errors → 1, and the 1 is the decided non-goal recorded in the section above. Do not read that as blocked.
Next holder: read the section above before the LIVE STATUS table. That table has now been stale three times on the same field, and the two corrections above it are dated. The re-derivation recipe is four commands and is the reliable part.
2026-08-30 (frankA) — re-measured; rung 6's remaining wall is a Track U decision, not a bug
Did what this ticket tells its next holder to do: re-measured before reading any note as a plan. Every prior park note was stale again, in the same direction — the corpus had advanced past what they describe.
| the notes said | measured on HEAD today |
|---|---|
rung 9 = bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized, open |
done |
| wall 6 Delphi half, open | done |
| wall 7 resourcestring, open | done |
generics.collections wall = unknown type: TKey |
not reproducible — a different wall now |
bug-p-two-different-nested-specializations-of-one-template-collide |
still open, backlog/ |
Four of the five referenced defects are closed. The one open item is unchanged.
How to re-measure (nobody had written this down)
pxx has no standalone-unit output, so a unit needs a driver:
S=library_candidates/rtl-generics/packages/rtl-generics/src
printf 'program d;\nuses Generics.Collections;\nbegin\nend.\n' > /tmp/d.pas
./compiler/pascal26 -Fu$S -Fi$S/inc /tmp/d.pas /tmp/d.bin
(--check-only does not exist; compiling the .pas directly gives "this file
is a unit, not a program", which is easy to misread as a corpus failure. Two
minutes lost to each, recorded so the next holder loses none.)
Current rung 6 state
| unit | result |
|---|---|
generics.strings |
compiles |
generics.defaults |
compiles end to end (confirms the 2026-08-29 note) |
generics.collections |
wall at :146 |
pascal26:146: error: generic templates must be class, record, interface, array
or procedure declarations
The wall is object, and it is NOT a whitelist omission
TCustomPointersCollection<T, PT> = object — the legacy value-object. The
whitelist in pasparser_generic.inc:1130 omits it, but widening it would be
wrong: there is no tkObject token, and object is already claimed by an
unrelated meaning — the rooted object reference type
([[feature-object-reference-type]], pasparser_decl.inc:492), whose comment
states outright that value-object "was never supported here".
So this is a known absence with a standing decision behind it:
[[decide-old-style-object-types]] chose option A, do not implement, in
decided/.
The decision's own revisit trigger has fired — filed, not decided
That decision named its trigger: "Option B, in full, the moment actual source someone wants to build needs it. Not an FPC test — a program." rtl-generics is that source, and this rung is prio 75.
Filed as [[decide-revisit-object-types-rtl-generics-fired-the-trigger]] rather than acted on, because the cost case has changed and the call is the user's. Measured, and this is the part worth carrying:
- the corpus contains exactly one
= object, across all six units; - it has no fields, no inheritance, no virtual methods, no constructor — none of the cost drivers the decision priced (storage, lifetime, assignment, VMT);
- the equivalent generic record-with-methods compiles and runs on HEAD today,
including
strict private typeand pointer-to-specialization access.
The two deltas are the keyword and a protected that nothing inherits from.
Rung 6 is therefore blocked on a decision, not on a bug, and no amount of frontend work moves it until that is answered. That is a different kind of stop from every previous wall on this ticket, all of which were defects — so do not go looking for the next ordering bug behind it.
Still open, unchanged
[[bug-p-two-different-nested-specializations-of-one-template-collide]] (p65) — independent of everything above, and its "where to start" remains a hypothesis from the error's shape, not a measurement.
Is the park's condition met? — judgement, 2026-08-30 (frankD, read-only pass)
Asked by the coordinator: "do seven resolved blockers add up to a resume?" Partly — and the count is answering a question this ticket stopped asking.
The seven are real, and the canonical table's condition IS met
Every wall in LIVE STATUS — THE ONE CANONICAL TABLE is now closed. Its own
conclusion was "Rung 6 is now behind wall 6 alone", and wall 6
([[bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body]])
closed 2026-08-28 in 35f485537 — which the 2026-08-29 note at the top of this
file already says. Wall 7 is resolved too. Wall 3 was established to be wall 6.
So on the condition as written, the park is discharged.
But the park was re-written mid-park, and the new condition is live
The Status line says "parked 2026-08-30 — rung 6 blocked on
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
[[decide-revisit-object-types-rtl-generics-fired-the-trigger]]". That ticket is
open in backlog/ at U p70. It post-dates all seven.
This is the structural point, and it generalises past this ticket. Counting resolved blockers assumes a park condition is static. This one was replaced while parked, so the seven and the current block are disjoint sets, and a resolved-blocker tally answers a question the ticket had already stopped asking. A park's condition needs a date as much as a park does — and only the Status line here carries one, which is why it is the only line that was right.
The resume is real, but it is not rung 6 — and both named alternatives moved
Rung 6 waits on a human decision and nothing an agent does changes that. What is actually available has itself drifted since it was written down:
- Rung 4 (fpjson) is the live candidate, and its row above was wrong in both
halves. The blocker resolved 2026-08-25 (
042e13b5c); the "in no testmgr tier" claim ended whentest-fpjsonwas added to full. So the rung is not RED-and-unwatched — it is unmeasured since its blocker closed, which is a different and much cheaper problem. I did not run it:make test-fpjsonis a full-tier target and the hook refuses it here; Track T sweeps it. - The escape hatch named in the U ticket has closed. That ticket argues —
correctly at the time — that
blocked-byshould NOT go on this umbrella, because it would hide workable rungs, citing [[bug-p-two-different-nested-specializations-of-one-template-collide]] [P p65] as "explicitly independent of this decision". That resolved the same day,4d5f86a0b. The reasoning stands; the example it rests on is gone, so the trade wants re-pricing rather than re-affirming.
On the frontmatter edge — do NOT add blocked-by here
The coordinator's standing instruction is to promote a real blocker into
frontmatter, and I am declining for this one, on the coordinator's own earlier
reasoning: blocked-by on an umbrella removes the whole ladder from ready
when only one rung is blocked. Correct ranking bought by hiding several workable
rungs is a worse trade.
The structural fix is that the edge has nowhere correct to go. blocked-by
is a whole-ticket field and the block is per-rung, so no frontmatter on this
file can be right. Rung 6 wants to be its own ticket, carrying the edge; then
the ranker sees the real dependency, the ladder stays visible, and the prose stops
being the only register that knows. Until that split, this section is the
compensating control and it is the same class of thing it is compensating for.
Sibling glance (asked for; read-only)
- [[feature-pascal-corpus-fpc-testsuite]] [P p65] — strong resume candidate.
Of 10 wikilinks, four are not in
done/: this umbrella (circular), two dangling (project_fpc_compat_next_queue,project_mimic_fpc_doneresolve to no file at all), and one real —task-pascal-conformance-long-tail, inbacklog/. Its own Status says the rung-1 harness is delivered and live. So its four-resolved count understates: it is effectively behind one item. (This umbrella cites two dangling links of its own —project_fpc_compat_next_queue,project_synapse_progress. A dangling wikilink reads as an open dependency to every tool that counts them, and as a typo to every human. Worth a sweep; not done here.) - [[feature-pascal-corpus-generics]] [P p65] — held by frankA, not touched. Five open links, none of them dangling. Relayed to frankA rather than edited.
What was and was not done here
Read-only judgement. Prose edits confined to this file: the canonical table
(which instructs "Update THIS table. Leave the snapshots alone."), the rung-4
ladder row, and this section. No dated snapshot was altered, no frontmatter
changed, no ticket moved, no compiler file opened. pasparser_generic.inc was
neither read for edit nor touched — frank-rust has held it uncommitted since
23:39.
2026-08-30 (coordinator) — RUNG 6's BLOCKER MOVED; retarget before anyone reads the park
The Status: line at the top of this file says rung 6 is "blocked on
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
decide-revisit-object-types-rtl-generics-fired-the-trigger". That is now stale
and points at settled work. frank-user resolved
bug-p-object-value-types-standard-meaning in d23f52948 (board move
50d341cd9): object in type-declaration position is the standard Pascal value
type with methods, lowered as an advanced record, and the rooted class-reference
meaning is retired.
Rung 6 now waits on
[[bug-p-generic-type-param-unresolved-in-class-abstract-template]] [P p70] —
TCustomPointersEnumerator<T, PT> = class abstract(TEnumerator<PT>) resolves PT
in the ancestor clause and not in its member signatures.
The measurement is worth more than the retarget: AN EARLIER LINE NUMBER IS NOT AN EARLIER FAILURE
The corpus wall moved backwards, 146 → 120, and that is the expected shape after this fix rather than a regression. frank-user measured both binaries against the same file:
| binary | probe as RECORDED | stops at |
|---|---|---|
pinned |
$ pinned generics.collections.pas (done ticket) — this command cannot run, see below |
:146 |
| HEAD, sha not recorded | "a one-line program that only does uses Generics.Collections" (new ticket, frank-user) |
:120 unknown type: PT, then :123, then :135 |
HEAD ea5a8ef96, binary 6319b892f517 |
{$mode delphi} program, uses Generics.Collections, -Fu<rtl-generics/src> (frank-rust) |
:135 unknown type: TArray — :120 never appears |
Rows 2 and 3 are the SAME PROBE and disagree. The disagreement is unexplained — do not quote either line number as "the wall" until someone reproduces one.
:146 is a SYNTAX error that aborts the parse. :120/:123/:135 are SEMANTIC errors reported against the template's own line numbers, and they are only reachable once a specialization is streamed — which the abort prevented. So the earlier number is deeper progress.
This also settles a retraction that was itself over-corrected. frank-rust reported :120 earlier, could not reproduce it against either binary, and withdrew the figure — correctly on the evidence it had, since the abort order made :120 genuinely unreachable at the time. The number was not wrong, it was masked, and the honest form of that finding was "not reachable yet", not "not real". Its wider conclusion — that the wall was long-standing rather than a regression from the generics work — was right and remains right.
Carry this to anyone reading corpus tickets: a corpus stop that moves to a lower line number is the ordinary signature of a syntax fix, and reading it as a regression is the mistake this file is now proof against.
2026-08-30 (coordinator, same day) — I RELAYED A LINE NUMBER I HAD NOT MEASURED
The table above originally read "HEAD: past 146; new wall at :120, unknown type: PT", flat, as though one binary had one wall. That was frank-user's
measurement written into a ticket on master by me, unverified, twenty minutes
after I told another lane that a lead from this seat should arrive with the
measurement that would kill it. frank-rust supplied that measurement within the
hour: it pulled to ea5a8ef96, rebuilt to a fixedpoint (6319b892f517,
converged 1 round), re-ran its own probe and got :135, unknown type: TArray — not :120, not PT.
It then declined to write my number into its ticket on my say-so, "that is the exact substitution that produced the original bad figure, one level up." Correct, and the table is amended rather than defended.
THE RECONCILIATION: THE TWO PROBES ARE DIFFERENT PROGRAMS
Both measurements are true, and the difference is visible in the two tickets
without running anything. frank-user's ticket records its invocation as
$ pinned generics.collections.pas — the unit compiled DIRECTLY, which
reports :120, :123 and :135 as a list. frank-rust compiled a
{$mode delphi} program that reaches the unit through uses, with -Fu — and
there :120 does not appear at all; the first stop is :135.
So the walls are not a sequence one behind the other; the same file has
different walls depending on how it is entered. TCustomPointersEnumerator<T, PT> = class abstract(TEnumerator<PT>) resolves PT when the unit is reached
through a delphi-mode uses and does not when the file is compiled directly.
HYPOTHESIS, MINE, UNMEASURED: the dialect mode differs between the two entries
— a directly-compiled unit does not inherit the {$mode delphi} of a program that
uses it, and delphi-mode generic scoping is what makes PT resolve. The
measurement that would settle it is one run: compile generics.collections.pas
directly with {$mode delphi} forced. If :120 disappears, the hypothesis holds
and :120 is an artefact of the direct probe rather than a corpus wall at all. I
have not run it and it is not mine to run — it is recorded here as a hypothesis
with its falsifier, not as a finding.
Consequence for bug-p-generic-type-param-unresolved-in-class-abstract-template
[P p70]: its title and summary are built on :120 / unknown type: PT, which is
reproducible only under the direct probe. Whoever takes it should run the
falsifier above first. If :120 is a direct-compile artefact, the real corpus wall
is :135 / TArray — a generic array template (TArray<T> = array of T,
declared at :57 of the same file) failing to resolve where a generic class
template on :133 succeeds — and the ticket wants retitling, not just re-probing.
THE RULE THAT COMES OUT OF THIS, AND IT IS BETTER THAN THE ONE I WROTE
Mine was "a corpus stop that moves to a lower line number is the ordinary signature of a syntax fix". It held. frank-rust's corollary is the load-bearing half:
While an abort stands, every figure behind it is UNFALSIFIABLE, not false. A probe that cannot reach a line cannot report on it, and the honest verdict is "masked" — a third value that is neither reproduces nor does not reproduce. Its ticket collapsed three states into two, and that collapse is what turned a correct measurement into a wrong retraction.
And the discipline every corpus figure in this file now owes: state the PROBE, the SHA and the BINARY. Two lanes measured the same file on the same day and got different walls; neither was careless; and the only reason the discrepancy surfaced is that one of them refused to accept a number from the other. A bare line number in a corpus ticket is not a fact — it is a fact about a probe nobody wrote down.
2026-08-30 (coordinator, third pass) — MY RECONCILIATION WAS WRONG, AND SO WAS THE CORRECTION TO IT
Two hours, three versions of this table, and the third one says we do not know. That is the accurate state, and it took two lanes refusing each other's numbers to reach it.
What is now established, each verified rather than relayed:
- pxx refuses a standalone unit. Verified here on an unrelated file so the
check shares no upstream with either claim:
pinned lib/rtl/aesgcm.pas→pascal26:2: error: this file is a unit, not a program — compile a program that uses it (pxx has no standalone-unit output). So the done ticket's$ pinned generics.collections.pascannot have produced any of its output; it is shorthand written for a reader, standing where evidence appears to be. - The mode hypothesis was dead in the source, not in a run.
generics.collections.pas:29is{$MODE DELPHI}{$H+}— the unit sets its own mode and inherits nothing from whoever compiles it. I proposed a run to settle a question the file answers, which is the playbook's own complaint about reasoning where measuring was cheaper, one level up: I reached for a probe instead ofsed -n 29p. - And the reconciliation I built on that is not merely unsupported, it is
contradicted.
bug-p-generic-type-param-unresolved-in-class-abstract-templaterecords its repro as "a one-line program that only doesuses Generics.Collections" — the same probe frank-rust ran. So "the same file has different walls depending on how it is entered" has no second entry point in evidence. Both lanes ran ausesprogram and got different first errors. - The two binaries were functionally identical.
git log d23f52948..ea5a8ef96 -- compiler/is empty: only board and docs commits separate them. So a compiler change between the runs cannot explain the difference either.
Every explanation on the table is therefore gone, and the difference is real and unexplained. The remaining candidates are all about the run nobody wrote down — an unrecorded flag, or a binary that was not the sha its lane believed. Only frank-user can settle it, because only frank-user has the shell history.
THE FOURTH MISSING VALUE: A RECORDED INVOCATION THAT DOES NOT RUN
frank-rust's companion to "masked", and it is the sharper of the two:
A command nobody re-executes is not evidence, it is a claim with a
$in front of it.
Both tickets carry a shell block as their repro. One of them cannot run. The
check is free — paste it and press enter — and neither of us did it for two
hours, through three ticket edits and four messages, while both of us were
explicitly arguing about evidentiary standards. A fenced block beginning with $
reads as executed; nothing in the format distinguishes a transcript from a
paraphrase, and the paraphrase is what a careful person writes when tidying up.
Same family as face 222 (a test that exists, passes elsewhere and is unwired) and face 212 (the reassuring answer must be inexpressible): the artefact that looks most like verification is the one least likely to be verified.
WHAT ANYONE TOUCHING THE CORPUS FIGURE MUST DO NOW
- Do not retitle the p70 ticket. frank-rust asked for that hold and is right —
retitling on the strength of its
:135would be the same move as its adopting my:120, in the other direction. - The only reproducible facts are frank-rust's, on two binaries it can name:
through a
usesfrom a{$mode delphi}program with-Fu,pinnedstops at:146and HEADea5a8ef96(binary6319b892f517) stops at:135. - Every corpus figure written here from now on states the probe, the sha, the binary — and is pasted from a run, not reconstructed.
UNBLOCKED — rung 6 is available (coordinator, 2026-08-30)
The status line said "parked, rung 6 blocked" while its blocker sat in
decided/. Corrected in place rather than only here, because a note at the
bottom of a long ticket does not reach the person who reads the status line and
moves on — the same failure frankB reported today about a frontmatter summary
outliving what it summarised, one field over.
decide-revisit-object-types-rtl-generics-fired-the-trigger [U p70] is decided:
option C. generics.collections.pas's single = object — no fields, no
inheritance, no virtual methods, no constructor — becomes a TObject, and the
measurement backs it: identical output on every use (widening assignment from any
class, cast-back with virtual dispatch, array of, record field, parameter, nil
compare) at code=63287B data=4276B bss=42532B, unchanged. TObject is strictly
better here, permitting Free/ClassName/Destroy without the cast the bare
reference required. The decision's original cost driver — a second object model
with its own storage, lifetime, assignment and VMT — was never what this rung
needed.
Residual risk, carried forward from the decision and accepted by the owner:
Pascal source outside this checkout using var x: object would break. Four
uses in this repo, all in its own tests.
Also from that decision's Consequences, not yet done: [[feature-p-legacy-value-object-types]] [P p15] is framed against option B's full scope and should be rewritten to option C's scope or closed in favour of the new P bug. Not urgent at p15, but it will mislead whoever reaches it.
Ownership: owner: frankA is stale — frankA has taken
bug-c-a-c-function-s-calling-convention-depends-on-the-target. Re-claim
before the first commit; resuming parked work is the one transition with no
PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.
prompt to re-claim.
CORRECTION to the note above: I described option B, which the owner REJECTED
Caught by frankB before it built anything, 2026-08-30. The unblock stands. The characterisation of the decision does not.
I wrote "option C … its single = object becomes a TObject". That second
half is option B, and the decision names it as rejected, verbatim: "Option B
is rejected for the reason the owner gave: object is not TObject and neither
FPC nor Delphi treats it as such. B prices inheritance, VMTs and a constructor
protocol that the corpus does not use and that nothing has asked for."
Option C, verbatim: "the rooted-reference object is RETIRED rather than
kept alongside it. object gets its standard Object Pascal meaning — a value
type, i.e. a record with callables — with a hard error on inheritance,
virtual, constructor and destructor rather than silent half-support."
How I got it wrong, since the mechanism is reusable: I read the file's tail
and its summary:, found a measurement table comparing a TObject version
against an object version, and reported it as the ruling. It was evidence
inside the analysis, not the answer — and the answer was in a # DECIDED
section I never opened. Reading a fragment of a long ticket and stating its
conclusion is the exact failure this board has logged three times this week; the
fragment I chose was the one that looked like a result.
And it is already implemented. = object lands at pasparser_decl.inc:5745
as a VMT-less value type (UClsIsRecord := True, hard error on an ancestor),
closed as [[bug-p-object-value-types-standard-meaning]] in done/. Measured by
frankB at HEAD 4f42b78b9, not read: a value-type object with methods
compiles, runs, prints 7 8, SizeOf 8 — no VMT pointer; and TB = object(TA)
gives "an object type cannot have an ancestor -- pxx lowers object as a value
type with no VMT …; use a class to inherit." Both arms live.
So there is no object work in this ticket. Anyone building to my note would have implemented the one thing the owner explicitly ruled out, against a decision file that says so.
Also corrected: no lexer.inc collision on this ticket. object is not a
lexer keyword — it is handled in pasparser_generic.inc and
pasparser_decl.inc, both of P's own carved-out files. The generic A/P
shared-lexer caution does not bind here.
The actual wall (frankB, HEAD 4f42b78b9 / pinned faf762981c3c)
rung 6a generics.defaults.pas ok [671512B code, 1661 procs, 25s]
rung 6b generics.collections.pas ERR "unknown type: TKey" [75s]
Rung 6a is NOT yet claimed clean, deliberately. generics.defaults compiles
standalone and fails to survive being used from collections. The obvious
reading is "defaults is fixed, collections is the wall" — but an uninstantiated
generic body may never be type-checked at all, in which case the standalone green
is vacuous and says nothing about that file. frankB has a control running (same
unit with TComparer<Integer>.Default actually instantiated) to tell those apart,
and is withholding the wall until it reads. A clean table with no control is what
burned the ESP float arm.
The wrong-file diagnostic frankB hit on the way is filed separately as [[bug-p-a-deferred-generic-body-s-diagnostic-names-the-wrong-file-and-line]].
2026-08-30 (frankwasm) — INDEPENDENT corroboration of the note above
frank-rust's measurement and mine were made separately and agree. Recorded because two independent arrivals at the same number is the only kind of confirmation worth having, and because my run carries a control theirs does not (and theirs carries one mine does not — the locally-declared generic interface).
Not a claim on this ticket. Measured before deciding whether to take it, and it changed the answer to that question.
binary at HEAD (post-28b2851cd), -Fu/usr/share/fpcsrc/3.2.2/packages/rtl-generics/src
6a uses Generics.Defaults ok code=671512B procs=1661
6b uses Generics.Collections pascal26:259: error: unknown type: IEnumerable
(and the `expected ')' before '>'` that follows it)
TKey is gone — zero occurrences, where the summary said four unbound
identifiers. bug-p-the-rtl-generics-corpus-stops-on-tkey-in-a-tlist-body is
in done/, closed by 28b2851cd (frank-rust): TList<T>'s template capture
overran by ~10,914 tokens because a bodiless nested class ran the depth loop one
level high. The summary here still told readers to trust that ticket as the live
wall and not to open a new one.
6a's numbers are byte-identical to the ones this file already recorded
(code=671512B procs=1661), which is the control: the corpus and the flags are
the same, so the difference at 6b is the compiler and not the measurement.
What this means for whoever takes the ladder
The remaining wall is not a frontend bug. FPC supplies generic IEnumerable<T> / IEnumerator<T> from the implicit ObjPas unit; our RTL
declares neither, and generics.collections.pas:259 specializes one. That is
bug-b-rtl-provides-no-ienumerable-generic-interface [B p55] — Track B, not
P — with 28b2851cd serving as the control that the compiler handles the
shape. So "take the corpus ladder" currently means drive a Track B ticket, or
work a different rung, and that should be a knowing choice rather than a
discovery three hours in.
Left in unfinished/ and still owned by frankB. Nothing here is claimed; the
summary is corrected because rule 2 requires it and this file is the one that
tells its own readers to trust only the canonical table.
2026-09-05 (frankB) — RUNG 6a WAS NOT GREEN, AND THE RECORD IS WHY NOBODY KNEW
Took this off next --track P. Re-measured rung 6a at tip 36d7e5fd4 before
doing anything with it, and it failed:
pascal26:1064: error: unexpected token in a unit implementation section:
it starts no declaration (a mistyped section header?)
in: .../generics.defaults.pas
near: ( AComparison ) ; end ; >>> THashService$TDelphiHashFactory specialize
$ cannot occur in Pascal source, so that is a MINTED alias spliced into the
token stream. 6b stopped in the same file rather than its own, so the wall
everyone was driving toward had moved backwards.
Fixed: [[bug-p-a-method-pointer-type-derails-the-delphi-generic-alias-anchor]].
DGenDeclAnchor counted the object in of object as opening a type body;
Generics.Defaults has five. Regression from b613b5fcf (2026-08-31), the day
after this file recorded 6a green. Bisected over 3425 commits.
Current state, measured, not cited
| rung | result |
|---|---|
6a generics.defaults |
compiles end to end — code=687896B data=138472B bss=127228B procs=1780, 16s |
6b generics.collections |
for-in: enumerator has no readable Current at generics.collections.pas:1481, 3m31s |
6b is back at the wall this file's summary already describes (a pin-ordering
dependency on IEnumerator<T>.Current, not a frontend bug). 6a's figures do
NOT match the 2026-08-30 record (671512B / 1661 procs) and are not claimed
to: six days of codegen moved in between. Equality would have been the surprise.
What this file should take from it
The two 2026-08-30 arrivals at code=671512B procs=1661 were genuinely
independent and byte-identical, which is the strongest corroboration this file
has ever had for any row — and corroboration is about the READING, not about
the AGE. Two correct measurements of a tree that no longer exists look exactly
like the discipline working. The better-confirmed a rung is, the more it gets
re-CITED rather than re-MEASURED, so the best-established rows are the ones most
likely to be stale.
A ladder's recorded rung reads as a floor — the thing already achieved — so a LATER rung failing inside an EARLIER rung's file reads as new depth rather than as regression. That is the tell to watch for here specifically: if rung N+1's error names rung N's file, re-run rung N before reading anything else.
Two confounds this file has recorded three times were checked before calling it a regression, and both mattered:
- Is it the same program?
md5sumonlibrary_candidates/...and/usr/share/fpcsrc/3.2.2/packages/rtl-generics/src/...— identical for both units. The three "THE TWO PROBES ARE DIFFERENT PROGRAMS" incidents above are all this check. - Is it the same invocation? Reproduced under frankwasm's exact recorded
-Fuform, not only under the one in "How to re-measure". - The PIN IS NOT A CONTROL HERE. It cannot build the current
lib/rtlat all (typinfo.pas,a value of this type has no members). A baseline that fails HARDER than expected is invisible as a control and reads as corroboration; read what a red baseline actually says before counting it.
And a note for anyone bisecting this compiler
Seeding each bisect step from the binary the previous step left behind does not
work and is not neutral: the compiler doing the building then changes at every
step, and a tip compiler refuses six-day-old compiler sources outright
(LoadFile expects string variables in IR codegen in cpreproc.inc). That
appeared as five consecutive build SKIPs and a range that never narrowed — a
degenerate bisect wearing the shape of a running one. Reseed from the PIN at
every step (cp stable_linux_amd64/default/pinned compiler/pascal26, touch
the sources, then make), assert the log says converged after rather than
verified, and it converges normally.
Parked 2026-09-06
Nobody holds it: the body has said 'owner: frankA is stale' since 2026-08-30 and the frontmatter owner has been empty ever since. working/ + no owner is the one board state invisible in both directions -- out of ready, attributed to nobody -- so at p75 this was the highest-prio P row in existence and absent from the campaign's own count. Parked so it ranks again; RESUME by re-claiming, the ladder itself is live and episodic.
Before resuming: read the reason above, then the ticket body. If the reason does not tell you what would make this worth picking up again, establishing that is the first step -- a park is a handoff to a stranger who may be you.
2026-09-06 (frankD) — RUNG 7 ATTEMPTED for the first time; wall 1 cleared
fcl-passrc is on this box at /usr/share/fpcsrc/3.2.2/packages/fcl-passrc/src
(no fetch needed — it is not under library_candidates/, which holds only
busybox / c-testsuite / fpc-testsuite / lua in this checkout).
Dependency order is pscanner (5333) → pastree (5947) → pparser (7823) →
pasresolver (29660). Start at pscanner; a driver program is required
because pxx refuses a standalone unit.
./compiler/pascal26 --mimic-fpc -Fu$S -Fi$S driver.pas out
--mimic-fpc IS REQUIRED and its absence looks like a frontend bug
Without it the first error is
FPC_FULLVERSION has no integer value here, so it cannot be compared. That is
my invocation, not a defect — paslexer.inc:1010 defines it as 30202 under
the mimic profile. paslexer.inc:1402 already records this exact confusion from
2026-09-05 on cfileutl.pas. An undefined symbol pushes on the conditional
stack as a BOOLEAN, so a missing define presents as a type mix. Recorded here
because the next person attempting a rung will hit it in their first minute.
Wall 1 — CLEARED, c4036925a
pscanner.pp:74: a const section followed by resourcestring ate the keyword
as the next constant's NAME. Two hand-maintained terminator lists of one concept
had drifted, and the comment on the broken one already claimed to mirror the
other. resourcestring and label fixed; threadvar is in the list but still
refuses on its own ticket (unsupported at any scope — the control is that it
refuses identically with NO preceding const).
Wall 2 — FILED, not fixed
Rung 7 now reaches pscanner.pp:893 and names two:
Args: array of constin a method-pointer type → [[bug-p-array-of-const-in-a-method-pointer-type-is-refused-and-parsing-it-is-the-trap]]. The three-line parse fix is written, measured and REVERTED: it makes the declaration compile while the indirect call printsLength=4025888forn=3. Read that ticket before touching the parameter loop.property ... default DefaultMaxIncludeStackDepth;— a propertydefaultclause taking a named constant. Not yet characterised; no ticket.
Wall 1 to wall 2 is +819 lines, past the whole const and type section.
Carrying the file's own standing lesson forward
This section is a dated snapshot like every other wall table here. The :74 and
:893 coordinates were true at c4036925a / binary eda82aef9249. Re-run the
rung before trusting either — and note both were reported against pscanner.pp
by name, which is the reliable half; the ticket warns the LINE and the near:
have taken turns being wrong.
2026-09-06 (frankD) — wall 3 characterised, filed, not fixed
pscanner.pp:893, property MaxIncludeStackDepth: integer read ... default DefaultMaxIncludeStackDepth;. Three defects behind one if at
pasparser_decl.inc:5711, and only the first is the wall:
- the value must be a constant EXPRESSION —
default <named const>,default <expr>andnodefaultare all refused, fpc compiles all three; - the two unrelated
defaultclauses share one arm, so a scalar value clause setspropIsDefault; - consequently a scalar
default 16declared BEFORE a realproperty Items[i]; default;steals the default-property slot.
(3) is order-dependent and that is the part worth carrying. Declare the
indexed property first and both compilers print 100; declare the scalar one
first and pxx refuses what fpc accepts. The natural way to write the example
hides it. Even with no indexed property at all the flag is visible in the
diagnostic — pxx says default property is write-only where fpc says
No default property available.
Not fixed here because it is three changes and one unknown: where the LITERAL is currently consumed is NOT established, and a constant-expression parse dropped in beside whatever is swallowing it would fight it.
2026-09-06 (frankS, relayed by frankD) — the skip list is an inventory of what the suite SPELLS
frankS, working the array cluster: tarray15/tarray16 are both about the
DECLARATION spelling of a dynamic-array initialiser
(var a: array of LongInt = (1,2,3)). The STATEMENT spelling — a := [1,2,3] —
was checked as a CONTROL, expecting the working case that made the declaration
case a gap. It compiled clean, printed len=435728179526, and segfaulted. It
was not on the skip list and never had been.
A missing name announces itself, a wrong branch does not, and a form the corpus never spells has no row to be wrong on. The skip list is an inventory of what the FPC testsuite happens to write. Where the suite writes only one of two spellings of a construct, the other is not measured as passing — it is not measured. 392/0/108 is a true number about 550 files and it is not a claim about the language.
frankS's operational consequence, and it is the reusable part: a new corpus
test has more leverage when it is the SIBLING SPELLING of a construct the suite
already covers than when it is a new construct. Both spellings of dyn-array
init, both spellings of a set/array literal, statement vs declaration, alias vs
anonymous type, bare vs qualified receiver. Two segfaults in one day, neither
with a row. (Fixed: 4640dc523 statement form, 0455ce596 declaration form.)
Corroborated the same day, twice, from this ticket's own rung 7 — and note that in both cases the WORKING spelling is what made the broken one invisible:
| construct | spelling that worked | sibling that did not |
|---|---|---|
SizeOf of a field in a method |
SizeOf(Self.FBuffer) |
SizeOf(FBuffer) (f8b0e0098) |
.Free on a computed receiver |
b.FA[i].Free |
b.Objects[i].Free, b.Pick(i).Free (ticket) |
The SizeOf case carries frankS's point further than a missing row does. Fixing
the refusal exposed a SECOND divergence in the same pair: with a global of the
field's name in scope, SizeOf(G) answered about the GLOBAL (100) while
Length(G) on the identical name answered about the field (16). fpc says 16 for
both. That one was never a refusal at all — a plausible number, no
diagnostic, and it would have survived the first fix untouched. So the sibling
rule is not only "the other spelling may be missing a row"; it is "the other
spelling may be answering a different question", which no skip list can
represent.
Practical rule for rows added under this ticket from here on: when a row is written for construct X, write the sibling spelling of X in the same file, and assert a VALUE on both rather than a clean compile. A refusal-only row is green for the whole of the second failure mode above.
2026-09-06 (frankD) — RUNG 7: pscanner.pp COMPILES, LINKS AND RUNS
Fourteen walls to zero. /usr/share/fpcsrc/3.2.2/packages/fcl-passrc/src/pscanner.pp,
5333 lines, --mimic-fpc -Fu<that dir>:
ok: rung7a [code=548632B data=116044B bss=88828B procs=1129]
AND THEN IT HUNG, WHICH IS THE FINDING WORTH MORE THAN THE ZERO. A driver
that constructs a TPascalScanner over a real file and pulls tokens never
returned. fpc's build of the same driver prints ident: K / number: 42 /
tokens>0: TRUE and exits 0. Ours now prints the same three lines.
The cause was one missing lookup, and it had two faces:
{ TFileLineReader (pscanner.pp:433): FTextFile is a Text FIELD }
WriteLn(F, 'x') -> printed `3x` ON THE CONSOLE, left the file EMPTY, exit 0
ReadLn(F, s) / EOF(F) -> read STDIN, so EOF never came and the program HUNG
IOHandleSymAt asks FindSym; a field has no Syms[] row; TextIOFileSym
answered -1; the CONSOLE path took the call. The whole Text lowering was keyed
on a symbol index (GenMakeIdent(fileSym, tyRecord), seven sites), so there was
nowhere for a field to go. Fixed by threading the handle as a NODE and adding a
designator-level detector.
What this rung is now teaching, and it is not about pscanner
A CORPUS RUNG MEASURED BY "DOES IT COMPILE" IS MEASURING THE WRONG THING, AND
EVERY ROW OF THIS LADDER HAS BEEN SCORED THAT WAY. Zero errors is where this
unit's most expensive defect STARTED: the compile was clean, the link was clean,
the binary ran, and the library did nothing. A wall count cannot see it —
there is no wall. Neither can expect_same on a compile, nor an exit code,
because the failing shape creates the output file and leaves it empty at rc=0.
Always drive the unit. For a parser library that is ten lines: build an input, run the public entry point, assert a value fpc also produces. Every rung above 4 should have one and none of them does.
The two halves also differ in how they present, which is the reusable half:
| how it fails | how it looks | |
|---|---|---|
| write to an unrecognised handle | goes to the console | file exists, is EMPTY, rc=0 |
| read from an unrecognised handle | reads stdin | HANGS, or "0 lines" under a redirect |
The write half is the dangerous one and the read half is the one that gets
found. Under a harness that redirects stdin from /dev/null the read half stops
hanging and reports zero lines — a plausible number — so a CI runner would
have turned the loud failure into the quiet one.
The six landed fixes on this rung
| # | fix | commit |
|---|---|---|
| 1 | a const section ate the resourcestring that ended it |
c4036925a |
| 2 | array of const in a procedural type |
87681a64a |
| 3 | a bracket argument at a bare-self method call | (bracket door) |
| 4 | a nested routine reaches all five of its class's member kinds | (member kinds) |
| 5 | a nested routine assigns the enclosing function's result by NAME | f45be9b78 |
| 6 | uses sysutils closed the dynamic-array Delete/Insert |
f5ad23c32 |
| 7 | .Free on a computed receiver, evaluated exactly once |
a5588f5c6 |
| 8 | a Text handle reached through a FIELD |
this commit |
Plus 181576cdc (frankB, the decl-path bracket instance) and 475528dae
(frankH, removing the sysutils declarations at the source).
The next unit on this rung is not pscanner. pastree.pp and pparser.pp
are the remaining bulk.
2026-09-06 — pastree.pp (5947 lines) attempted: four walls, two down
The first pass reported two, and clearing them revealed two more — the same "invisible until the one before it lands" shape this rung has produced twice already, so four is a lower bound, not the count.
| # | line | shape | state |
|---|---|---|---|
| 1 | 2101 | ReleaseAndNil(TPasElement(InterfaceName)) — a class typecast as a var argument |
FIXED |
| 2 | 2397 | a field named ExceptObject vs sysutils' function ExceptObject |
FIXED |
| 3 | 2979 | a receiver-less Free; inside a method |
FIXED |
| 4 | 4940 | NameParts.Assign(Parts) — no TFPList.Assign |
FIXED (lib/rtl) |
| 5 | 5817 | Fields := nil on an array of <record> FIELD |
FIXED (two defects) |
Walls 1 and 2 were both enumerated predicates — a hand-maintained list that
must grow per new member and gives no diagnostic when it does not — and that is
now the third and fourth instance found on this rung. IsVarArgLvalueCast
named AN_PTR_CAST while the backend had peeled AN_CLASS_CAST for months;
the member-shadow rule named FindUMeth while fields, properties and class
vars are equally members.
WALL 2 IS THE ONE WORTH CARRYING, AND NOT FOR THE FIX. The rule lives in
two copies — pasparser_expr.inc:5167 for the read, pasparser_stmt.inc:7858
for the write — and the statement copy's own comment already said "the two
MUST move together". They had drifted anyway. The two copies emit
by-reference argument must be a variable and cannot assign to the result of a function call respectively: two diagnostics, neither naming resolution,
one cause. The corpus only ever shows you the arm it happens to use — pastree
reads the field into a var argument, so the read arm was the whole visible
defect, and the write arm surfaced only because the fixture asserted an
assignment. Landing the read fix alone would have left a shadowed field
readable and unwritable: a state worse than the one it replaced, and one the
corpus unit would have scored as progress.
Wall 3 is the receiver-less sibling of today's .Free work (a5588f5c6
materialised a computed receiver; a bare Free; in a method never gets as far
as a receiver) — and with it, five recognisers now serve one concept, four
of them keyed on the shape of a RECEIVER, which is why the spelling with no
receiver matched none of them. Wall 4 was an outright RTL absence: our TFPList
is an empty descendant of TList and Assign was on neither.
WALL 5 ARRIVED WITH NO COORDINATE AT ALL AND THAT WAS ITS OWN DEFECT. It printed
pascal26:0: error: incompatible types: cannot assign Pointer to record
and that was the whole of it — no in: <file>, no near: window, because
ErrorPrintAt drives all three off the line number. Every earlier wall on this
rung came with a coordinate that was at least a starting point, including the
ones this file warns are untrustworthy; this one had none, in 5947 lines.
The cause was not this unit and not this statement. AllocNode stamped
ASTLine := 0 for every node in a usesd unit — a correct DWARF decision (the
RTL must contribute no line-table rows) that was ALSO serving as the coordinate
for every semantic diagnostic. So this was never a pastree problem: it was
every semantic error in every corpus unit anyone has ever run, and the rungs
kept producing usable numbers only because PARSE errors are reported off the
lexer's own position. Split and fixed —
[[bug-a-a-semantic-diagnostic-in-a-used-unit-has-no-location-at-all]]; the first
diagnosis was wrong and is corrected in
[[bug-a-a-desugared-assignment-reports-a-type-error-with-no-location-at-all]].
With a line, wall 5 reduced in one step to Fields := nil on an
array of <record> FIELD, and it was two defects with the first hiding the
second: the assignment kind check typed the field as its ELEMENT and refused
it, and once that false reject was gone the store zeroed four bytes over an
eight-byte array handle and SIGSEGV'd. A false reject was load-bearing —
nothing had ever reached the lowering behind it.
[[bug-a-a-nil-assignment-to-a-dynamic-array-field-is-lowered-as-a-record-zero]].
pastree.pp IS DONE — COMPILED, LINKED AND DRIVEN
All four walls cleared. And the driver is the claim, not the build: the compile
probe for this unit is program rung7b; uses pastree; begin end., it reported
ok: with a code size and a proc count, and it would have reported exactly
that if the unit did nothing at all — the same empty-driver trap this file
recorded for pscanner.pp eight hours earlier, walked into again in a file I
wrote myself. A real driver — construct a TPasModule, a TPasVariable, a
TBinaryExpr, a TRecordValues; print GetDeclaration; AddRef/Release
down to zero — matches fpc 3.2.2 byte for byte:
module = MyUnit / TPasModule / module
variable = Counter / variable
parent = MyUnit
binexpr = 41 + 1
recvalues = 2 / (a: 7; b: 8)
released = TRUE
afteraddref = Counter
done = TRUE
That run exercises three of the four fixes on purpose: TRecordValues.Destroy
is the Fields := nil path, Release ends in the receiver-less Free;, and
the tree it builds is the one TFPList.Assign populates.
2026-09-06 — pparser.pp (7823 lines) attempted: two walls, one down
| # | line | shape | state |
|---|---|---|---|
| 1 | 286 | FTokenRing: array[0..FTokenRingSize-1] — a CLASS CONST as a field's array bound |
FIXED |
| 2 | 635 | var CCNames: array[TCallingConvention] of String = (...) — a local var-section array initializer |
open, filed not fixed |
Wall 1 is the same shape as everything else on this rung: the const evaluator
recovers a class const from a const SECTION and from a METHOD BODY, and a
field declaration is in neither. ParsingClassBodyCi already spanned the gap.
The diagnostic was not a constant — about the expression, for a defect in
scope, so it pointed at the bound and not at the lookup.
Wall 2 is filed rather than fixed, and the measurement is why. The same
declaration spelled const in the same routine compiles and runs correctly
today — so the element loop, the enum-indexed bound, the string elements and
the routine-local flush all already work for exactly this shape, and only the
var path never got an entry to them. A door, not a capability gap. But the
const array initializer is ~315 lines INLINE in ParseConstSection, entangled
with that routine's locals, and it parses the TYPE as well as the initializer —
which the var path has already done. A reusable
ParseArrayInitializerInto(symIdx, isLocal) has to derive the dimension list
and element kind from the already-declared SYMBOL rather than from the shared
parse, and that is the step that stops being mechanical, because each
derived value has to be verified equal to what the parse produced rather than
assumed. Banked rather than half-done:
[[feature-p-a-local-var-section-array-initializer]].
Next on this rung: pparser.pp behind wall 2, then pasresolver.pp (29660).
2026-09-06 — rung 7 pparser.pp: THREE errors to ONE (frank-coord-core)
Re-measured, not inferred, at binary 6950458c2da2 (pin v407 seed + HEAD):
$ pascal26 --mimic-fpc -Fu/usr/share/fpcsrc/3.2.2/packages/fcl-passrc/src \
-Fulib/rtl -Fulib/rtl/platform/posix rung7c.pas rung7c
pascal26:2670: error: no overload of PeekOper$62727 matches these arguments
One error. 9.6s. This file recorded "THREE errors from TWO causes"; the
second cause is closed and the .Name lookup on a dynamic array's record
element is gone, so the bogus CompareText arity report it poisoned into is
gone with it.
The cause was
bug-a-a-dynamic-array-of-class-loses-its-element-type-when-it-is-a-parameter,
fixed at 86852f93a. TPasUsesClause = array of TPasUsesUnit arriving as a
parameter lost its element's class identity, because the parameter
classification chain had an arm for array of RECORD and none for array of CLASS, so the class case fell into the SCALAR arm and its element rec id went
to RecName instead of ElemRecName.
CORRECTED, at frankD's insistence and against my own first wording here. I
wrote that this file had PREDICTED that cause and that the prediction held. It
had not, and frankD said so rather than accept the credit. What this file
recorded — and what DID hold — is the CASCADE reading: that the .Name failure
and the bogus CompareText arity report were ONE cause counted twice, and that
it sat at a .Name lookup on a dynamic array's element. The mechanism (element
is a class AND the array is a parameter; parr and tyRecord present, parr and tyClass missing) is nowhere in what was written here.
Their reason for correcting it is the one that matters on this rung:
cascade-versus-two-defects is cheap to read off an error list; a missing arm
is not, and this rung has already had one wrong subject travel for a day
because a near: window from the wrong file read as content. A track record
that rounds up is how that happens twice.
The survivor is :2670 and its report is partly a lie. Filed as
[[bug-p-the-two-halves-of-an-overload-report-spell-an-array-argument-differently]]:
the argument half prints a raw TypeKind and the candidate half is IsArray-aware,
so a CORRECT array of record argument reads as a mismatch. Reduced to 12
lines. In :2670, argument 3 is fine and argument 2 is the whole
mismatch — consistent with the wrong capture actuals
[[bug-p-a-sibling-call-to-a-capturing-nested-function-gets-the-wrong-capture-actuals]]
describes. Do not reduce it believing two arguments are bad.
Caveat this file already states and which applies to this row too: in: names
pscanner.pp while the construct is in pparser.pp. Unchanged, and still the
coordinate problem, not a new one.