feat(stdlib): TList<T> default array property — List[i] subscript

TList<T> now exposes a `default` array property, so elements can be read
and written with the familiar subscript syntax instead of .Get(i):

    L[0] := 100;            // L.SetItem(0, 100)
    WriteLn(L[0] + L[1]);   // L.Get(i)

Adds TList<T>.SetItem (a pointer-target store, so the compiler's ARC
discipline releases the old element and retains the new for managed T)
and the property Items[AIndex]: T read Get write SetItem; default.

Also fixes the generic-instance clone path in the semantic analyser: it
copied every property-decl field except IsDefault, so a default property
declared on a generic template (TList<T>) lost its default flag on
instantiation and List[i] did not resolve. The clone now carries
IsDefault.

Verified List[i] read, write, and polymorphic element dispatch
(List[i].Area through a base-typed element) on both backends. Adds
TE2ETListTests.TestRun_TList_DefaultProperty_{ReadWrite,Polymorphic}.

Note: this makes the stdlib depend on the `default` directive (added in
the previous commit), so the pre-release bootstrap binary is refreshed to
a default-aware stage-2 in a follow-up.
This commit is contained in:
Graeme Geldenhuys 2026-06-16 14:57:50 +01:00
parent 798df8eb15
commit 42ca9d7535
3 changed files with 81 additions and 0 deletions

View file

@ -2386,6 +2386,7 @@ begin
NewPDecl.WriteName := PDecl.WriteName;
NewPDecl.IndexParamName := PDecl.IndexParamName;
NewPDecl.IndexTypeName := SubstTypeParam(PDecl.IndexTypeName, Templ.ParamNames, Args);
NewPDecl.IsDefault := PDecl.IsDefault;
ClonedCD.Properties.Add(NewPDecl);
end;

View file

@ -34,6 +34,10 @@ type
reference was a local in an exited routine must survive in the list
(decision recorded in language-rationale: collection ownership). }
procedure TestRun_TList_ClassElements_RetainedAcrossScope;
{ Default array property: List[i] subscript for read and write. }
procedure TestRun_TList_DefaultProperty_ReadWrite;
procedure TestRun_TList_DefaultProperty_Polymorphic;
end;
implementation
@ -207,6 +211,69 @@ begin
AssertEquals('retained elements readable (native)', '77' + #10 + '88' + #10, Output);
end;
const
SrcTListDefaultRW = '''
program P;
uses Generics.Collections;
var lst: TList<Integer>;
begin
lst := TList<Integer>.Create;
lst.Add(1); lst.Add(2);
lst[0] := 100; lst[1] := 200;
WriteLn(lst[0] + lst[1]);
lst.Free()
end.
''';
SrcTListDefaultPoly = '''
program P;
uses Generics.Collections;
type
TShape = class function Area: Integer; virtual; begin Result := 0 end; end;
TBox = class(TShape)
FS: Integer;
constructor Create(s: Integer); begin FS := s end;
function Area: Integer; override; begin Result := FS * FS end;
end;
var lst: TList<TShape>; i, total: Integer;
begin
lst := TList<TShape>.Create;
lst.Add(TBox.Create(3)); lst.Add(TBox.Create(4));
total := 0;
for i := 0 to lst.Count - 1 do total := total + lst[i].Area();
WriteLn(total);
lst.Free()
end.
''';
procedure TE2ETListTests.TestRun_TList_DefaultProperty_ReadWrite;
var Output: string; RCode: Integer;
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertTrue('compile+run (qbe)',
CompileAndRunWithRTLDebugOn(beQBE, SrcTListDefaultRW, Output, RCode, False));
AssertEquals('exit code (qbe)', 0, RCode);
AssertEquals('List[i] read/write (qbe)', '300' + #10, Output);
AssertTrue('compile+run (native)',
CompileAndRunWithRTLDebugOn(beNative, SrcTListDefaultRW, Output, RCode, False));
AssertEquals('exit code (native)', 0, RCode);
AssertEquals('List[i] read/write (native)', '300' + #10, Output);
end;
procedure TE2ETListTests.TestRun_TList_DefaultProperty_Polymorphic;
var Output: string; RCode: Integer;
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertTrue('compile+run (qbe)',
CompileAndRunWithRTLDebugOn(beQBE, SrcTListDefaultPoly, Output, RCode, False));
AssertEquals('exit code (qbe)', 0, RCode);
AssertEquals('List[i].Area (qbe)', '25' + #10, Output);
AssertTrue('compile+run (native)',
CompileAndRunWithRTLDebugOn(beNative, SrcTListDefaultPoly, Output, RCode, False));
AssertEquals('exit code (native)', 0, RCode);
AssertEquals('List[i].Area (native)', '25' + #10, Output);
end;
initialization
RegisterTest(TE2ETListTests);

View file

@ -36,12 +36,15 @@ type
procedure Grow;
procedure Add(Value: T);
function Get(AIndex: Integer): T;
procedure SetItem(AIndex: Integer; Value: T);
function IndexOf(Value: T): Integer;
procedure Delete(AIndex: Integer);
procedure Clear;
procedure Destroy;
function GetEnumerator: TListEnumerator<T>;
property Count: Integer read FCount;
{ Default array property — enables List[i] for read and write. }
property Items[AIndex: Integer]: T read Get write SetItem; default;
end;
{ Generic LIFO stack backed by a dynamic array. Push/Pop/Peek operate on
@ -262,6 +265,16 @@ begin
Result := Src^
end;
procedure TList<T>.SetItem(AIndex: Integer; Value: T);
var
Dest: ^T;
begin
{ The ^T := Value store carries the compiler's ARC discipline for a managed
T the previous element is released and the new one retained. }
Dest := Self.FData + AIndex * SizeOf(T);
Dest^ := Value
end;
function TList<T>.IndexOf(Value: T): Integer;
var
I: Integer;