A set candidate at a bracket slot vetoes the narrowing instead of winning it
type TByteSet = set of Byte;
TVeto = class
procedure P(N: Integer; A: array of Integer); overload;
procedure P(N: Integer; A: TByteSet); overload;
end;
...
ve.P(2, [7, 8]);
| fpc 3.2.2 | veto set |
| pxx | veto ints cnt=2 |
Found while building the fixture for [[bug-p-two-array-parameters-at-one-bracket-slot-are-decided-by-declaration-order]], by including the veto path as a must-not-move control and comparing it against fpc out of habit. The control was expected to be inert and turned out to be a divergence.
THE BLOCKER THIS TICKET SHARED WITH ITS SIBLING IS GONE — 2026-09-09
The sibling
[[bug-p-two-non-const-array-overloads-at-a-bracket-slot-cannot-be-ranked-by-element-type]]
is closed, and it closed by showing that the shared blocker was misnamed.
Both tickets said "the probe cannot parse a [...], so it cannot rank". True,
and it does not matter: fpc ranks by the elements' class, and a class is
readable from the tokens. BracketArgElemClass in pasparser_call.inc now
answers ORDINAL / REAL / STRING / CHAR / UNKNOWN, and BracketCandRank scores a
candidate against it.
So the information this ticket was waiting for exists now. What is left is not a question about the argument; it is that the answer changes how the argument is PARSED.
The measured rule, so the next reader does not re-derive it
fpc 3.2.2, 2026-09-09, both declaration orders every row:
| set candidate against | elements | fpc picks |
|---|---|---|
array of Integer |
[7, 8] |
the SET |
array of Integer |
[700, 800] |
the SET |
array of string |
[7, 8] |
the SET |
array of string |
['a', 'b'] |
the string array |
| either | [1.5, 2.5] |
refused — "Ordinal expression expected" |
| either | [] |
refused — ambiguous |
So the rule is: a set candidate takes a bracket slot whenever the elements are
ordinal, and loses when they are not. That is BEC_ORDINAL → set, everything
else → set is not viable, which is two lines in BracketCandRank.
Why it was NOT done with the sibling, and it is not effort
Naming the set candidate is not enough. ParamIsVarRecArray decides on the
PARSE side how [...] is read, and it applies the same veto so that "the two
decisions cannot disagree about which spelling won". Rank the set here without
moving that, and the probe's reading and the committed parse disagree — which
is the single worst failure shape this file has, and the one
devdocs/dev/debugging-playbook.md records as a half-wired door: it does not
refuse, it agrees and then builds something else.
The work is therefore: move the veto and the parse side together, in one change, with a fixture that asserts the VALUE the set body sees. A row that only prints which body ran would pass with the argument built as an open array and handed to a set parameter.
Why the veto is not simply wrong
ParamIsVarRecArray's comment states the rule: "A SET parameter anywhere at
this slot vetoes it: then [...] really may be a set, and guessing the other
way would break a working call to buy this one." That is still correct. What was
missing was the ability to look, and that is no longer missing — see the
section above. The veto's caution stays right for an UNKNOWN element list, which
is exactly where BracketArgElemClass still answers UNKNOWN.
Why prio 15
The veto's comment claims nothing in the tree mixes the two spellings at one slot. Re-measured 2026-09-09 and still true, so this costs nothing today. It is recorded because the guard row now pins pxx's answer, and a pinned answer that nobody has written down as a divergence is how a divergence becomes a belief.
Not the case the parent ticket already settled
[[bug-p-two-array-parameters-at-one-bracket-slot-are-decided-by-declaration-order]]
records a NEIGHBOURING set case as settled and not a defect: with
M(N: Integer; S: TCh) and M(N: Integer; A: array of const) visible, fpc
calls M(1, ['a']) ambiguous and refuses it while we accept it as the set —
us accepting what fpc rejects is not a defect. Different candidate pair. Here
the competitor is array of Integer, fpc compiles it without complaint, and it
picks the SET. Re-measured against fpc 3.2.2 on 2026-09-09; both rows are true
and neither implies the other.
Resolution, 2026-09-09 (frankS)
Two lines in BracketCandRank, once the sibling
[[bug-p-two-non-const-array-overloads-at-a-bracket-slot-cannot-be-ranked-by-element-type]]
had made the element class available:
if (cls = BEC_ORDINAL) and (ParamOwnKind(pi, pj) = tySet) then Result := 100;
100 rather than 90 is the whole rule — it has to beat array of Integer on the
same list.
The expensive assumption this ticket carried, and it was wrong
The ticket said matching fpc "changes how the argument is PARSED, not just
which candidate is named", and ranked itself as a coordinated two-site change
with a half-wired-door risk. It is one site.
TryParseBracketArgForSlot is handed the chosen Procs[] row; on a set
parameter it finds neither ParamIsVarRecArray nor ParamIsOpenArrayScalar,
returns -1, and the argument falls to ParseExpr — which parses [7, 8] as a
set. Selection and parsing read the same row, so the disagreement the ticket was
guarding against cannot occur.
Both of this family's tickets over-estimated their blocker in the same direction, and both from reasoning about the machinery rather than reading it. The sibling's blocker named the wrong input; this one named a second site that does not need to move.
The veto is not gone
It still applies to every element list BracketArgElemClass cannot classify —
anything that is not exactly one literal per element. That is precisely the
population the veto's own comment was written about: "the [...] really may be
a set there, and guessing the other way would break a working call to buy this
one." Where we can see the elements we no longer guess; where we cannot, we
still decline.
A correction the fixture carries
Against a set candidate, fpc refuses a multi-character string element —
['x', 'yz'] gives "Ordinal expression expected", because it reads the
literal as a set and 'yz' is not a member. It accepts ['a', 'b'] and runs
the string array. The rule as first written here, "string elements take the
string array", was generalised from single-character lists, which were the only
ones in the 72-row matrix. We accept the multi-character form and run the string
body — us accepting what fpc rejects, so not a defect, but not a row an
fpc-oracle fixture may assert either. The fixture uses ['a', 'b'] and says
why.
Guarded in two places
test_p_a_bracket_slot_is_ranked_by_what_its_elements_are gains six set rows,
both declaration orders, both directions of the rule; the set body sums its
members rather than announcing itself, because set alone would pass whether
the literal reached it as a set or as garbage handed to a set parameter.
And test_p_an_array_of_const_wins_a_bracket_argument now matches fpc on
every line. Its veto row held pxx's own answer under a loud label — the
file's one pinned divergence — and that is the argument for pinning a divergence
loudly rather than leaving it unwritten: it stayed exactly as long as it took to
measure the rule.
Positive control, verified: the pinned compiler answers veto ints cnt=2
for the same source where HEAD and fpc both answer veto set.
Log
- 2026-09-09 — resolved; this names the commit that carried the resolve, which is not always the one that carried the change — commit 7324f9167.