Browse Source

fcl-passrc: usenanalyzer: class constructor/destructor

git-svn-id: trunk@41359 -
Mattias Gaertner 6 years ago
parent
commit
894aebf8dd

+ 16 - 4
packages/fcl-passrc/src/pasresolver.pp

@@ -2012,6 +2012,7 @@ type
     function IsClassMethod(El: TPasElement): boolean;
     function IsClassMethod(El: TPasElement): boolean;
     function IsClassField(El: TPasElement): boolean;
     function IsClassField(El: TPasElement): boolean;
     function GetFunctionType(El: TPasElement): TPasFunctionType;
     function GetFunctionType(El: TPasElement): TPasFunctionType;
+    function MethodIsStatic(El: TPasProcedure): boolean;
     function IsMethod(El: TPasProcedure): boolean;
     function IsMethod(El: TPasProcedure): boolean;
     function IsHelperMethod(El: TPasElement): boolean;
     function IsHelperMethod(El: TPasElement): boolean;
     function IsHelper(El: TPasElement): boolean;
     function IsHelper(El: TPasElement): boolean;
@@ -5898,6 +5899,8 @@ begin
         RaiseMsg(20181231150305,nInvalidXModifierY,sInvalidXModifierY,[GetElementTypeName(Proc),'override'],Proc);
         RaiseMsg(20181231150305,nInvalidXModifierY,sInvalidXModifierY,[GetElementTypeName(Proc),'override'],Proc);
       if Proc.IsDynamic then
       if Proc.IsDynamic then
         RaiseMsg(20181231150319,nInvalidXModifierY,sInvalidXModifierY,[GetElementTypeName(Proc),'dynamic'],Proc);
         RaiseMsg(20181231150319,nInvalidXModifierY,sInvalidXModifierY,[GetElementTypeName(Proc),'dynamic'],Proc);
+      if Proc.IsStatic then
+        RaiseMsg(20190216214651,nInvalidXModifierY,sInvalidXModifierY,[GetElementTypeName(Proc),'static'],Proc);
       if El.Args.Count>0 then
       if El.Args.Count>0 then
         RaiseMsg(20181231150404,nXCannotHaveParameters,sXCannotHaveParameters,[GetElementTypeName(Proc)],Proc);
         RaiseMsg(20181231150404,nXCannotHaveParameters,sXCannotHaveParameters,[GetElementTypeName(Proc)],Proc);
       end;
       end;
@@ -5931,7 +5934,7 @@ begin
           RaiseMsg(20190116215823,nInvalidXModifierY,sInvalidXModifierY,[ObjKindNames[ObjKind]+' '+GetElementTypeName(Proc),'virtual'],Proc);
           RaiseMsg(20190116215823,nInvalidXModifierY,sInvalidXModifierY,[ObjKindNames[ObjKind]+' '+GetElementTypeName(Proc),'virtual'],Proc);
         if Proc.IsOverride then
         if Proc.IsOverride then
           RaiseMsg(20190116215825,nInvalidXModifierY,sInvalidXModifierY,[ObjKindNames[ObjKind]+' '+GetElementTypeName(Proc),'override'],Proc);
           RaiseMsg(20190116215825,nInvalidXModifierY,sInvalidXModifierY,[ObjKindNames[ObjKind]+' '+GetElementTypeName(Proc),'override'],Proc);
-        if (ObjKind<>okClassHelper) and IsClassMethod(Proc) then
+        if (ObjKind<>okClassHelper) and IsClassMethod(Proc) and not IsClassConDestructor then
           begin
           begin
           if not Proc.IsStatic then
           if not Proc.IsStatic then
             RaiseMsg(20190201153831,nClassMethodsMustBeStaticInX,sClassMethodsMustBeStaticInX,[ObjKindNames[ObjKind]],Proc);
             RaiseMsg(20190201153831,nClassMethodsMustBeStaticInX,sClassMethodsMustBeStaticInX,[ObjKindNames[ObjKind]],Proc);
@@ -5972,8 +5975,9 @@ begin
         RaiseMsg(20181218195552,nInvalidXModifierY,sInvalidXModifierY,['record '+GetElementTypeName(Proc),'abstract'],Proc);
         RaiseMsg(20181218195552,nInvalidXModifierY,sInvalidXModifierY,['record '+GetElementTypeName(Proc),'abstract'],Proc);
       if Proc.IsForward then
       if Proc.IsForward then
         RaiseMsg(20181218195514,nInvalidXModifierY,sInvalidXModifierY,['record '+GetElementTypeName(Proc),'forward'],Proc);
         RaiseMsg(20181218195514,nInvalidXModifierY,sInvalidXModifierY,['record '+GetElementTypeName(Proc),'forward'],Proc);
