← board

type T = type byte; — the distinct-type declaration is not parsed

type TMyB = type byte;
var x: TMyB; c: cardinal;
begin c := $12345678; x := 5; WriteLn(x, ' ', TMyB(c)); end.

FPC: 5 120. pxx: error: unknown type: type at the declaration.

type T = type Base declares a type that is layout-identical to Base but DISTINCT from it for overload resolution, RTTI identity and var-parameter matching — the standard Pascal idiom for a strong typedef. Plain type T = Base (an alias) parses fine; only the = type form does not.

Loud, so ranked low per CLAUDE.md's compat table ("FPC accepts a form we reject" → compat, ranked by how much real code uses it). It is common in FPC RTL headers and in Delphi-lineage code, so it will recur.

Found beside [[bug-p-a-cast-through-an-ordinal-type-alias-does-not-truncate]] while reducing rung 3 of [[feature-pascal-corpus-oop]] — deliberately filed apart, because that one is a silent wrong value and this one is a parse error, and folding a loud gap into a silent bug is how the silent half gets lost.

Gate

The program above matching FPC, plus a check that the distinct type is actually distinct where that is observable (a var parameter of Base must not accept a T, once overload/parameter matching is in scope).

Re-ranked 25 → 55 (owner, 2026-09-04)

The owner named this ticket unprompted as one he considers relevant, with the reason the compat table asks for — "sometimes programmers have reasons to re-type". That is the "how much real code uses it" input, supplied by the one person entitled to supply it, so the rank moves.

Not to 70: it stays below its silent sibling [[bug-p-a-cast-through-an-ordinal-type-alias-does-not-truncate]] (prio 70, now done/), because that one returned a wrong VALUE and this one refuses to compile. Loud beats silent. But 25 was too low for a form that stops a program building outright.

Premise re-verified before re-ranking, on stable_linux_amd64/default/pinned (v403), not HEAD:

pascal26:1: error: unknown type: type
  near: type TMyB = >>> type byte ;

fpc on the same source prints 5 120. So the ticket's report is still exactly true a week on: it is a PARSE gap, not a silent alias. Recording that because the two are easy to conflate — the silent-alias behaviour people remember belonged to the sibling, and that half is already fixed.

PARSED 2026-09-04 (frankB) — and the distinctness half is a separate ticket

compiler/pasparser_decl.inc, in ParseTypeSection right after Expect(tkEq): the type keyword is consumed ahead of the whole type-binding chain, so every right-hand shape takes the arm it would have taken without it and no arm below learns a new case. type helper for T is the other meaning of tkType in exactly this position and is excluded by name.

test/test_distinct_type_decl.pas covers one RHS arm each — a builtin scalar, a distinct type OF a distinct type, a pointer alias, an array type, a managed string, a record, a procedural type — plus the ticket's own repro. Every row matches fpc 3.2.2 exactly; the pin refuses the file at line 27.

THE CONTROL COULD NOT LIVE IN THAT FILE, which is worth recording: the guard is written by name (not ... 'helper') rather than as "skip a tkType here", and the failure it prevents is that every type helper stops resolving — invisible to all seven rows above. FPC 3.2.2 has no type helper at all (only record/class helper; it answers Identifier not found "helper" in both objfpc and delphi modes), so a control row for it cannot sit in an FPC-oracled file. test_type_helper_property, test_type_helper_typename_receiver and test_class_helper_for_a_class are that control, all three already wired, all three run green after this change.

The second half of this ticket's gate is NOT met and is now its own ticket. The gate asked for "a check that the distinct type is actually distinct where that is observable". It is observable — measured, FPC prints base then distinct for a two-overload probe — and pxx does not: it warns duplicate definition of 'P' with the same parameter types and binds both calls to one body. That is not fallout from this fix; it is what pxx already does for a plain type TMyB = byte, so this spelling reached an existing loud behaviour rather than adding a silent one. Split out as [[bug-p-a-distinct-type-declaration-is-parsed-but-is-not-distinct]] because the missing piece is a CHANNEL, not a predicate: there is no LastTypeAlias, no Syms[].AliasIdx and no ProcParam alias carrier beside ProcParamRecId and ProcParamSetEnumId, so an alias has no identity anywhere downstream of its declaration. Filing rather than folding, for the reason this ticket's own author gave about its silent sibling: a loud gap and a wrong-binding gap are different work and one hides the other.

Measured while here and NOT a defect: SizeOf(TRec) for record a, b: Integer answers 8 in pxx and 4 in fpc's DEFAULT mode, because FPC's default-mode Integer is 2 bytes. Under {$MODE OBJFPC} both answer 8. The test carries the directive for that reason.

Gate

make compiler/pascal26 converged; tools/gate.sh quick GREEN with the FPC seed canary CONCURRENT.

Log