← board

A named array-type parameter was a scalar wherever no implementation header repaired it

type
  TDyn = array of Integer;
  TBase = class
    procedure Dy(a: TDyn); virtual; abstract;   { no implementation header }
  end;
var b: TBase; d: TDyn;
begin
  b := TSub.Create; SetLength(d, 3);
  b.Dy(d);      { pxx: no overload of Dy matches these arguments.  fpc: fine }
end.

No interface anywhere. The ticket was filed against interface dispatch because that is where it was first seen, and the abstract-method row above is what says the report named a symptom.

The cause

The four parameter parsers in pasparser_decl.incParseRecordMethodDecl, ParseProcTypeSignature, and the interface- and class-method arms of ParseTypeSection — each wrote

mIsArr := False;
if CurTok.Kind = tkArray then begin Next; Expect(tkOf,'of'); ...; mIsArr := True; end
else mTk := ParseTypeKind;

ParseTypeKind collapses a named array type to a scalar, so the row got IsArray = False and TypeKind = the ELEMENT kind — the field's other meaning, recorded under the wrong one ([[refactor-p-a-parameters-own-kind-and-its-element-kind-are-one-field-and-the-name-says-neither]]). ParseSubroutine (pasparser_proc.inc) has had the FindArrayType arm since named fixed-array parameters were made to work, and the method parsers never grew it.

A method is declared twice. The class body writes the row; the implementation header goes through ParseSubroutine and overwrites it. So the defect was present for every method in the language and repaired for every method with a body in the same unit. It survives in exactly two spellings — an interface method, which has no implementation header at all, and virtual; abstract, which has none either. Same shape as [[bug-p-a-default-value-is-accepted-on-an-open-array-parameter]], where the interface method was likewise the one nobody asked.

Why one spelling was refused and the other crashed

FindUMethOverloadAhead's single-candidate probe (pasparser_call.inc) reads:

if Procs[pi].Params[pj].IsArray then continue;   { array param: no type question }
if ProcParamUntyped[...] then continue;
if argIsNil[j] then continue;
if not MatchParamAccepted(pi, pj, argTk[j]) then ok := False;

With IsArray = False the parameter is asked about by TYPE, and the type on record is the element kind — so a dynamic-array argument is refused. nil skips the same check on the line below, which is why the ticket's original repro compiled and then segfaulted: it reached the call with a scalar marshalling for a dynamic array.

The fix, and the part of it that IsArray alone did not buy

ParamNamedArrayType — one arm, called from all four parsers — consumes the type name and answers with the element kind, the pointee and the dynamic depth.

IsArray alone fixed the refusal and the const crash, and left var a: TDyn through an interface compiling and segfaulting: a var dynamic array needs the by-ref HANDLE ABI, which keys off ProcParamDynDepth, a column the three method parsers never wrote at all. Threaded through, including both Self-shift loops — a column added to a shift loop and not its sibling is the defect that block's own comments already record.

What is still missing, stated rather than implied

ParseSubroutine's arm also records the fixed LENGTH and low bound, the N-D dimensions, and the element-row geometry for an open array of named rows. The three method parsers have no channels for any of it. So a named FIXED array parameter of an interface or abstract method still carries the open-array ArrLen := 1000 placeholder: correct for reading, indexing and High(), which is what the fixture asserts, and wrong for a whole-array assignment inside the callee (bug-array-assign-to-var-param's shape, one declaration site over). Filling those is the rest of the job and wants the durable-param-row treatment rather than three more columns bolted on.

The sibling, resolved

[[bug-p-an-interface-dispatched-call-that-omits-a-defaulted-argument-segfaults]] was filed beside this one with the independence explicitly NOT established, and the honest answer is now measured: they do not share a cause. That one was a missing default-argument FILL in four call-site arms; this one is a parameter ROW written wrong at declaration time. Fixing that one did not fix this one — it was re-measured still crashing at that commit — and fixing this one does not touch defaults.

Log