Foo(MakeArray) — a dyn-array result passed straight to a call types as Pointer
RESOLVED 2026-08-26 — already fixed when re-measured, and the re-measurement found two live bugs beside it. See Outcome at the end.
Found 2026-08-25 by Track B while adding SplitString to lib/rtl/strutils.pas
(feature-b-rtl-gap-inventory-22-sysutils-strutils-symbols). The natural way to
use it is Dump(SplitString(line, ',')); that is the shape that fails.
Repro (nested3.pas)
program nested3;
uses sysutils;
function Make: TStringArray;
begin
SetLength(Result, 2);
end;
procedure Show(const tag: AnsiString; const v: TStringArray);
begin
Writeln(tag, Length(v));
end;
begin
Show('n=', Make);
end.
| compiler | result |
|---|---|
fpc -O- -Mobjfpc |
compiles, prints n=2 |
| pxx (pinned stable) | rejected at compile time |
error: no overload of Show matches these arguments
argument types: (ShortString, Pointer)
candidates:
Show(AnsiString, AnsiString)
Two things are visible in that message and they are separate: the argument
side reports Pointer where TStringArray was expected — that is the bug —
and the candidate side prints the TStringArray parameter as AnsiString,
which is a diagnostic-rendering problem on top of it.
The tell — the function and the parameter are both fine
Assigning the identical call to a variable first compiles and runs:
var a: TStringArray;
...
a := Make;
Show('n=', a); { compiles, prints n=2 }
So Make's return type, Show's parameter type, and the dynamic array itself
all work. What is lost is the type of the call's result in argument position —
the assignment case succeeds only because the assignment's target type supplies
what inference did not.
Same shape as bug-pchar-difference-in-writeln-arg-segfaults, filed the same
session: an expression whose type is correct when a target forces it, and
degraded when nothing does.
Reach
Any f(g(...)) where g returns a dynamic array. That includes the whole
SplitString / TStringHelper.Split idiom, which is the ordinary way real FPC
code slices a line, so the wall is hit on the first realistic use.
Track B workaround in place
test/lib_strutils_words.pas assigns each SplitString result to a local
before using it. Library code in lib/rtl/** was NOT reshaped — nothing there
passes a dyn-array result directly today, so no platonic code was bent.
Gate
Track P: make compiler/pascal26 (self-host fixedpoint) + the repro compiling
and printing n=2.
Outcome (2026-08-26)
The filed symptom no longer reproduces. The ticket's own nested3.pas
compiles and prints n=2, and so does every widening of it I could construct:
a user-defined array of Integer, by-value and const parameters, an
array-returning call used as Length()'s operand, subscripted directly
(MakeI[1]), and Dump(SplitString(line, ',')) — the shape the ticket was
filed from. Fixed by something between 2026-08-25 and now; no commit here
claims it, so it went with a change that did not know it was the fix.
Left blocked-by: [] and moved as resolved rather than deleted, because the
measurement below is the reason it was worth reopening at all.
What the re-measurement DID find
Widening the repro to an overload SET — which the original error message
(no overload of Show matches) pointed at — turned up two live bugs, both
silent, both with no diagnostic, and both the same root cause as each other:
pxx presents an array's ELEMENT kind as its type kind, so an array parameter
and a scalar parameter of that element type are indistinguishable to overload
resolution, which then takes whichever was DECLARED FIRST.
| call | overloads, in declaration order | fpc | pxx (before) |
|---|---|---|---|
P('hello') |
P(array of AnsiString), P(AnsiString) |
str hello |
arr 5 |
S(ia) |
S(v: Integer), S(const v: TIA) |
Sarr 4 |
Sint 1440743456 |
T(fa) |
T(v: Integer), T(const v: TFA) |
Tfix 11 |
Tint 4303792 |
The last two are the dyn-array handle and the variable's address, printed as integers. Writing the same two overloads the other way round gives the right answer in every case, which is exactly why this had gone unnoticed.
bug-a-an-integer-argument-binds-a-fixed-array-overload had already fixed one
quarter of this — an ordinal argument against an array parameter — via the
MatchArgScalar channel. Both remaining directions are now fixed beside it:
strings joined that channel, and a new MatchArgArray channel covers the
mirror (a definitely-array argument cannot bind a scalar parameter). The two
are deliberately NOT each other's negation: every uncertain argument shape
stays False in both, or it would be blocked from every candidate at once.
Pinned in test/test_array_and_scalar_overload_binding.pas, both directions,
including the reversed-declaration pairs that were always right.
The diagnostic half is still open
The ticket also noted that the candidate list printed a TStringArray
parameter as AnsiString. That is the same element-kind-is-the-type-kind
convention showing through OverloadReport, and it is still true — a
diagnostic-rendering problem, not a miscompile, so it is left where the
FPC-parity ceiling puts it.