← board

Two array parameters at one bracket slot are decided by declaration order

The repro

{$mode objfpc}
type TC = class
  procedure P2(N: Integer; A: array of Integer); overload;
  procedure P2(N: Integer; A: array of const); overload;
end;
...
c.P2(2, [7, 8]);
fpc 3.2.2 -Mobjfpc vr n=2 cnt=2 — the array of const body
pxx at f00d3d230 ints n=2 cnt=2 — the array of Integer body
pin v405 ints n=2 cnt=2pre-existing, not a regression

Reversing the declaration order changes which body pxx runs. That is the tell: nothing about the argument is being consulted.

Why it is not fixed in f00d3d230

That commit made ArgListHasBracketElem return a mask of the bracket SLOTS and narrowed the candidates on them. With one array candidate at the slot the answer is unambiguous and it is taken. With two, the narrowing declines and falls through to the arity path unchanged, on purpose: FindUMethOverloadAhead's probe parses arguments speculatively to learn their types and cannot parse a bracket argument at all — no parameter to bind against, so ParseArgExpr reads the [ as a set literal and Error halts. It therefore has no element type to rank array of Integer against array of const with. Picking one anyway would be a guess wearing the shape of a decision, and the failure mode is a silently wrong body either way.

Unblocking this needs the probe to become parameter-aware, which is the residual of [[refactor-p-the-overload-probe-cannot-see-the-argument-match-channels]] (done, but its own summary says the TypesCompatible widening was not done).

Is fpc even right here?

Unclear, and it matters for the prio rather than for the diagnosis. [7, 8] is two integers and array of Integer is the closer fit by any element-type reading; fpc prefers array of const regardless. No real source is known that wants either answer — this shape came out of a control I wrote, not out of a corpus. Rank it when something real asks for it; what must not happen is someone re-deriving the boundary from scratch, which is why the rows above are here.

Note the neighbouring case, which is NOT this one and is settled: a tySet parameter at a bracket slot vetoes the narrowing outright, because there the [...] really may be a set. fpc calls M(1, ['a']) against M(N: Integer; S: TCh) / M(N: Integer; A: array of const) ambiguous and refuses it; we accept it as the set. Us accepting what fpc rejects is not a defect.

Gate

make compiler/pascal26, the repro above matching fpc, plus tools/run_fgl_corpus.sh 7/7 and tools/run_pascal_conformance.sh 391/0 — the two rows that catch an overload-selection regression; quick alone does not.

The same property exists on the CONSTRUCTOR path (frankB, 2026-09-06)

Widening this ticket rather than filing beside it, because it is one property with two addresses.

ClassCtorArraySigAt (pasparser_call.inc, new at 1c8a6cfd5) scans a class's constructors for one whose parameter at the bracket slot is an array, and takes the first. So

type TC = class
  constructor Create(const A: array of Integer); overload;
  constructor Create(const A: array of const); overload;
end;

decides how TC.Create([7, 8]) is PARSED by declaration order, exactly as the method case decides which body runs by it. It has the same cause and the same reason for not being fixed: the bracket must be parsed before overload resolution can run, so at the moment of the decision there is nothing to rank the candidates by. The predicate it replaced (ClassCtorWantsVarRecAt) had the same first-match behaviour and additionally always answered TVarRec, which WAS a defect and is fixed; the ordering is not.

Noting it here so that whoever makes the probe parameter-aware knows there are two call sites to satisfy and not one, and so that nobody re-derives the constructor half from scratch.

Still no real source wanting either answer, on either path. That is the prio, and it has not moved.

Claimed and released without work (2026-09-09, frankS)

Recorded so the next reader does not spend the same twenty minutes: I claimed this, read it, and put it back because the ticket is already right about itself.

"No real source is known that wants either answer — this shape came out of a control I wrote, not out of a corpus." That is CLAUDE.md's own test (ON PAR WITH THE LANGUAGE, NOT WITH FPC) applied by the ticket's author to the ticket, and working it would be chasing fpc on a shape nobody writes. The cost is not small either: the residual is making FindUMethOverloadAhead's speculative probe parameter-aware enough to parse a bracket argument, and I checked — this is that limitation's only open consumer.

So the group is one ticket wide and its value is the record, not the fix. Re-rank it upward when real source asks, which is what the body already says; do not take it because it looks like a bug with a clean repro, which is exactly how it reads from the queue.

Fixed (2026-09-09, frankS) — and the release above was wrong about WHY

I released this ticket a few hours earlier on the section above, and that reasoning had one false step. It is worth writing down because the false step is the ticket's own, repeated in three places and by two authors.

"The probe cannot parse a bracket, so there is nothing to rank the candidates by." True, and it rules out ranking by the ARGUMENT. It does not rule out ranking at all — and fpc's actual rule uses no argument. array of const takes a bracket slot unconditionally. That is a property of the PARAMETERS, and the parameters are sitting right there in Procs[] at the moment of the decision.

The release also inherited "no real source is known that wants either answer" from the body. That is still true of the ELEMENT-TYPE half. It is not true of this half: array of const beside a typed array is what a formatting or logging API declares, and the ctor case is worse than a wrong body — it decides how the bracket is PARSED, so an array of const constructor declared second was receiving an Integer vector.

Measured before implementing

fpc 3.2.2, both declaration orders, array of const against array of Integer, array of string, array of Double and array of Byte, with element lists of integers, strings, floats and an empty []. Array-of-const wins every row, including rows whose elements cannot convert to the other candidate's element type. Nothing about the literal is consulted.

The fix, at both addresses this ticket covers

The veto is untouched and deliberately so: a tySet parameter at the slot still declines the narrowing outright, for the reason the veto's own comment gives — the [...] really may be a set, and guessing the other way would break a working call to buy this one.

Fixture

test/test_p_an_array_of_const_wins_a_bracket_argument (Makefile test_arrconstwin26, in test-core), 12 rows, every family declared in both orders — a one-order fixture cannot fail this bug, because first-declared is right half the time by construction. Against the pinned pre-fix compiler exactly 5 of the 12 move, and they are exactly the rows declaring array of const second. The constructor rows assert a SUM, not a count: a count reads the same whether the bracket was parsed with an Integer stride or a TVarRec one, so it cannot fail the defect this half actually has.

One row of the .expected is pxx's own output and not fpc's, labelled loudly in the fixture header — see the residual below.

What is NOT closed, and it is why this is not the whole mechanism

So this closes the ticket's stated repro and both of its addresses, and hands off the part that really does need the probe. Do not read the two residuals as this bug reopening: they are separately measured, separately ranked, and one of them is pinned by a guard row so it cannot move silently.

Log