docs+test: collection ownership conventions — generic retention pinned
Resolves the TStringList.Objects / TList<T> retention question from the bug backlog as an explicit design decision (language-rationale, 'Collection Ownership'): - TList<T>/TStack<T>/TQueue<T> managed elements ARE retained on store — this already works (generic stores lower to ARC-aware pointer writes); TE2ETListTests.TestRun_TList_ClassElements_RetainedAcrossScope now pins it on both backends (object survives its creating scope). - TStringList.Objects[] stays NON-OWNING by design: the API type is Pointer and the integer-cast idiom (TObject(PtrUInt(N)), used in 27 places in the compiler itself) makes blind retention impossible. Convention documented at the declaration and in the rationale. - Known limitation recorded: generic Clear/Destroy do not yet release remaining managed elements (needs a Default(T)-style zero-store); tracked in the leak backlog. Suite: 2946 OK; FIXPOINT_OK; NATIVE_FIXPOINT_OK.
This commit is contained in:
parent
d6b706ffd3
commit
b9e0ea7a9e
|
|
@ -29,6 +29,11 @@ type
|
|||
procedure TestRun_TListInteger_AddGetCount;
|
||||
procedure TestRun_TListInteger_IndexOf;
|
||||
procedure TestRun_TListString_IndexOf_NotFound;
|
||||
|
||||
{ Class elements are RETAINED on store: an object whose only other
|
||||
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;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -158,6 +163,50 @@ begin
|
|||
AssertTrue('IndexOf(gamma)=-1', Pos('-1', Output) >= 0);
|
||||
end;
|
||||
|
||||
const
|
||||
SrcTListClassRetain = '''
|
||||
program P;
|
||||
uses generics.collections;
|
||||
type
|
||||
TC = class
|
||||
N: Integer;
|
||||
end;
|
||||
procedure Fill(L: TList<TC>; S: TStack<TC>);
|
||||
var C: TC;
|
||||
begin
|
||||
C := TC.Create();
|
||||
C.N := 77;
|
||||
L.Add(C);
|
||||
C := TC.Create();
|
||||
C.N := 88;
|
||||
S.Push(C);
|
||||
end;
|
||||
var
|
||||
L: TList<TC>;
|
||||
S: TStack<TC>;
|
||||
begin
|
||||
L := TList<TC>.Create();
|
||||
S := TStack<TC>.Create();
|
||||
Fill(L, S);
|
||||
writeln(L.Get(0).N);
|
||||
writeln(S.Peek().N);
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TE2ETListTests.TestRun_TList_ClassElements_RetainedAcrossScope;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+link+run (qbe)',
|
||||
CompileAndRunWithRTLDebugOn(beQBE, SrcTListClassRetain, Output, RCode, False));
|
||||
AssertEquals('exit code (qbe)', 0, RCode);
|
||||
AssertEquals('retained elements readable (qbe)', '77' + #10 + '88' + #10, Output);
|
||||
AssertTrue('compile+link+run (native)',
|
||||
CompileAndRunWithRTLDebugOn(beNative, SrcTListClassRetain, Output, RCode, False));
|
||||
AssertEquals('exit code (native)', 0, RCode);
|
||||
AssertEquals('retained elements readable (native)', '77' + #10 + '88' + #10, Output);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2ETListTests);
|
||||
|
||||
|
|
|
|||
|
|
@ -3566,3 +3566,40 @@ publishing the interface as the API surface.
|
|||
through chained expressions is not yet wired.
|
||||
* Indexed and default array properties in interfaces are not yet
|
||||
supported.
|
||||
|
||||
== Collection Ownership — ARC Conventions
|
||||
|
||||
=== Decision
|
||||
|
||||
The collection types follow three explicit ownership conventions:
|
||||
|
||||
* `TObjectList` — ownership is opt-in: `Create(True)` retains on add and
|
||||
releases (destroying at refcount zero) on remove/clear/destroy;
|
||||
`Create(False)` stores borrowed references.
|
||||
* `TList<T>`, `TStack<T>`, `TQueue<T>` and the other generic collections —
|
||||
managed elements (`string`, class, dynamic array) ARE retained on store
|
||||
and released when an element slot is overwritten (stores lower to
|
||||
ARC-aware pointer writes). An object whose only other reference was a
|
||||
local in an exited routine therefore survives in the collection.
|
||||
* `TStringList.Objects[]` — NON-OWNING raw pointer slots, by design.
|
||||
The API type is `Pointer`, and storing integer payloads cast to
|
||||
pointers (`TObject(PtrUInt(N))`) is an accepted idiom (the compiler
|
||||
itself uses it); blind retention would corrupt such entries. Callers
|
||||
storing real objects must hold a strong reference elsewhere for the
|
||||
entry's lifetime.
|
||||
|
||||
=== Rationale
|
||||
|
||||
Retention for typed generic elements is safe because `T` is statically
|
||||
known — only genuinely managed types get ARC operations. `Objects[]`
|
||||
cannot distinguish a pointer-encoded integer from an object reference at
|
||||
runtime, so it keeps FPC/Delphi's borrowed-pointer contract.
|
||||
|
||||
=== Known limitation (leak backlog)
|
||||
|
||||
`Clear`/`Destroy` on the generic collections do not yet release the
|
||||
retained references of remaining managed elements (releasing requires a
|
||||
`Default(T)`-style generic zero-store that the language does not provide
|
||||
yet). Until then, collections of managed elements leak their contents
|
||||
when destroyed non-empty; element overwrite and `Delete` shifting are
|
||||
ARC-balanced.
|
||||
|
|
|
|||
|
|
@ -187,6 +187,13 @@ type
|
|||
constructor Create;
|
||||
procedure Destroy;
|
||||
function Add(S: string): Integer;
|
||||
{ Objects[] is NON-OWNING by design: slots hold raw, untyped pointers
|
||||
with no ARC retain/release. Callers commonly store integer payloads
|
||||
cast to pointers (TObject(PtrUInt(N))), which makes blind retention
|
||||
impossible. When storing real objects, the caller must keep a strong
|
||||
reference alive for the lifetime of the entry (e.g. an owning
|
||||
TObjectList keep-alive) — see docs/language-rationale.adoc,
|
||||
"Collection Ownership". }
|
||||
procedure AddObject(S: string; AObject: Pointer);
|
||||
function Find(const S: string; var Index: Integer): Boolean;
|
||||
function IndexOf(const S: string): Integer;
|
||||
|
|
|
|||
Loading…
Reference in a new issue