fix(semantic): accept empty open-array literal [] as an open-array argument

An empty bracket literal [] passed to a plain open-array parameter
(`array of T`) failed overload resolution with "No matching overload" on both
backends. A non-empty literal works because AnalyseArrayLiteralExpr infers the
element type and types it `array of <elem>`; an empty [] has nothing to infer
from and was left untyped (nil), so it matched neither the open-array param (nil
arg type) nor the existing set / array-of-const special cases.

Two changes in uSemantic:
* ArgMatchScore now scores an empty TArrayLiteralExpr as an exact (2) match
  against any tyOpenArray parameter — an empty open array is valid for any
  element type — placed before the nil-arg bail so overload selection accepts it.
* RetypeSetLiteralArgs pins the empty []'s ResolvedType to the chosen formal's
  open-array type, so codegen emits a zero-length open array (data=nil, high=-1)
  of the right element type.

Overload disambiguation is preserved ([] picks the open-array overload; a scalar
argument still picks the scalar overload), and two equally-matching open-array
overloads still raise a clean "ambiguous overload" error. Works for
`array of string`, `array of Integer`, and `array of const`.

This was the last open item in native-testrunner-investigation.txt; the native
TestRunner now passes the full suite unfiltered. Adds dual-backend e2e tests.
All three fixpoints green; 3501 tests pass.
This commit is contained in:
Graeme Geldenhuys 2026-06-18 11:53:51 +01:00
parent 937146a3ac
commit 1ef226369d
2 changed files with 72 additions and 0 deletions

View file

@ -8000,6 +8000,15 @@ begin
TArrayLiteralExpr(Arg).IsConstArray := True;
Arg.ResolvedType := Par.ResolvedType;
end;
{ Empty bracket literal [] bound to a plain open-array formal: pin its type
to the formal so codegen emits a zero-length open array (data=nil, high=-1)
of the right element type. The untyped [] would otherwise reach codegen
with no ResolvedType. }
if (Par.ResolvedType <> nil) and (Par.ResolvedType.Kind = tyOpenArray) and
(Arg is TArrayLiteralExpr) and
(TArrayLiteralExpr(Arg).Elements.Count = 0) and
(Arg.ResolvedType = nil) then
Arg.ResolvedType := Par.ResolvedType;
end;
end;
@ -8073,6 +8082,17 @@ begin
Result := 2;
Exit;
end;
{ An EMPTY bracket literal [] passed to a plain open-array parameter (array of
T) is a valid empty open array of any element type it has no elements to
infer a type from, so AnalyseArrayLiteralExpr left it untyped (nil). Match
it here, before the nil-arg bail; AnalyseProcCall re-types it to the formal's
open-array type so codegen emits a zero-length (data=nil, high=-1) array. }
if (AParam.Kind = tyOpenArray) and (AArgExpr is TArrayLiteralExpr) and
(TArrayLiteralExpr(AArgExpr).Elements.Count = 0) then
begin
Result := 2;
Exit;
end;
if AArg = nil then Exit;
{ Integer literal (untyped constant) matches any integer type exactly
mirrors Pascal's treatment of untyped integer constants. Floating-point

View file

@ -52,6 +52,10 @@ type
procedure TestRun_DynToOpen_Empty;
procedure TestRun_DynToOpen_OfString;
procedure TestRun_DynToOpen_PassToNested;
{ Empty bracket literal [] as an open-array argument }
procedure TestRun_EmptyLiteral_ToOpenArray;
procedure TestRun_EmptyLiteral_OverloadDisambiguation;
end;
implementation
@ -484,6 +488,54 @@ begin
AssertRunsOnAll(SrcDynNested, '100' + #10, 0);
end;
procedure TE2EOpenArrayTests.TestRun_EmptyLiteral_ToOpenArray;
{ An empty bracket literal [] passed to a plain open-array parameter both for a
string and an integer open array is a valid zero-length open array. This
previously failed overload resolution ("No matching overload"). }
const Src = '''
program P;
procedure ShowS(const A: array of string);
begin WriteLn(Length(A)) end;
procedure ShowI(const A: array of Integer);
var i, s: Integer;
begin
s := 0;
for i := 0 to Length(A) - 1 do s := s + A[i];
WriteLn(Length(A), ' ', s)
end;
begin
ShowS([]);
ShowS(['a', 'b']);
ShowI([]);
ShowI([10, 20, 30])
end.
''';
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnAll(Src,
'0' + #10 + '2' + #10 + '0 0' + #10 + '3 60' + #10, 0);
end;
procedure TE2EOpenArrayTests.TestRun_EmptyLiteral_OverloadDisambiguation;
{ [] selects the open-array overload over a non-array one; a non-bracket
argument still picks the scalar overload. }
const Src = '''
program P;
procedure F(X: Integer); overload;
begin WriteLn('int') end;
procedure F(const A: array of string); overload;
begin WriteLn('oa ', Length(A)) end;
begin
F([]);
F(['x']);
F(5)
end.
''';
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnAll(Src, 'oa 0' + #10 + 'oa 1' + #10 + 'int' + #10, 0);
end;
initialization
RegisterTest(TE2EOpenArrayTests);