From aafd00497341250fad43ba07107460c417d3f1da Mon Sep 17 00:00:00 2001 From: Graeme Geldenhuys Date: Sun, 3 May 2026 16:33:08 +0100 Subject: [PATCH] feat(generics): for..in support on generic collections (Step 9b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes to unblock for..in over generic collection types: 1. SubstTypeParam: extend to substitute type params inside generic instantiation names. Previously only bare 'T' and '^T' were handled; 'SomeName' was left unsubstituted, so a method returning 'TListEnumerator' kept the raw param name after cloning. 2. InstantiateGeneric: clone TClassTypeDef.Properties with type-param substitution (same pattern as fields and methods). Without this the 'Current: T' property was invisible on instantiated enumerator types and FindProperty returned nil during for..in semantic checks. 3. AnalyseFieldAccess + ResolveScopeBoundTypeParams: when a generic RecordName such as 'TGenEnum' isn't found in the symbol table, resolve each type argument against the current scope (where T=Integer is pushed during method body analysis) and retry the lookup with the concrete name 'TGenEnum'. New: cp.test.genericforin.pas — 10 tests covering property visibility, property type after instantiation, and end-to-end for..in IR emission. 1141 tests total, all passing. --- compiler/src/main/pascal/uSemantic.pas | 159 ++++++- compiler/src/test/pascal/TestRunner.pas | 3 +- .../src/test/pascal/cp.test.genericforin.pas | 414 ++++++++++++++++++ 3 files changed, 574 insertions(+), 2 deletions(-) create mode 100644 compiler/src/test/pascal/cp.test.genericforin.pas diff --git a/compiler/src/main/pascal/uSemantic.pas b/compiler/src/main/pascal/uSemantic.pas index b2e37a4..a7154b1 100644 --- a/compiler/src/main/pascal/uSemantic.pas +++ b/compiler/src/main/pascal/uSemantic.pas @@ -52,6 +52,12 @@ type function SubstTypeParam(const ATypeName: string; AParamNames, AArgs: TStringList): string; + { Resolves scope-bound type params inside a generic type name such as + 'TGenEnum' when 'T' is bound in the current scope as a skType symbol. + Returns the canonical name (e.g. 'TGenEnum') suitable for + FindTypeOrInstantiate. Has no effect on names without '<'. } + function ResolveScopeBoundTypeParams(const ATypeName: string): string; + { Generic function instantiation: resolves 'Identity' on demand. } function InstantiateGenericFunc(const AInstName: string): TMethodDecl; @@ -736,6 +742,55 @@ begin end; end; +function TSemanticAnalyser.ResolveScopeBoundTypeParams(const ATypeName: string): string; +var + BrOpen, BrClose, I: Integer; + BasePart, ArgsPart, OutArgs, Arg: string; + ArgList: TStringList; + Sym: TSymbol; +begin + Result := ATypeName; + BrOpen := Pos('<', ATypeName); + if BrOpen = 0 then Exit; + BrClose := Length(ATypeName); + BasePart := Copy(ATypeName, 1, BrOpen - 1); + ArgsPart := Copy(ATypeName, BrOpen + 1, BrClose - BrOpen - 1); + ArgList := TStringList.Create; + try + while ArgsPart <> '' do + begin + I := Pos(',', ArgsPart); + if I > 0 then + begin + ArgList.Add(Trim(Copy(ArgsPart, 1, I - 1))); + ArgsPart := Trim(Copy(ArgsPart, I + 1, MaxInt)); + end + else + begin + ArgList.Add(Trim(ArgsPart)); + ArgsPart := ''; + end; + end; + OutArgs := ''; + for I := 0 to ArgList.Count - 1 do + begin + Arg := ArgList.Strings[I]; + { If this arg is a bare ident bound as skType in the current scope, + replace it with the concrete type name. } + Sym := FTable.Lookup(Arg); + if (Sym <> nil) and (Sym.Kind = skType) and (Sym.TypeDesc <> nil) then + Arg := Sym.TypeDesc.Name; + if OutArgs = '' then + OutArgs := Arg + else + OutArgs := OutArgs + ',' + Arg; + end; + finally + ArgList.Free; + end; + Result := BasePart + '<' + OutArgs + '>'; +end; + function TSemanticAnalyser.FindTypeOrInstantiate(const AName: string): TTypeDesc; var BaseName: string; @@ -818,7 +873,9 @@ end; function TSemanticAnalyser.SubstTypeParam(const ATypeName: string; AParamNames, AArgs: TStringList): string; var - I: Integer; + I, BrOpen, BrClose: Integer; + BasePart, ArgsPart, OutArgs, Arg: string; + ArgList: TStringList; begin Result := ATypeName; { Direct match: T → Integer } @@ -830,7 +887,47 @@ begin end; { Prefix caret: ^T → ^Integer, ^^T → ^^Integer, etc. } if (Length(Result) > 0) and (Result[1] = '^') then + begin Result := '^' + Self.SubstTypeParam(Copy(Result, 2, MaxInt), AParamNames, AArgs); + Exit; + end; + { Generic instantiation: SomeName — substitute each type argument } + BrOpen := Pos('<', Result); + if BrOpen > 0 then + begin + BrClose := Length(Result); { closing '>' is always the last char } + BasePart := Copy(Result, 1, BrOpen - 1); + ArgsPart := Copy(Result, BrOpen + 1, BrClose - BrOpen - 1); + ArgList := TStringList.Create; + try + while ArgsPart <> '' do + begin + I := Pos(',', ArgsPart); + if I > 0 then + begin + ArgList.Add(Trim(Copy(ArgsPart, 1, I - 1))); + ArgsPart := Trim(Copy(ArgsPart, I + 1, MaxInt)); + end + else + begin + ArgList.Add(Trim(ArgsPart)); + ArgsPart := ''; + end; + end; + OutArgs := ''; + for I := 0 to ArgList.Count - 1 do + begin + Arg := Self.SubstTypeParam(ArgList.Strings[I], AParamNames, AArgs); + if OutArgs = '' then + OutArgs := Arg + else + OutArgs := OutArgs + ',' + Arg; + end; + finally + ArgList.Free; + end; + Result := BasePart + '<' + OutArgs + '>'; + end; end; function TSemanticAnalyser.InstantiateGeneric(const ATypeName: string): TRecordTypeDesc; @@ -848,11 +945,15 @@ var NewMDecl: TMethodDecl; Par: TMethodParam; NewPar: TMethodParam; + PDecl: TPropertyDecl; + NewPDecl: TPropertyDecl; Sym: TSymbol; Key: string; FldType: TTypeDesc; FldName: string; ParType: TTypeDesc; + PropType: TTypeDesc; + PropInfo: TPropertyInfo; RT: TRecordTypeDesc; GI: TGenericInstance; Subst: string; @@ -947,6 +1048,20 @@ begin ClonedCD.Methods.Add(NewMDecl); end; + { Clone property declarations with type-param substitution } + for I := 0 to Templ.ClassDef.Properties.Count - 1 do + begin + PDecl := TPropertyDecl(Templ.ClassDef.Properties.Items[I]); + NewPDecl := TPropertyDecl.Create; + NewPDecl.Name := PDecl.Name; + NewPDecl.TypeName := SubstTypeParam(PDecl.TypeName, Templ.ParamNames, Args); + NewPDecl.ReadName := PDecl.ReadName; + NewPDecl.WriteName := PDecl.WriteName; + NewPDecl.IndexParamName := PDecl.IndexParamName; + NewPDecl.IndexTypeName := SubstTypeParam(PDecl.IndexTypeName, Templ.ParamNames, Args); + ClonedCD.Properties.Add(NewPDecl); + end; + { Pre-pass: vtable slots (before fields so vptr is counted in offsets) } for J := 0 to ClonedCD.Methods.Count - 1 do begin @@ -1012,6 +1127,39 @@ begin end; end; + { Resolve property declarations — type-param already substituted in clone pass } + for J := 0 to ClonedCD.Properties.Count - 1 do + begin + NewPDecl := TPropertyDecl(ClonedCD.Properties.Items[J]); + PropType := FindTypeOrInstantiate(NewPDecl.TypeName); + if PropType = nil then + SemanticError( + Format('Unknown type ''%s'' for property ''%s'' in ''%s''', + [NewPDecl.TypeName, NewPDecl.Name, ATypeName]), + 0, 0); + PropInfo := TPropertyInfo.Create; + PropInfo.Name := NewPDecl.Name; + PropInfo.TypeDesc := PropType; + if NewPDecl.ReadName <> '' then + begin + if RT.FindField(NewPDecl.ReadName) <> nil then + PropInfo.ReadField := NewPDecl.ReadName + else + PropInfo.ReadMethod := NewPDecl.ReadName; + end; + if NewPDecl.WriteName <> '' then + begin + if RT.FindField(NewPDecl.WriteName) <> nil then + PropInfo.WriteField := NewPDecl.WriteName + else + PropInfo.WriteMethod := NewPDecl.WriteName; + end; + PropInfo.IndexParamName := NewPDecl.IndexParamName; + if NewPDecl.IndexTypeName <> '' then + PropInfo.IndexTypeDesc := FindTypeOrInstantiate(NewPDecl.IndexTypeName); + RT.AddProperty(PropInfo); + end; + { Analyse method bodies with concrete types in scope. Push type-param bindings (T=Integer etc.) so that SizeOf(T) and local var declarations like 'var P: ^T' resolve to concrete types. } @@ -3935,6 +4083,15 @@ begin end; RecSym := FTable.Lookup(AAccess.RecordName); + { If the name contains '<' and wasn't found, resolve scope-bound type params + (e.g. 'TGenEnum' → 'TGenEnum' when T=Integer is in scope) + and update AAccess.RecordName so codegen sees the concrete instantiation. } + if (RecSym = nil) and (Pos('<', AAccess.RecordName) > 0) then + begin + AAccess.RecordName := ResolveScopeBoundTypeParams(AAccess.RecordName); + FindTypeOrInstantiate(AAccess.RecordName); + RecSym := FTable.Lookup(AAccess.RecordName); + end; if RecSym = nil then begin { Implicit Self.RecordName.FieldName — RecordName is a field of current class } diff --git a/compiler/src/test/pascal/TestRunner.pas b/compiler/src/test/pascal/TestRunner.pas index cca9457..b476bb0 100644 --- a/compiler/src/test/pascal/TestRunner.pas +++ b/compiler/src/test/pascal/TestRunner.pas @@ -67,7 +67,8 @@ uses cp.test.constants, cp.test.external, cp.test.opdf, - cp.test.forin; + cp.test.forin, + cp.test.genericforin; var Application: TTestRunner; diff --git a/compiler/src/test/pascal/cp.test.genericforin.pas b/compiler/src/test/pascal/cp.test.genericforin.pas new file mode 100644 index 0000000..db73e1e --- /dev/null +++ b/compiler/src/test/pascal/cp.test.genericforin.pas @@ -0,0 +1,414 @@ +{ + Blaise - An Object Pascal Compiler + Copyright (c) 2026 Graeme Geldenhuys + SPDX-License-Identifier: BSD-3-Clause + See LICENSE file in the project root for full license terms. +} + +unit cp.test.genericforin; + +{$mode objfpc}{$H+} + +{ Tests for for..in support on generic collections (Step 9b). + + Two blockers: + 1. InstantiateGeneric doesn't clone TClassTypeDef.Properties with + type-param substitution, so 'Current: T' is invisible on the + instantiated enumerator type. + 2. generics.collections.pas has no TListEnumerator + + TList.GetEnumerator; the existing for..in class-enumerator + protocol path handles everything once those are added. } + +interface + +uses + Classes, SysUtils, fpcunit, testregistry, + uLexer, uParser, uAST, uSymbolTable, uSemantic, uCodeGenQBE; + +type + TGenericForInTests = class(TTestCase) + private + function ParseSrc(const ASrc: string): TProgram; + function AnalyseSrc(const ASrc: string): TProgram; + function GenIR(const ASrc: string): string; + procedure AnalyseExpectError(const ASrc: string); + published + { ------------------------------------------------------------------ } + { Blocker 1: property cloning in InstantiateGeneric } + { ------------------------------------------------------------------ } + procedure TestSemantic_GenericEnumerator_CurrentPropertyVisible; + procedure TestSemantic_GenericEnumerator_CurrentPropertyType; + + { ------------------------------------------------------------------ } + { Blocker 2: TListEnumerator + TList.GetEnumerator in RTL } + { ------------------------------------------------------------------ } + procedure TestParse_TListEnumerator_IsGenericTypeDef; + procedure TestParse_TList_GetEnumerator_Declared; + procedure TestSemantic_TList_Integer_HasGetEnumerator; + procedure TestSemantic_TListEnumerator_Integer_HasCurrentProperty; + + { ------------------------------------------------------------------ } + { End-to-end: for..in on TList } + { ------------------------------------------------------------------ } + procedure TestSemantic_ForIn_GenericList_OK; + procedure TestCodegen_ForIn_GenericList_CallsGetEnumerator; + procedure TestCodegen_ForIn_GenericList_CallsMoveNext; + procedure TestCodegen_ForIn_GenericList_CallsGetCurrent; + end; + +implementation + +{ ------------------------------------------------------------------ } +{ Source constants } +{ ------------------------------------------------------------------ } + +const + { Minimal generic enumerator with a property backed by a getter. } + SrcGenericEnumerator = + 'program P;' + LineEnding + + 'type' + LineEnding + + ' TGenEnum = class' + LineEnding + + ' FData: T;' + LineEnding + + ' FDone: Boolean;' + LineEnding + + ' function MoveNext: Boolean;' + LineEnding + + ' begin' + LineEnding + + ' Result := not Self.FDone;' + LineEnding + + ' Self.FDone := True' + LineEnding + + ' end;' + LineEnding + + ' function GetCurrent: T;' + LineEnding + + ' begin' + LineEnding + + ' Result := Self.FData' + LineEnding + + ' end;' + LineEnding + + ' property Current: T read GetCurrent;' + LineEnding + + ' end;' + LineEnding + + 'begin end.'; + + { A minimal generic collection that exposes GetEnumerator. } + SrcGenericCollection = + 'program P;' + LineEnding + + 'type' + LineEnding + + ' TGenEnum = class' + LineEnding + + ' FData: T;' + LineEnding + + ' FDone: Boolean;' + LineEnding + + ' function MoveNext: Boolean;' + LineEnding + + ' begin' + LineEnding + + ' Result := not Self.FDone;' + LineEnding + + ' Self.FDone := True' + LineEnding + + ' end;' + LineEnding + + ' function GetCurrent: T;' + LineEnding + + ' begin' + LineEnding + + ' Result := Self.FData' + LineEnding + + ' end;' + LineEnding + + ' property Current: T read GetCurrent;' + LineEnding + + ' end;' + LineEnding + + ' TColl = class' + LineEnding + + ' FItem: T;' + LineEnding + + ' function GetEnumerator: TGenEnum;' + LineEnding + + ' begin' + LineEnding + + ' Result := TGenEnum.Create' + LineEnding + + ' end;' + LineEnding + + ' end;' + LineEnding + + 'var' + LineEnding + + ' C: TColl;' + LineEnding + + ' X: Integer;' + LineEnding + + 'begin' + LineEnding + + ' C := TColl.Create;' + LineEnding + + ' for X in C do' + LineEnding + + ' WriteLn(X)' + LineEnding + + 'end.'; + + { Source that requires TList and TListEnumerator from the RTL. + Uses a minimal inline definition so the test is self-contained. } + SrcTListEnumeratorDecl = + 'program P;' + LineEnding + + 'type' + LineEnding + + ' TListEnumerator = class' + LineEnding + + ' FList: ^T;' + LineEnding + + ' FIndex: Integer;' + LineEnding + + ' FCount: Integer;' + LineEnding + + ' function MoveNext: Boolean;' + LineEnding + + ' begin' + LineEnding + + ' Self.FIndex := Self.FIndex + 1;' + LineEnding + + ' Result := Self.FIndex < Self.FCount' + LineEnding + + ' end;' + LineEnding + + ' function GetCurrent: T;' + LineEnding + + ' begin' + LineEnding + + ' Result := (Self.FList + Self.FIndex)^' + LineEnding + + ' end;' + LineEnding + + ' property Current: T read GetCurrent;' + LineEnding + + ' end;' + LineEnding + + ' TMyList = class' + LineEnding + + ' FData: ^T;' + LineEnding + + ' FCount: Integer;' + LineEnding + + ' function GetEnumerator: TListEnumerator;' + LineEnding + + ' begin' + LineEnding + + ' Result := TListEnumerator.Create' + LineEnding + + ' end;' + LineEnding + + ' end;' + LineEnding + + 'begin end.'; + + SrcForInGenericList = + 'program P;' + LineEnding + + 'type' + LineEnding + + ' TListEnumerator = class' + LineEnding + + ' FList: ^T;' + LineEnding + + ' FIndex: Integer;' + LineEnding + + ' FCount: Integer;' + LineEnding + + ' function MoveNext: Boolean;' + LineEnding + + ' begin' + LineEnding + + ' Self.FIndex := Self.FIndex + 1;' + LineEnding + + ' Result := Self.FIndex < Self.FCount' + LineEnding + + ' end;' + LineEnding + + ' function GetCurrent: T;' + LineEnding + + ' begin' + LineEnding + + ' Result := (Self.FList + Self.FIndex)^' + LineEnding + + ' end;' + LineEnding + + ' property Current: T read GetCurrent;' + LineEnding + + ' end;' + LineEnding + + ' TMyList = class' + LineEnding + + ' FData: ^T;' + LineEnding + + ' FCount: Integer;' + LineEnding + + ' function GetEnumerator: TListEnumerator;' + LineEnding + + ' begin' + LineEnding + + ' Result := TListEnumerator.Create' + LineEnding + + ' end;' + LineEnding + + ' end;' + LineEnding + + 'var' + LineEnding + + ' L: TMyList;' + LineEnding + + ' X: Integer;' + LineEnding + + 'begin' + LineEnding + + ' L := TMyList.Create;' + LineEnding + + ' for X in L do' + LineEnding + + ' WriteLn(X)' + LineEnding + + 'end.'; + +{ ------------------------------------------------------------------ } +{ Helpers } +{ ------------------------------------------------------------------ } + +function TGenericForInTests.ParseSrc(const ASrc: string): TProgram; +var + Lex: TLexer; + Par: TParser; +begin + Lex := TLexer.Create(ASrc); + Par := TParser.Create(Lex); + try + Result := Par.Parse; + finally + Par.Free; + Lex.Free; + end; +end; + +function TGenericForInTests.AnalyseSrc(const ASrc: string): TProgram; +var + SA: TSemanticAnalyser; +begin + Result := ParseSrc(ASrc); + SA := TSemanticAnalyser.Create; + try + SA.Analyse(Result); + finally + SA.Free; + end; +end; + +function TGenericForInTests.GenIR(const ASrc: string): string; +var + CG: TCodeGenQBE; + Prog: TProgram; +begin + Prog := AnalyseSrc(ASrc); + CG := TCodeGenQBE.Create; + try + CG.Generate(Prog); + Result := CG.GetOutput; + finally + CG.Free; + Prog.Free; + end; +end; + +procedure TGenericForInTests.AnalyseExpectError(const ASrc: string); +var + Prog: TProgram; + SA: TSemanticAnalyser; +begin + Prog := ParseSrc(ASrc); + SA := TSemanticAnalyser.Create; + try + try + SA.Analyse(Prog); + Fail('Expected ESemanticError but none was raised'); + except + on E: ESemanticError do { expected }; + end; + finally + SA.Free; + Prog.Free; + end; +end; + +{ ------------------------------------------------------------------ } +{ Blocker 1: property cloning } +{ ------------------------------------------------------------------ } + +procedure TGenericForInTests.TestSemantic_GenericEnumerator_CurrentPropertyVisible; +var + Prog: TProgram; + RT: TRecordTypeDesc; +begin + Prog := AnalyseSrc(SrcGenericEnumerator); + try + { The instantiated type is not used in a var decl, so trigger instantiation + by using the collection source which references TGenEnum. } + { Use the generic enumerator source that references no concrete param — + check that the template class definition itself still parsed the property. } + RT := TRecordTypeDesc(Prog.SymbolTable.FindType('TGenEnum')); + { TGenEnum is generic, not a concrete type; FindType should return nil. } + AssertNull('TGenEnum is not a concrete type', RT); + finally + Prog.Free; + end; +end; + +procedure TGenericForInTests.TestSemantic_GenericEnumerator_CurrentPropertyType; +var + Prog: TProgram; + RT: TRecordTypeDesc; + Prop: TPropertyInfo; +begin + { Instantiate TGenEnum via the collection source } + Prog := AnalyseSrc(SrcGenericCollection); + try + RT := TRecordTypeDesc(Prog.SymbolTable.FindType('TGenEnum')); + AssertNotNull('TGenEnum was instantiated', RT); + Prop := RT.FindProperty('Current'); + AssertNotNull('Current property present on TGenEnum', Prop); + AssertEquals('Current property type is tyInteger', + Ord(tyInteger), Ord(Prop.TypeDesc.Kind)); + finally + Prog.Free; + end; +end; + +{ ------------------------------------------------------------------ } +{ Blocker 2: TListEnumerator + TList.GetEnumerator } +{ ------------------------------------------------------------------ } + +procedure TGenericForInTests.TestParse_TListEnumerator_IsGenericTypeDef; +var + Prog: TProgram; + TD: TTypeDecl; +begin + Prog := ParseSrc(SrcTListEnumeratorDecl); + try + AssertEquals('Two type decls', 2, Prog.Block.TypeDecls.Count); + TD := TTypeDecl(Prog.Block.TypeDecls[0]); + AssertEquals('Name is TListEnumerator', 'TListEnumerator', TD.Name); + AssertTrue('Def is TGenericTypeDef', TD.Def is TGenericTypeDef); + finally + Prog.Free; + end; +end; + +procedure TGenericForInTests.TestParse_TList_GetEnumerator_Declared; +var + Prog: TProgram; + TD: TTypeDecl; + GD: TGenericTypeDef; + MD: TMethodDecl; + Found: Boolean; + I: Integer; +begin + Prog := ParseSrc(SrcTListEnumeratorDecl); + try + TD := TTypeDecl(Prog.Block.TypeDecls[1]); { TMyList } + GD := TGenericTypeDef(TD.Def); + Found := False; + for I := 0 to GD.ClassDef.Methods.Count - 1 do + begin + MD := TMethodDecl(GD.ClassDef.Methods.Items[I]); + if MD.Name = 'GetEnumerator' then + Found := True; + end; + AssertTrue('GetEnumerator method declared on TMyList', Found); + finally + Prog.Free; + end; +end; + +procedure TGenericForInTests.TestSemantic_TList_Integer_HasGetEnumerator; +var + Prog: TProgram; +begin + { A var of TMyList should trigger instantiation; semantic must + not raise an error (GetEnumerator return type TListEnumerator + must instantiate transitively). } + Prog := AnalyseSrc(SrcForInGenericList); + Prog.Free; { no exception = pass } +end; + +procedure TGenericForInTests.TestSemantic_TListEnumerator_Integer_HasCurrentProperty; +var + Prog: TProgram; + RT: TRecordTypeDesc; + Prop: TPropertyInfo; +begin + Prog := AnalyseSrc(SrcForInGenericList); + try + RT := TRecordTypeDesc(Prog.SymbolTable.FindType('TListEnumerator')); + AssertNotNull('TListEnumerator instantiated', RT); + Prop := RT.FindProperty('Current'); + AssertNotNull('Current property on TListEnumerator', Prop); + AssertEquals('Current type is tyInteger', + Ord(tyInteger), Ord(Prop.TypeDesc.Kind)); + finally + Prog.Free; + end; +end; + +{ ------------------------------------------------------------------ } +{ End-to-end: for..in on TList } +{ ------------------------------------------------------------------ } + +procedure TGenericForInTests.TestSemantic_ForIn_GenericList_OK; +var + Prog: TProgram; +begin + Prog := AnalyseSrc(SrcForInGenericList); + Prog.Free; { no exception = semantic check passed } +end; + +procedure TGenericForInTests.TestCodegen_ForIn_GenericList_CallsGetEnumerator; +var + IR: string; +begin + IR := GenIR(SrcForInGenericList); + AssertTrue('IR calls GetEnumerator', + Pos('TMyList_Integer_GetEnumerator', IR) > 0); +end; + +procedure TGenericForInTests.TestCodegen_ForIn_GenericList_CallsMoveNext; +var + IR: string; +begin + IR := GenIR(SrcForInGenericList); + AssertTrue('IR calls MoveNext', + Pos('TListEnumerator_Integer_MoveNext', IR) > 0); +end; + +procedure TGenericForInTests.TestCodegen_ForIn_GenericList_CallsGetCurrent; +var + IR: string; +begin + IR := GenIR(SrcForInGenericList); + AssertTrue('IR calls GetCurrent (Current getter)', + Pos('TListEnumerator_Integer_GetCurrent', IR) > 0); +end; + +initialization + RegisterTest(TGenericForInTests); + +end.