← board

inherited ignores the parent's default parameter values

Measured (pxx stable_linux_amd64/default/pinned, VERSION 374; oracle FPC 3.2.2)

program t;
type
  TBase = class
    n: Integer;
    constructor Create(AN: Integer = 8);
  end;
  TDer = class(TBase)
    constructor Create(b: Boolean);
  end;
constructor TBase.Create(AN: Integer = 8); begin n := AN; end;
constructor TDer.Create(b: Boolean); begin inherited Create; end;   { <-- }
var d: TDer;
begin d := TDer.Create(True); writeln('n=', d.n); d.Free; end.
form FPC 3.2.2 pxx
b := TBase.Create; (direct, default omitted) n=8 n=8 — works
inherited Create; from a descendant ctor n=8 error: inherited call argument count mismatch
inherited Foo; — plain virtual method, same shape runs same error

So the capability (apply a declared default when the argument is omitted) is already implemented on the ordinary call path and is simply not reached from the inherited path. This is the exact double-case shape devdocs/dev/normalise-dont-special-case.md describes: normalise the two call paths onto one argument-filling step rather than teaching inherited about defaults as a second mechanism. Grep for the sibling before closing — the inherited arity check appears to run before defaults are filled in, so inherited with any omitted trailing default is affected, constructors and methods alike.

Why it matters (the real-world hit)

FPC's own fgl.pp — the RTL's generic-container unit, the flagship --mimic-fpc compile target — cannot be used for TFPGObjectList because of exactly this, at rtl/objpas/fgl.pp:1061:

constructor TFPGObjectList.Create(FreeObjects: Boolean);
begin
  inherited Create;                 { TFPSList.Create(AItemSize: Integer = sizeof(Pointer)) }
  FFreeObjects := FreeObjects;
end;

inherited Create + a defaulted parent parameter is the single most common constructor idiom in Object Pascal class libraries, so this one gap is worth more than its size suggests.

Repro / gate

test/fgl/objectlist.pas is the corpus driver, currently skip-listed against this ticket in test/fgl/pxx.skip. Fixing this should let it run — remove the skip line and tools/run_fgl_corpus.sh then enforces the pass against the recorded FPC 3.2.2 oracle output.

Gate = make compiler/pascal26 (self-host fixedpoint) + tools/gate.sh quick.

Rung: [[feature-pascal-corpus-fgl]] · umbrella [[feature-pascal-corpus-expansion]] · method note [[devdocs/dev/normalise-dont-special-case.md]]

Log


RESOLVED 2026-08-26

Fixed exactly as the ticket asked: ParseInheritedCallAST now reuses FillDefaultArgs — the same helper the direct-call path already used — before the arity check runs, instead of comparing argNo against ParamCount on an argument list nothing had finished building.

if (argNo < Procs[mpi].ParamCount) and (argNo >= 1) and
   (argNo <= MAX_PROC_PARAMS - 1) and
   ProcParamHasDefault[mpi * MAX_PROC_PARAMS + argNo] then
begin
  FillDefaultArgs(mpi, argNo, node, lastArg);
  argNo := Procs[mpi].ParamCount;
end;
if argNo <> Procs[mpi].ParamCount then
  Error('inherited call argument count mismatch');

This covers inherited Create;, inherited Foo;, inherited Bar used as a function result, and the partial form inherited Foo(7) where later parameters default.

A second bug fell out of varying the call site

Probing the same defect from the outsided.Foo; rather than inherited Foo; — turned up a separate crash on the ordinary instance-method path: a parenless call to an all-defaulted virtual method segfaulted, while every neighbouring spelling (d.Foo(), d.Foo(5), a zero-parameter virtual, and the same method non-virtual) was correct. Same missing step, other call path, so it landed in the same commit; [[bug-p-a-parenless-call-to-an-all-defaulted-virtual-method-segfaults]] records it, including the CanFillDefaultsFrom trap that made the first attempt at the fix compile, self-host, and change nothing.

Verified

test/test_inherited_and_parenless_defaults.pas (Makefile test_inhdef26) — ten rows covering both bugs, byte-identical against fpc 3.2.2. gate.sh quick GREEN.