← board

The mechanism, not the instance

Filed by frank-coordinator from a follow-up frankB flagged while filing bug-p-fatal-directive-is-silently-ignored. Separated deliberately — CLAUDE.md's normalise-dont-special-case rule says to grep for the sibling before closing a double case, and here the sibling is unbounded: fixing {$FATAL} closes that ticket and leaves the mechanism that produced it untouched.

Measured, 2026-08-28:

grep -c "else if CaseEqual(command" compiler/lexer.inc   ->  34

Thirty-four arms (warning, message, error, mode, …) and no terminal else that diagnoses an unrecognised directive. A directive outside the set is consumed and discarded: no warning, no note, exit 0.

Why this is a bug and not a diagnostic-parity nit

Same reasoning frankB used for {$FATAL}, and it generalises: a directive's purpose is to change what the compiler DOES. Ignoring one does not change a message — it changes the artifact, or whether an artifact exists at all. A source that says "this configuration is unsupported, do not build", or that sets a range check, an alignment, a calling convention, gets silently built the other way.

That is the silent-wrong-behaviour escape in CLAUDE.md's compat table, not the deferrable "our diagnostic differs" row.

What the sweep is

  1. Enumerate the directives FPC/Delphi accept that real Pascal in our corpora actually uses, and diff against the 34.
  2. Do not trust a single extraction of either side — the census that found this class in lib/crtl was written twice and each implementation silently dropped a different name (atexit to a (* filter; longjmp to sort -u under a UTF-8 locale). Manufacture a disagreement.
  3. Add the terminal else. Its shape is the real decision: a hard error breaks every source using a directive we do not implement but could safely ignore ({$IFOPT}, vendor-specific pragmas); silence is the current bug. A warning that names the directive is the likely answer, with a small allow-list of known-inert ones so the warning stays meaningful.

Fixed 2026-09-04 (frankS, Track P)

The chain has a terminal arm now. It classifies rather than warning flatly, because the ticket's open question — hard error, warning, or allow-list — has three answers and not two:

A one-polarity switch splits on the SIGN, not on the name{$H+} is pxx behaviour and inert, {$H-} asks for a shortstring default we do not implement. Same for {$A} and for {$B}, where it is + that asks for something we do not do. And the long spellings split the same way, which is the sibling this repo's normalise-dont-special-case rule is about: listing longstrings or booleval as flatly inert would have put the consequential polarity back in the silent set through its other spelling. {$BOOLEVAL ON} = {$B+}, {$LONGSTRINGS OFF} = {$H-}.

Warning, not error, and FPC settled it rather than taste. Measured 2026-09-04: {$PACKRECRDS 1} (the ticket's own class, one letter dropped) gave SizeOf(T)=8 under pxx where {$PACKRECORDS 1} gives 5, silently; FPC says Warning: Illegal compiler directive "$PACKRECRDS" and then lays it out the same 8. The layouts already agreed — the entire divergence was the diagnostic. -Werror reaches it, so a project can make it fatal without a flag of its own.

The census the ticket asked for was written twice and the two mechanisms disagree by construction (a grep -o requiring the letter immediately after $, and a Python walk allowing whitespace and the (*$ *) form). Positive control fired: mech-1 misses {$ mode delphi} and (*$H+*), mech-2 catches both. On the real tree they agree on 54 names, and a static pass over every .pas/.inc/.pp in the repo confirms the only occurrences that would now warn are inside COMMENTS — which make compiler/pascal26 corroborates by building silent.

The test's total count is the silence control and it is the part that matters. The failure value here is silence, so "the inert block stayed quiet" is a row that passes on the broken compiler. test_pascal_directive_unknown_warns.pas puts 19 inert directives above the five that must warn and asserts 2 / 3 / exactly 5 — the third number is the only one that catches an inert directive starting to warn, which is how this diagnostic would get switched off.

It found real work immediately. --mimic-fpc-compiler --target=arm32 now gets past {$i fpcdefs.inc} (see the packrecords ticket) and reports that FPC's own compiler sources open with {$H-} and {$PACKENUM 1}, neither implemented here. Both were silent before. Filed as feature-p-packenum-and-h-minus-for-the-fpc-compiler-corpus.

Log

The lib/ and examples/ claim, re-measured 2026-09-04 with a live control

frankD's point while landing bug-p-a-stray-end-at-unit-implementation-top-level-is-silently-skipped (5981ed071), and it applies backwards to this ticket: a zero census from a probe that was never live reads identically to a real absence — both print nothing. So the positive control is not optional when the answer is zero.

The original claim here — "a static pass confirms every occurrence that would now warn is inside a COMMENT" — was backed by the static pass alone for lib/ and examples/, and that was weaker than it read:

Re-measured with two instruments that fail differently, plus a control drawn from the population the question is about:

  1. Static checker, positive-controlled. A planted file with {$PACKENUM 1}, {$H-} and {$totallybogusdirective} is reported 3/3. Re-scanned by area: lib 0, examples 0, test 4 (the deliberate fixture), compiler 8 (all inside this file's own new comments).
  2. Actually compiling them, which no gate does. 49 example programs built with the NEW compiler: 31 compiled, 18 failed for unrelated reasons. The precondition is asserted — a warning count of 0 over a corpus where nothing compiled is not a measurement. 0 directive warnings across all 31, which pull lib/rtl and lib/pcl in transitively.
  3. The control that proves instrument 2 reaches lib/rtl at all. {$PACKENUM 1} planted into lib/rtl/sysutils.pas, then examples/calc/calcdemo.pas recompiled: reported, at sysutils.pas line 3. Restored with git checkout -- (the safe direction), tree verified clean.

No defect found — but the claim is falsifiable now, and it was not before. Note that pxx has no standalone unit output ("this file is a unit, not a program"), so a first attempt that compiled all 133 lib/** units directly returned 133/133 failures and 0 warnings: a guard that could not fire, printing the answer I was hoping for. That is the same shape, caught by asking what the run would have looked like if the instrument were dead.