← board

d: Double = 2.5 fails to parse in a parameter list

Repro

program n;
procedure P(d: Double = 2.5); begin end;
begin end.
pascal26:2: error: unexpected token
  near: P  d  Double  >>>

What narrows it

shape result
procedure P(d: Double = 2.5) error
procedure P(d: Double = 2) ok (integer literal into a Double param)
procedure P(d: Double = 2.5; s: Integer = 1) error, reported at s
constructor C(d: Double = 2.5; ...) same error — not method-specific

The misleading part is the position: with a following parameter the diagnostic points at that parameter, not at the float. That reads as "the second parameter is malformed" and sent the first investigation the wrong way.

Likely cause (unverified — measure before writing this into a fix)

The declaration site parses the default through the integer const folder (ConstEval / ConstEvalTerm in compiler/parser.inc, Int64 throughout). A float token matches none of its arms, so r is returned unchanged and the token is never consumed — which is exactly why the failure surfaces one token later rather than at the literal.

Note the storage is already there and is used by NilPy: ProcParamDefaultIsFloat (defs.inc) documents "DefaultVal holds its IEEE 754 bits, as a float TOKEN does", and DefaultArgValueNode (parser.inc) already rebuilds an AN_FLOAT_LIT from it. So this is a declaration-side gap only — the Pascal parser never sets the flag. Sibling: the string-literal case, [[bug-p-string-literal-default-in-a-parameter-list-is-not-a-constant]], which fails in the same place with a different message.

Fix both together — they are one concept (a non-ordinal literal default) with one missing mechanism, and fixing one arm alone is how the ctor path in the parent ticket stayed broken.

Gate

The repro compiling, plus a value check that the default actually arrives (P called with no argument sees 2.5, not 0) — a parse-only test would pass on a fix that records the wrong bits. Then tools/gate.sh quick. Extend test/test_default_params_methods.pas rather than adding a new file.

Log

Resolution (2026-08-10)

Confirmed as diagnosed: the default was parsed by the integer const folder, which has no arm for tkFloat, so it returned without consuming the token — hence the error landing on the next parameter.

Root cause was breadth, not the missing arm. There are four parameter parsers in compiler/parser.inc — free routine, class method, interface/forward method, record method — and each carried its own copy of the default-value parse. The copies had drifted exactly as normalise-dont-special-case predicts: SET defaults had been taught to two of them, floats to none, and the record-method copy still carried a comment claiming "the same two shapes the other parameter parsers take", which stopped being true when the third shape landed. Adding a fifth arm in one place would have left three sites still broken.

Fix: one ParseParamDefaultValue (string / float / set / ordinal); all four sites call it. The record-method site gained set defaults for free, which it had never had. ProcParamDefaultIsFloat — already defined in defs.inc and already rebuilt into an AN_FLOAT_LIT by DefaultArgValueNode — is now plumbed through all four paths; only the Pascal declaration side had ever been missing.

Regression test: test/test_default_params_methods.pas grew seven checks, one per parser site plus a float default into a Variant parameter (the boxing arm) — 15 → 22; Makefile assertion updated. Every site is covered because a one-site test is precisely what let three copies stay broken.

Gate: tools/gate.sh quick GREEN (self-host fixedpoint + testmgr quick).

Note on the sibling ticket: bug-p-string-literal-default-...-is-not-a-constant was withdrawn before any work — its repro was a shell-quoting artifact of mine (''a'' in a single-quoted bash string collapses to a bare identifier a). String defaults were always fine. Two REAL bugs surfaced while checking that, and are filed separately: