← board

Anonymous enum and subrange types are not accepted

Found 2026-08-22 while probing Low/High ([[bug-a-low-high-of-an-ordinal-variable-answer-0-and-minus-1]]) — the probe program would not compile because it declared var e: (eA, eB, eC).

The measurement

fpc -Mobjfpc -O1 3.2.2 vs pxx fa549a775. Each row is a declaration.

declaration fpc pxx before
var e: (eA, eB, eC) ok unknown type
var s: 1..5 ok unknown type
var c: 'a'..'z' ok unknown type: a
var v: record f: 1..5; end ok error
type TR = record f: (a, b); end ok error
var a: array[0..2] of 1..5 ok error
type T = array[1..3] of (a, b) ok error
procedure P(x: 1..5) rejected rejected (agrees)
var a: array[0..2] of Integer ok ok
var r: record x: Integer; end ok ok
var p: ^Integer ok ok
var st: set of Byte ok ok
var f: function(x: Integer): Integer ok ok

The bottom five are the point: ParseTypeKind already accepted an anonymous record, procedural type, pointer, array and set. Enum and subrange were the two forms nobody had reached for.

Root cause

Both grammars existed, in ParseTypeSection, behind a name. T = (a, b) parsed a member list with explicit ordinals, holes and {$SCOPEDENUMS}; T = lo..hi parsed two ConstEvals and retained the bounds for {$R+}. Neither was reachable from ParseTypeKind, which is what every use of a type goes through. So the feature was half-present in the way devdocs/dev/normalise-dont-special-case.md describes: one concept, two spellings, and only the spelling someone had needed was wired up.

The named subrange path even carries the comment "same treatment as an inline var x: lo..hi subrange" — which was aspirational. There was no inline form.

The fix

Because both arms produce the same LastType* state the named forms produce, every consumer — AllocVar, the record-field walker, the array-element path — needed no change at all. That is the test of whether an anonymous arm is in the right place.

Verified against fpc

Value, ordering, comparison and Low/High for an anonymous enum; explicit ordinals ((hx = 10, hy, hz = 20) giving 10, 11, 20); an integer subrange, a negative-low subrange and a char subrange with their own bounds; all four as record-field and array-element types; and for i := Low(n) to High(n) running the right number of times. Byte-identical to fpc -O1 on the same source except one row.

That row is SizeOf(n) for a subrange: 4 here, 1 in fpc, because our subranges are stored at the base type's width — compat-pascal-subrange-storage-size, unrelated to this ticket. Asserted at the pxx value so a change to that rule surfaces here rather than drifting.

Gate

make compiler/pascal26 (self-host fixedpoint) + tools/gate.sh quick GREEN. Test test/test_anonymous_enum_and_subrange_types.pas, 24 assertions, wired into test-core.