-      if IsClassMethod(Proc) then
+      if IsClassMethod(Proc) and not IsClassConDestructor then
         begin
         begin
+        // Note: class constructor/destructor must not be static
         if not Proc.IsStatic then
         if not Proc.IsStatic then
           RaiseMsg(20190106121503,nClassMethodsMustBeStaticInX,sClassMethodsMustBeStaticInX,['records'],Proc);
           RaiseMsg(20190106121503,nClassMethodsMustBeStaticInX,sClassMethodsMustBeStaticInX,['records'],Proc);
         end
         end
@@ -6292,8 +6296,9 @@ begin
     begin
     begin
     // add 'Self'
     // add 'Self'
     if (DeclProc.ClassType=TPasClassConstructor)
     if (DeclProc.ClassType=TPasClassConstructor)
-        or (DeclProc.ClassType=TPasClassDestructor)
-        or (DeclProc.ClassType=TPasClassProcedure)
+        or (DeclProc.ClassType=TPasClassDestructor) then
+      // actually class constructor/destructor are static
+    else if (DeclProc.ClassType=TPasClassProcedure)
         or (DeclProc.ClassType=TPasClassFunction) then
         or (DeclProc.ClassType=TPasClassFunction) then
       begin
       begin
       if (ClassOrRecScope is TPasClassScope)
       if (ClassOrRecScope is TPasClassScope)
@@ -22310,6 +22315,13 @@ begin
     Result:=nil;
     Result:=nil;
 end;
 end;
 
 
+function TPasResolver.MethodIsStatic(El: TPasProcedure): boolean;
+begin
+  Result:=(ptmStatic in El.ProcType.Modifiers)
+    or (El.ClassType=TPasClassConstructor)
+    or (El.ClassType=TPasClassDestructor);
+end;
+
 function TPasResolver.IsMethod(El: TPasProcedure): boolean;
 function TPasResolver.IsMethod(El: TPasProcedure): boolean;
 var
 var
   ProcScope: TPasProcedureScope;
   ProcScope: TPasProcedureScope;

+ 74 - 20
packages/fcl-passrc/src/pasuseanalyzer.pas

@@ -218,12 +218,16 @@ const
     );
     );
 
 
 type
 type
+  TPAOtherCheckedEl = (
+    pocClassConstructor
+    );
 
 
   { TPasAnalyzer }
   { TPasAnalyzer }
 
 
   TPasAnalyzer = class
   TPasAnalyzer = class
   private
   private
-    FChecked: array[TPAUseMode] of TPasAnalyzerKeySet; // tree of TElement
+    FModeChecked: array[TPAUseMode] of TPasAnalyzerKeySet; // tree of TElement
+    FOtherChecked: array[TPAOtherCheckedEl] of TPasAnalyzerKeySet; // tree of TElement
     FOnMessage: TPAMessageEvent;
     FOnMessage: TPAMessageEvent;
     FOptions: TPasAnalyzerOptions;
     FOptions: TPasAnalyzerOptions;
     FOverrideLists: TPasAnalyzerKeySet; // tree of TPAOverrideList sorted for Element
     FOverrideLists: TPasAnalyzerKeySet; // tree of TPAOverrideList sorted for Element
@@ -245,7 +249,8 @@ type
     function PAElementExists(El: TPasElement): boolean; inline;
     function PAElementExists(El: TPasElement): boolean; inline;
     procedure CreateTree; virtual;
     procedure CreateTree; virtual;
     function MarkElementAsUsed(El: TPasElement; aClass: TPAElementClass = nil): boolean; // true if new
     function MarkElementAsUsed(El: TPasElement; aClass: TPAElementClass = nil): boolean; // true if new
-    function ElementVisited(El: TPasElement; Mode: TPAUseMode): boolean;
+    function ElementVisited(El: TPasElement; Mode: TPAUseMode): boolean; overload;
+    function ElementVisited(El: TPasElement; OtherCheck: TPAOtherCheckedEl): boolean; overload;
     procedure MarkImplScopeRef(El, RefEl: TPasElement; Access: TPSRefAccess);
     procedure MarkImplScopeRef(El, RefEl: TPasElement; Access: TPSRefAccess);
     procedure UseElement(El: TPasElement; Access: TResolvedRefAccess;
     procedure UseElement(El: TPasElement; Access: TResolvedRefAccess;
       UseFull: boolean); virtual;
       UseFull: boolean); virtual;
@@ -263,6 +268,7 @@ type
     procedure UseProcedureType(ProcType: TPasProcedureType); virtual;
     procedure UseProcedureType(ProcType: TPasProcedureType); virtual;
     procedure UseType(El: TPasType; Mode: TPAUseMode); virtual;
     procedure UseType(El: TPasType; Mode: TPAUseMode); virtual;
     procedure UseClassOrRecType(El: TPasMembersType; Mode: TPAUseMode); virtual;
     procedure UseClassOrRecType(El: TPasMembersType; Mode: TPAUseMode); virtual;
+    procedure UseClassConstructor(El: TPasMembersType); virtual;
     procedure UseVariable(El: TPasVariable; Access: TResolvedRefAccess;
     procedure UseVariable(El: TPasVariable; Access: TResolvedRefAccess;
       UseFull: boolean); virtual;
       UseFull: boolean); virtual;
     procedure UseResourcestring(El: TPasResString); virtual;
     procedure UseResourcestring(El: TPasResString); virtual;
@@ -952,9 +958,19 @@ function TPasAnalyzer.ElementVisited(El: TPasElement; Mode: TPAUseMode
 begin
 begin
   if El=nil then
   if El=nil then
     exit(true);
     exit(true);
-  if FChecked[Mode].ContainsItem(El) then exit(true);
+  if FModeChecked[Mode].ContainsItem(El) then exit(true);
+  Result:=false;
+  FModeChecked[Mode].Add(El,false);
+end;
+
+function TPasAnalyzer.ElementVisited(El: TPasElement;
+  OtherCheck: TPAOtherCheckedEl): boolean;
+begin
+  if El=nil then
+    exit(true);
+  if FOtherChecked[OtherCheck].ContainsItem(El) then exit(true);
   Result:=false;
   Result:=false;
-  FChecked[Mode].Add(El,false);
+  FOtherChecked[OtherCheck].Add(El,false);
 end;
 end;
 
 
 procedure TPasAnalyzer.MarkImplScopeRef(El, RefEl: TPasElement;
 procedure TPasAnalyzer.MarkImplScopeRef(El, RefEl: TPasElement;
@@ -1021,6 +1037,8 @@ begin
     El:=El.Parent;
     El:=El.Parent;
     if not (El is TPasType) then break;
     if not (El is TPasType) then break;
     MarkElementAsUsed(El);
     MarkElementAsUsed(El);
+    if El is TPasMembersType then
+      UseClassConstructor(TPasMembersType(El));
   until false;
   until false;
 end;
 end;
 
 
@@ -1544,7 +1562,6 @@ begin
   UseExpr(El.format2);
   UseExpr(El.format2);
   C:=El.ClassType;
   C:=El.ClassType;
   if (C=TPrimitiveExpr)
   if (C=TPrimitiveExpr)
-      or (C=TSelfExpr)
       or (C=TBoolConstExpr)
       or (C=TBoolConstExpr)
       or (C=TNilExpr) then
       or (C=TNilExpr) then
     // ok
     // ok
@@ -1618,7 +1635,7 @@ begin
       RaiseNotSupported(20170403173817,Params);
       RaiseNotSupported(20170403173817,Params);
     end;
     end;
     end
     end
-  else if (C=TSelfExpr) or ((C=TPrimitiveExpr) and (TPrimitiveExpr(Expr).Kind=pekIdent)) then
+  else if (C=TPrimitiveExpr) and (TPrimitiveExpr(Expr).Kind=pekIdent) then
     begin
     begin
     if (Expr.CustomData is TResolvedReference) then
     if (Expr.CustomData is TResolvedReference) then
       begin
       begin
@@ -1942,7 +1959,7 @@ var
   List, ProcList: TFPList;
   List, ProcList: TFPList;
   o: TObject;
   o: TObject;
   Map: TPasClassIntfMap;
   Map: TPasClassIntfMap;
-  ImplProc, IntfProc: TPasProcedure;
+  ImplProc, IntfProc, Proc: TPasProcedure;
   aClass: TPasClassType;
   aClass: TPasClassType;
 begin
 begin
   FirstTime:=true;
   FirstTime:=true;
@@ -1970,7 +1987,7 @@ begin
   ClassScope:=nil;
   ClassScope:=nil;
   IsCOMInterfaceRoot:=false;
   IsCOMInterfaceRoot:=false;
 
 
-  if El is TPasClassType then
+  if El.ClassType=TPasClassType then
     begin
     begin
     aClass:=TPasClassType(El);
     aClass:=TPasClassType(El);
     if aClass.IsForward then
     if aClass.IsForward then
@@ -2022,37 +2039,44 @@ begin
     Member:=TPasElement(El.Members[i]);
     Member:=TPasElement(El.Members[i]);
     if FirstTime and (Member is TPasProcedure) then
     if FirstTime and (Member is TPasProcedure) then
       begin
       begin
+      Proc:=TPasProcedure(Member);
       ProcScope:=Member.CustomData as TPasProcedureScope;
       ProcScope:=Member.CustomData as TPasProcedureScope;
-      if TPasProcedure(Member).IsOverride and (ProcScope.OverriddenProc<>nil) then
+      if Proc.IsOverride and (ProcScope.OverriddenProc<>nil) then
         begin
         begin
         // this is an override
         // this is an override
         AddOverride(ProcScope.OverriddenProc,Member);
         AddOverride(ProcScope.OverriddenProc,Member);
         if ScopeModule<>nil then
         if ScopeModule<>nil then
           begin
           begin
           // when analyzing a single module, all overrides are assumed to be called
           // when analyzing a single module, all overrides are assumed to be called
-          UseProcedure(TPasProcedure(Member));
+          UseProcedure(Proc);
           continue;
           continue;
           end;
           end;
+        end
+      else if (Proc.ClassType=TPasClassConstructor)
+          or (Proc.ClassType=TPasClassDestructor) then
+        begin
+        UseProcedure(Proc);
+        continue;
         end;
         end;
       if IsCOMInterfaceRoot then
       if IsCOMInterfaceRoot then
         begin
         begin
         case lowercase(Member.Name) of
         case lowercase(Member.Name) of
         'queryinterface':
         'queryinterface':
-          if (TPasProcedure(Member).ProcType.Args.Count=2) then
+          if (Proc.ProcType.Args.Count=2) then
             begin
             begin
-            UseProcedure(TPasProcedure(Member));
+            UseProcedure(Proc);
             continue;
             continue;
             end;
             end;
         '_addref':
         '_addref':
-          if TPasProcedure(Member).ProcType.Args.Count=0 then
+          if Proc.ProcType.Args.Count=0 then
             begin
             begin
-            UseProcedure(TPasProcedure(Member));
+            UseProcedure(Proc);
             continue;
             continue;
             end;
             end;
         '_release':
         '_release':
-          if TPasProcedure(Member).ProcType.Args.Count=0 then
+          if Proc.ProcType.Args.Count=0 then
             begin
             begin
-            UseProcedure(TPasProcedure(Member));
+            UseProcedure(Proc);
             continue;
             continue;
             end;
             end;
         end;
         end;
@@ -2119,6 +2143,20 @@ begin
     end;
     end;
 end;
 end;
 
 
+procedure TPasAnalyzer.UseClassConstructor(El: TPasMembersType);
+var
+  i: Integer;
+  Member: TPasElement;
+begin
+  if ElementVisited(El,pocClassConstructor) then exit;
+  for i:=0 to El.Members.Count-1 do
+    begin
+    Member:=TPasElement(El.Members[i]);
+    if (Member.ClassType=TPasClassConstructor) or (Member.ClassType=TPasClassDestructor) then
+      UseProcedure(TPasProcedure(Member));
+    end;
+end;
+
 procedure TPasAnalyzer.UseVariable(El: TPasVariable;
 procedure TPasAnalyzer.UseVariable(El: TPasVariable;
   Access: TResolvedRefAccess; UseFull: boolean);
   Access: TResolvedRefAccess; UseFull: boolean);
 var
 var
@@ -2612,10 +2650,20 @@ end;
 constructor TPasAnalyzer.Create;
 constructor TPasAnalyzer.Create;
 var
 var
   m: TPAUseMode;
   m: TPAUseMode;
+  oc: TPAOtherCheckedEl;
 begin
 begin
   CreateTree;
   CreateTree;
   for m in TPAUseMode do
   for m in TPAUseMode do
-    FChecked[m]:=TPasAnalyzerKeySet.Create(
+    FModeChecked[m]:=TPasAnalyzerKeySet.Create(
+      {$ifdef pas2js}
+      @PasElementToHashName
+      {$else}
+      @ComparePointer
+      {$endif}
+      ,nil
+      );
+  for oc in TPAOtherCheckedEl do
+    FOtherChecked[oc]:=TPasAnalyzerKeySet.Create(
       {$ifdef pas2js}
       {$ifdef pas2js}
       @PasElementToHashName
       @PasElementToHashName
       {$else}
       {$else}
@@ -2634,23 +2682,29 @@ end;
 destructor TPasAnalyzer.Destroy;
 destructor TPasAnalyzer.Destroy;
 var
 var
   m: TPAUseMode;
   m: TPAUseMode;
+  oc: TPAOtherCheckedEl;
 begin
 begin
   Clear;
   Clear;
   FreeAndNil(FOverrideLists);
   FreeAndNil(FOverrideLists);
   FreeAndNil(FUsedElements);
   FreeAndNil(FUsedElements);
   for m in TPAUseMode do
   for m in TPAUseMode do
-    FreeAndNil(FChecked[m]);
+    FreeAndNil(FModeChecked[m]);
+  for oc in TPAOtherCheckedEl do
+    FreeAndNil(FOtherChecked[oc]);
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
 procedure TPasAnalyzer.Clear;
 procedure TPasAnalyzer.Clear;
 var
 var
   m: TPAUseMode;
   m: TPAUseMode;
+  oc: TPAOtherCheckedEl;
 begin
 begin
   FOverrideLists.FreeItems;
   FOverrideLists.FreeItems;
   FUsedElements.FreeItems;
   FUsedElements.FreeItems;
   for m in TPAUseMode do
   for m in TPAUseMode do
-    FChecked[m].Clear;
+    FModeChecked[m].Clear;
+  for oc in TPAOtherCheckedEl do
+    FOtherChecked[oc].Clear;
 end;
 end;
 
 
 procedure TPasAnalyzer.AnalyzeModule(aModule: TPasModule);
 procedure TPasAnalyzer.AnalyzeModule(aModule: TPasModule);
@@ -2732,7 +2786,7 @@ end;
 
 
 function TPasAnalyzer.IsTypeInfoUsed(El: TPasElement): boolean;
 function TPasAnalyzer.IsTypeInfoUsed(El: TPasElement): boolean;
 begin
 begin
-  Result:=FChecked[paumTypeInfo].ContainsItem(El);
+  Result:=FModeChecked[paumTypeInfo].ContainsItem(El);
 end;
 end;
 
 
 function TPasAnalyzer.IsModuleInternal(El: TPasElement): boolean;
 function TPasAnalyzer.IsModuleInternal(El: TPasElement): boolean;

+ 43 - 1
packages/fcl-passrc/tests/tcresolver.pas

@@ -504,6 +504,8 @@ type
     Procedure TestAdvRecord_ConstructorNoParamsFail;
     Procedure TestAdvRecord_ConstructorNoParamsFail;
     Procedure TestAdvRecord_ClassConstructor;
     Procedure TestAdvRecord_ClassConstructor;
     Procedure TestAdvRecord_ClassConstructorParamsFail;
     Procedure TestAdvRecord_ClassConstructorParamsFail;
+    Procedure TestAdvRecord_ClassConstructor_CallFail;
+    Procedure TestAdvRecord_ClassConstructorDuplicateFail;
     Procedure TestAdvRecord_NestedRecordType;
     Procedure TestAdvRecord_NestedRecordType;
     Procedure TestAdvRecord_NestedArgConstFail;
     Procedure TestAdvRecord_NestedArgConstFail;
     Procedure TestAdvRecord_Property;
     Procedure TestAdvRecord_Property;
@@ -8249,7 +8251,7 @@ begin
   '  TRec = record',
   '  TRec = record',
   '    class var w: word;',
   '    class var w: word;',
   '    class procedure {#a}Create; static;',
   '    class procedure {#a}Create; static;',
-  '    class constructor Create; static;',
+  '    class constructor Create;', // name clash is allowed!
   '  end;',
   '  end;',
   'class constructor TRec.Create;',
   'class constructor TRec.Create;',
   'begin',
   'begin',
@@ -8282,6 +8284,46 @@ begin
   CheckResolverException('class constructor cannot have parameters',nXCannotHaveParameters);
   CheckResolverException('class constructor cannot have parameters',nXCannotHaveParameters);
 end;
 end;
 
 
+procedure TTestResolver.TestAdvRecord_ClassConstructor_CallFail;
+begin
+  StartProgram(false);
+  Add([
+  '{$modeswitch advancedrecords}',
+  'type',
+  '  TRec = record',
+  '    class constructor Create;',
+  '  end;',
+  'class constructor TRec.Create;',
+  'begin',
+  'end;',
+  'begin',
+  '  TRec.Create;',
+  '']);
+  CheckResolverException('identifier not found "Create"',nIdentifierNotFound);
+end;
+
+procedure TTestResolver.TestAdvRecord_ClassConstructorDuplicateFail;
+begin
+  StartProgram(false);
+  Add([
+  '{$modeswitch advancedrecords}',
+  'type',
+  '  TRec = record',
+  '    class constructor Create;',
+  '    class constructor Init;',
+  '  end;',
+  'class constructor TRec.Create;',
+  'begin',
+  'end;',
+  'class constructor TRec.Init;',
+  'begin',
+  'end;',
+  'begin',
+  '']);
+  CheckResolverException('Multiple class constructor in record TRec: Create and Init',
+    nMultipleXinTypeYNameZCAandB);
+end;
+
 procedure TTestResolver.TestAdvRecord_NestedRecordType;
 procedure TTestResolver.TestAdvRecord_NestedRecordType;
 begin
 begin
   StartProgram(false);
   StartProgram(false);

+ 58 - 0
packages/fcl-passrc/tests/tcuseanalyzer.pas

@@ -165,6 +165,7 @@ type
     procedure TestWP_ClassInterface_Typeinfo;
     procedure TestWP_ClassInterface_Typeinfo;
     procedure TestWP_ClassInterface_TGUID;
     procedure TestWP_ClassInterface_TGUID;
     procedure TestWP_ClassHelper;
     procedure TestWP_ClassHelper;
+    procedure TestWP_ClassHelper_ClassConstrucor_Used;
 
 
     // scope references
     // scope references
     procedure TestSR_Proc_UnitVar;
     procedure TestSR_Proc_UnitVar;
@@ -3093,6 +3094,63 @@ begin
   AnalyzeWholeProgram;
   AnalyzeWholeProgram;
 end;
 end;
 
 
+procedure TTestUseAnalyzer.TestWP_ClassHelper_ClassConstrucor_Used;
+begin
+  StartProgram(false);
+  Add([
+  'type',
+  '  {#TObject_used}TObject = class',
+  '    class constructor {#TObject_Init_used}Init;',
+  '    class destructor {#TObject_Done_used}Done;',
+  '  end;',
+  '  {#TBird_used}TBird = class',
+  '    {#TBird_A_notused}A: word;',
+  '    class constructor {#TBird_Init_used}Init;',
+  '    class destructor {#TBird_Done_used}Done;',
+  '  end;',
+  '  {#TBirdHelper_used}TBirdHelper = class helper for TBird',
+  '    procedure {#TBirdHelper_Fly_used}Fly;',
+  '    class constructor {#TBirdHelper_Init_used}Init;',
+  '    class destructor {#TBirdHelper_Done_used}Done;',
+  '  end;',
+  '  TAnt = class',
+  '    class constructor {#TAnt_Init_notused}Init;',
+  '    class destructor {#TAnt_Done_notused}Done;',
+  '  end;',
+  'class constructor TObject.Init;',
+  'begin',
+  'end;',
+  'class destructor TObject.Done;',
+  'begin',
+  'end;',
+  'class constructor TBird.Init;',
+  'begin',
+  'end;',
+  'class destructor TBird.Done;',
+  'begin',
+  'end;',
+  'procedure TBirdHelper.Fly;',
+  'begin',
+  'end;',
+  'class constructor TBirdHelper.Init;',
+  'begin',
+  'end;',
+  'class destructor TBirdHelper.Done;',
+  'begin',
+  'end;',
+  'class constructor TAnt.Init;',
+  'begin',
+  'end;',
+  'class destructor TAnt.Done;',
+  'begin',
+  'end;',
+  'var b: TBird;',
+  'begin',
+  '  b.Fly;',
+  '']);
+  AnalyzeWholeProgram;
+end;
+
 procedure TTestUseAnalyzer.TestSR_Proc_UnitVar;
 procedure TTestUseAnalyzer.TestSR_Proc_UnitVar;
 begin
 begin
   StartUnit(false);
   StartUnit(false);