From 5efbfcc2b0dbc2fd40bd176d7d3a0f3d5e832aa7 Mon Sep 17 00:00:00 2001 From: Mattias Gaertner Date: Mon, 7 Jan 2019 15:54:33 +0000 Subject: [PATCH] fcl-passrc: parser: local and anonymous records cannot be advanced, resolver: adv records: recordvalues, class methods must be static, sub class git-svn-id: trunk@40795 - --- packages/fcl-passrc/src/pasresolveeval.pas | 6 +- packages/fcl-passrc/src/pasresolver.pp | 117 ++++++++++++++------- packages/fcl-passrc/src/pparser.pp | 17 ++- packages/fcl-passrc/tests/tcresolver.pas | 115 +++++++++++++++++++- 4 files changed, 205 insertions(+), 50 deletions(-) diff --git a/packages/fcl-passrc/src/pasresolveeval.pas b/packages/fcl-passrc/src/pasresolveeval.pas index 609619ca41..b902310aea 100644 --- a/packages/fcl-passrc/src/pasresolveeval.pas +++ b/packages/fcl-passrc/src/pasresolveeval.pas @@ -154,14 +154,14 @@ const nMethodHidesMethodOfBaseType = 3077; nContextExpectedXButFoundY = 3078; nContextXInvalidY = 3079; - // free 3080; + nIdentifierXIsNotAnInstanceField = 3080; nXIsNotSupported = 3081; nOperatorIsNotOverloadedAOpB = 3082; nIllegalQualifierAfter = 3084; nIllegalQualifierInFrontOf = 3085; nIllegalQualifierWithin = 3086; nMethodClassXInOtherUnitY = 3087; - // free 3088 + nClassMethodsMustBeStaticInRecords = 3088; nCannotMixMethodResolutionAndDelegationAtX = 3089; nImplementsDoesNotSupportArrayProperty = 3101; nImplementsDoesNotSupportIndex = 3102; @@ -277,6 +277,7 @@ resourcestring sMethodHidesMethodOfBaseType = 'Method "%s" hides method of base type "%s" at %s'; sContextExpectedXButFoundY = '%s: expected "%s", but found "%s"'; sContextXInvalidY = '%s: invalid %s'; + sIdentifierXIsNotAnInstanceField = 'Identifier "%s" is not an instance field'; sConstructingClassXWithAbstractMethodY = 'Constructing a class "%s" with abstract method "%s"'; sXIsNotSupported = '%s is not supported'; sOperatorIsNotOverloadedAOpB = 'Operator is not overloaded: "%s" %s "%s"'; @@ -285,6 +286,7 @@ resourcestring sIllegalQualifierWithin = 'illegal qualifier "%s" within "%s"'; sMethodClassXInOtherUnitY = 'method class "%s" in other unit "%s"'; sNoMatchingImplForIntfMethodXFound = 'No matching implementation for interface method "%s" found'; + sClassMethodsMustBeStaticInRecords = 'Class methods must be static in records'; sCannotMixMethodResolutionAndDelegationAtX = 'Cannot mix method resolution and delegation at %s'; sImplementsDoesNotSupportArrayProperty = '"implements" does dot support array property'; sImplementsDoesNotSupportIndex = '"implements" does not support "index"'; diff --git a/packages/fcl-passrc/src/pasresolver.pp b/packages/fcl-passrc/src/pasresolver.pp index cfd517532d..66f398bda9 100644 --- a/packages/fcl-passrc/src/pasresolver.pp +++ b/packages/fcl-passrc/src/pasresolver.pp @@ -913,7 +913,7 @@ type DeclarationProc: TPasProcedure; // the corresponding forward declaration ImplProc: TPasProcedure; // the corresponding proc with Body OverriddenProc: TPasProcedure; // if IsOverride then this is the ancestor proc (virtual or override) - ClassScope: TPasClassOrRecordScope; + ClassOrRecordScope: TPasClassOrRecordScope; SelfArg: TPasArgument; Flags: TPasProcedureScopeFlags; BoolSwitches: TBoolSwitches; // if Body<>nil then body start, otherwise when FinishProc @@ -1860,7 +1860,7 @@ type function IsVariableConst(El, PosEl: TPasElement; RaiseIfConst: boolean): boolean; virtual; function ResolvedElCanBeVarParam(const ResolvedEl: TPasResolverResult; PosEl: TPasElement; RaiseIfConst: boolean = true): boolean; - function ResolvedElIsClassInstance(const ResolvedEl: TPasResolverResult): boolean; + function ResolvedElIsClassOrRecordInstance(const ResolvedEl: TPasResolverResult): boolean; // utility functions function ElHasModeSwitch(El: TPasElement; ms: TModeSwitch): boolean; function GetElModeSwitches(El: TPasElement): TModeSwitches; @@ -2975,7 +2975,7 @@ var begin Result:=inherited FindIdentifier(Identifier); if Result<>nil then exit; - CurScope:=ClassScope; + CurScope:=ClassOrRecordScope; if CurScope=nil then exit; repeat Result:=CurScope.FindIdentifier(Identifier); @@ -3000,7 +3000,7 @@ var begin inherited IterateElements(aName, StartScope, OnIterateElement, Data, Abort); if Abort then exit; - CurScope:=ClassScope; + CurScope:=ClassOrRecordScope; if CurScope=nil then exit; repeat CurScope.IterateElements(aName, StartScope, OnIterateElement, Data, Abort); @@ -3022,7 +3022,7 @@ var begin Result:=Self; repeat - if Result.ClassScope<>nil then exit; + if Result.ClassOrRecordScope<>nil then exit; Proc:=TPasProcedure(Element); if not (Proc.Parent is TProcedureBody) then exit(nil); Proc:=Proc.Parent.Parent as TPasProcedure; @@ -3033,8 +3033,8 @@ end; procedure TPasProcedureScope.WriteIdentifiers(Prefix: string); begin inherited WriteIdentifiers(Prefix); - if ClassScope<>nil then - ClassScope.WriteIdentifiers(Prefix+'CS '); + if ClassOrRecordScope<>nil then + ClassOrRecordScope.WriteIdentifiers(Prefix+'CS '); end; destructor TPasProcedureScope.Destroy; @@ -5569,9 +5569,14 @@ begin RaiseMsg(20181218195552,nInvalidXModifierY,sInvalidXModifierY,['record '+GetElementTypeName(Proc),'abstract'],Proc); if Proc.IsForward then RaiseMsg(20181218195514,nInvalidXModifierY,sInvalidXModifierY,['record '+GetElementTypeName(Proc),'forward'],Proc); - if Proc.IsStatic then - if (Proc.ClassType<>TPasClassProcedure) and (Proc.ClassType<>TPasClassFunction) then - RaiseMsg(20181218195519,nInvalidXModifierY,sInvalidXModifierY,['record '+GetElementTypeName(Proc),'static'],Proc); + if (Proc.ClassType=TPasClassProcedure) + or (Proc.ClassType=TPasClassFunction) + or (Proc.ClassType=TPasClassConstructor) + or (Proc.ClassType=TPasClassDestructor) then + begin + if not Proc.IsStatic then + RaiseMsg(20190106121503,nClassMethodsMustBeStaticInRecords,sClassMethodsMustBeStaticInRecords,[],Proc); + end; end else begin @@ -5742,7 +5747,7 @@ begin // ToDo: store the scanner flags *before* it has parsed the token after the proc StoreScannerFlagsInProc(ProcScope); ClassOrRecScope:=Scopes[ScopeCount-2] as TPasClassOrRecordScope; - ProcScope.ClassScope:=ClassOrRecScope; + ProcScope.ClassOrRecordScope:=ClassOrRecScope; FindData:=Default(TFindOverloadProcData); FindData.Proc:=Proc; FindData.Args:=Proc.ProcType.Args; @@ -5842,7 +5847,7 @@ begin // search proc in class/record ImplProcScope:=ImplProc.CustomData as TPasProcedureScope; - ClassOrRecScope:=ImplProcScope.ClassScope; + ClassOrRecScope:=ImplProcScope.ClassOrRecordScope; if ClassOrRecScope=nil then RaiseInternalError(20161013172346); ClassRecType:=NoNil(ClassOrRecScope.Element) as TPasMembersType; @@ -5881,7 +5886,7 @@ begin or (DeclProc.ClassType=TPasClassProcedure) or (DeclProc.ClassType=TPasClassFunction) then begin - if (not DeclProc.IsStatic) and (ClassOrRecScope is TPasClassScope) then + if ClassOrRecScope is TPasClassScope then begin // 'Self' in a class proc is the hidden classtype argument SelfArg:=TPasArgument.Create('Self',DeclProc); @@ -5891,7 +5896,9 @@ begin SelfArg.ArgType:=TPasClassScope(ClassOrRecScope).CanonicalClassOf; SelfArg.ArgType.AddRef{$IFDEF CheckPasTreeRefCount}('TPasArgument.ArgType'){$ENDIF}; AddIdentifier(ImplProcScope,'Self',SelfArg,pikSimple); - end; + end + else + RaiseInternalError(20190106121745); end else begin @@ -6071,6 +6078,14 @@ var end; end; + function ExpectedClassAccessorStatic: boolean; + begin + if (ClassScope<>nil) and (proClassPropertyNonStatic in Options) then + Result:=false + else + Result:=true; + end; + procedure CheckIndexArg(ArgNo: integer; const IndexResolved: TPasResolverResult; ProcArg: TPasArgument; ErrorEl: TPasElement); var @@ -6476,7 +6491,7 @@ begin begin if Proc.ClassType<>TPasClassFunction then RaiseXExpectedButYFound(20170216151834,'class function',GetElementTypeName(Proc),PropEl.ReadAccessor); - if Proc.IsStatic=(proClassPropertyNonStatic in Options) then + if Proc.IsStatic<>ExpectedClassAccessorStatic then if Proc.IsStatic then RaiseMsg(20170216151837,nClassPropertyAccessorMustNotBeStatic,sClassPropertyAccessorMustNotBeStatic,[],PropEl.ReadAccessor) else @@ -6531,11 +6546,11 @@ begin begin if Proc.ClassType<>TPasClassProcedure then RaiseXExpectedButYFound(20170216151903,'class procedure',GetElementTypeName(Proc),PropEl.WriteAccessor); - if Proc.IsStatic=(proClassPropertyNonStatic in Options) then - if Proc.IsStatic then - RaiseMsg(20170216151905,nClassPropertyAccessorMustNotBeStatic,sClassPropertyAccessorMustNotBeStatic,[],PropEl.WriteAccessor) - else - RaiseMsg(20170216151906,nClassPropertyAccessorMustBeStatic,sClassPropertyAccessorMustBeStatic,[],PropEl.WriteAccessor); + if Proc.IsStatic<>ExpectedClassAccessorStatic then + if Proc.IsStatic then + RaiseMsg(20170216151905,nClassPropertyAccessorMustNotBeStatic,sClassPropertyAccessorMustNotBeStatic,[],PropEl.WriteAccessor) + else + RaiseMsg(20170216151906,nClassPropertyAccessorMustBeStatic,sClassPropertyAccessorMustBeStatic,[],PropEl.WriteAccessor); end else begin @@ -6617,12 +6632,22 @@ var DirectAncestor: TPasType; // e.g. TPasAliasType or TPasClassType AncestorClassEl: TPasClassType; + function IsDefaultAncestor(c: TPasClassType; const DefAncestorName: string): boolean; + begin + Result:=SameText(c.Name,DefAncestorName) + and (c.Parent is TPasSection); + end; + procedure FindDefaultAncestor(const DefAncestorName, Expected: string); var CurEl: TPasElement; begin AncestorClassEl:=nil; - if (CompareText(aClass.Name,DefAncestorName)=0) then exit; + if SameText(aClass.Name,DefAncestorName) then + begin + if IsDefaultAncestor(aClass,DefAncestorName) then exit; + RaiseXExpectedButYFound(20190106132328,'top level '+DefAncestorName,'nested '+aClass.Name,aClass); + end; CurEl:=FindElementWithoutParams(DefAncestorName,aClass,false); if not (CurEl is TPasType) then RaiseXExpectedButYFound(20180321150128,Expected,GetElementTypeName(CurEl),aClass); @@ -6715,7 +6740,7 @@ begin okClass: begin DefAncestorName:='TObject'; - if (CompareText(aClass.Name,DefAncestorName)=0) or aClass.IsExternal then + if aClass.IsExternal or IsDefaultAncestor(aClass,DefAncestorName) then begin // ok, no ancestor AncestorClassEl:=nil; @@ -6736,7 +6761,7 @@ begin DefAncestorName:='IInterface' else DefAncestorName:='IUnknown'; - if SameText(DefAncestorName,aClass.Name) then + if IsDefaultAncestor(aClass,DefAncestorName) then AncestorClassEl:=nil else begin @@ -8128,7 +8153,7 @@ begin SelfScope:=ProcScope.GetSelfScope; if SelfScope=nil then RaiseMsg(20170216152141,nInheritedOnlyWorksInMethods,sInheritedOnlyWorksInMethods,[],El); - ClassRecScope:=SelfScope.ClassScope; + ClassRecScope:=SelfScope.ClassOrRecordScope; AncestorScope:=nil; if ClassRecScope is TPasClassScope then @@ -8183,7 +8208,7 @@ begin SelfScope:=ProcScope.GetSelfScope; if SelfScope=nil then RaiseMsg(20170216152148,nInheritedOnlyWorksInMethods,sInheritedOnlyWorksInMethods,[],El); - ClassRecScope:=SelfScope.ClassScope; + ClassRecScope:=SelfScope.ClassOrRecordScope; AncestorScope:=nil; if ClassRecScope is TPasClassScope then @@ -8895,7 +8920,7 @@ procedure TPasResolver.ResolveRecordValues(El: TRecordValues); if SameText(Result.Name,aName) then exit; end; - if (RecType.VariantEl is TPasVariable) then + if RecType.VariantEl is TPasVariable then begin Result:=TPasVariable(RecType.VariantEl); if SameText(Result.Name,aName) then @@ -8938,9 +8963,12 @@ begin Member:=GetMember(RecType,Field^.Name); if Member=nil then RaiseIdentifierNotFound(20180429104703,Field^.Name,Field^.NameExp); - if not (Member is TPasVariable) then - RaiseMsg(20180429121933,nVariableIdentifierExpected,sVariableIdentifierExpected, + if Member.ClassType<>TPasVariable then + RaiseMsg(20180429121933,nIdentifierXIsNotAnInstanceField,sIdentifierXIsNotAnInstanceField, [],Field^.ValueExp); + if TPasVariable(Member).VarModifiers*[vmClass,vmStatic]<>[] then + RaiseMsg(20190105221450,nIdentifierXIsNotAnInstanceField,sIdentifierXIsNotAnInstanceField, + ['record assignment'],Field^.ValueExp); CreateReference(Member,Field^.NameExp,rraAssign); // check duplicates for j:=0 to i-1 do @@ -8957,7 +8985,9 @@ begin for i:=0 to RecType.Members.Count-1 do begin Member:=TPasElement(RecType.Members[i]); - if not (Member is TPasVariable) then continue; + if Member.ClassType<>TPasVariable then continue; + if TPasVariable(Member).VarModifiers*[vmClass,vmStatic]<>[] then + continue; j:=length(El.Fields)-1; while (j>=0) and not SameText(Member.Name,El.Fields[j].Name) do dec(j); @@ -9232,7 +9262,7 @@ var i: Integer; DeclEl: TPasElement; Proc: TPasProcedure; - aClassType: TPasClassType; + aClassOrRec: TPasMembersType; begin if IsElementSkipped(El) then exit; if El is TPasDeclarations then @@ -9250,13 +9280,15 @@ begin end; end; end - else if El.ClassType=TPasClassType then + else if El is TPasMembersType then begin - aClassType:=TPasClassType(El); - if aClassType.ObjKind in [okInterface,okDispInterface] then exit; - for i:=0 to aClassType.Members.Count-1 do + aClassOrRec:=TPasMembersType(El); + if (aClassOrRec is TPasClassType) + and (TPasClassType(aClassOrRec).ObjKind in [okInterface,okDispInterface]) + then exit; + for i:=0 to aClassOrRec.Members.Count-1 do begin - DeclEl:=TPasElement(aClassType.Members[i]); + DeclEl:=TPasElement(aClassOrRec.Members[i]); if DeclEl is TPasProcedure then begin Proc:=TPasProcedure(DeclEl); @@ -9660,7 +9692,7 @@ begin RaiseNotYetImplemented(20161013170956,El); ProcScope.VisibilityContext:=ClassOrRecType; - ProcScope.ClassScope:=NoNil(ClassOrRecType.CustomData) as TPasClassOrRecordScope; + ProcScope.ClassOrRecordScope:=NoNil(ClassOrRecType.CustomData) as TPasClassOrRecordScope; end;// HasDot=true end; @@ -15084,7 +15116,7 @@ begin and (TPasWithExprScope(StartScope).Scope is TPasClassOrRecordScope) then ClassRecScope:=TPasClassOrRecordScope(TPasWithExprScope(StartScope).Scope) else if (StartScope is TPasProcedureScope) then - ClassRecScope:=TPasProcedureScope(StartScope).ClassScope + ClassRecScope:=TPasProcedureScope(StartScope).ClassOrRecordScope else RaiseInternalError(20170131150855,GetObjName(StartScope)); TypeEl:=ClassRecScope.Element as TPasType; @@ -18271,7 +18303,7 @@ begin exit(NotLocked(IdentEl)); end; -function TPasResolver.ResolvedElIsClassInstance( +function TPasResolver.ResolvedElIsClassOrRecordInstance( const ResolvedEl: TPasResolverResult): boolean; var TypeEl: TPasType; @@ -18280,8 +18312,13 @@ begin if ResolvedEl.BaseType<>btContext then exit; TypeEl:=ResolvedEl.LoTypeEl; if TypeEl=nil then exit; - if TypeEl.ClassType<>TPasClassType then exit; - if TPasClassType(TypeEl).ObjKind<>okClass then exit; + if TypeEl.ClassType=TPasClassType then + begin + if TPasClassType(TypeEl).ObjKind<>okClass then exit; + end + else if TypeEl.ClassType=TPasRecordType then + else + exit; if (ResolvedEl.IdentEl is TPasVariable) or (ResolvedEl.IdentEl.ClassType=TPasArgument) or (ResolvedEl.IdentEl.ClassType=TPasResultElement) then diff --git a/packages/fcl-passrc/src/pparser.pp b/packages/fcl-passrc/src/pparser.pp index c91e4a24d2..e4f57441ea 100644 --- a/packages/fcl-passrc/src/pparser.pp +++ b/packages/fcl-passrc/src/pparser.pp @@ -3557,7 +3557,9 @@ begin RecordEl.SetGenericTemplates(List); NextToken; ParseRecordFieldList(RecordEl,tkend, - msAdvancedRecords in Scanner.CurrentModeSwitches); + (msAdvancedRecords in Scanner.CurrentModeSwitches) + and not (Declarations is TProcedureBody) + and (RecordEl.Name<>'')); CheckHint(RecordEl,True); Engine.FinishScope(stTypeDef,RecordEl); end; @@ -6344,7 +6346,15 @@ begin tkClass: begin if Not AllowMethods then - ParseExc(nErrRecordMethodsNotAllowed,SErrRecordMethodsNotAllowed); + begin + NextToken; + case CurToken of + tkConst: ParseExc(nErrRecordConstantsNotAllowed,SErrRecordConstantsNotAllowed); + tkvar: ParseExc(nErrRecordVariablesNotAllowed,SErrRecordVariablesNotAllowed); + else + ParseExc(nErrRecordMethodsNotAllowed,SErrRecordMethodsNotAllowed); + end; + end; if isClass then ParseExc(nParserTypeSyntaxError,SParserTypeSyntaxError); isClass:=True; @@ -6437,7 +6447,8 @@ begin try Result.PackMode:=PackMode; NextToken; - ParseRecordFieldList(Result,tkEnd,msAdvancedRecords in Scanner.CurrentModeSwitches); + ParseRecordFieldList(Result,tkEnd, + (msAdvancedRecords in Scanner.CurrentModeSwitches) and not (Parent is TProcedureBody)); Engine.FinishScope(stTypeDef,Result); ok:=true; finally diff --git a/packages/fcl-passrc/tests/tcresolver.pas b/packages/fcl-passrc/tests/tcresolver.pas index cc59b09089..03fdde97cd 100644 --- a/packages/fcl-passrc/tests/tcresolver.pas +++ b/packages/fcl-passrc/tests/tcresolver.pas @@ -490,7 +490,10 @@ type Procedure TestAdvRecord; Procedure TestAdvRecord_Private; Procedure TestAdvRecord_StrictPrivate; + Procedure TestAdvRecord_MethodImplMissingFail; Procedure TestAdvRecord_VarConst; + Procedure TestAdvRecord_RecVal_ConstFail; + Procedure TestAdvRecord_RecVal_ClassVarFail; Procedure TestAdvRecord_LocalForwardType; Procedure TestAdvRecord_Constructor_NewInstance; Procedure TestAdvRecord_ConstructorNoParamsFail; @@ -504,6 +507,8 @@ type Procedure TestAdvRecord_RecordAsFuncResult; Procedure TestAdvRecord_InheritedFail; Procedure TestAdvRecord_ForInEnumerator; + Procedure TestAdvRecord_InFunctionFail; + Procedure TestAdvRecord_SubClass; // class Procedure TestClass; @@ -7874,6 +7879,20 @@ begin CheckResolverException('Can''t access strict private member A',nCantAccessPrivateMember); end; +procedure TTestResolver.TestAdvRecord_MethodImplMissingFail; +begin + StartProgram(false); + Add([ + '{$modeswitch advancedrecords}', + 'type', + ' TRec = record', + ' procedure SetSize(Value: word);', + ' end;', + 'begin', + '']); + CheckResolverException(sForwardProcNotResolved,nForwardProcNotResolved); +end; + procedure TTestResolver.TestAdvRecord_VarConst; begin StartProgram(false); @@ -7913,6 +7932,42 @@ begin ParseProgram; end; +procedure TTestResolver.TestAdvRecord_RecVal_ConstFail; +begin + StartProgram(false); + Add([ + '{$modeswitch advancedrecords}', + 'type', + ' TRec = record', + ' V1: word;', + ' const', + ' C1 = 3;', + ' end;', + 'var', + ' r: TRec = (V1:2; C1: 4);', + 'begin', + '']); + CheckResolverException(sIdentifierXIsNotAnInstanceField,nIdentifierXIsNotAnInstanceField); +end; + +procedure TTestResolver.TestAdvRecord_RecVal_ClassVarFail; +begin + StartProgram(false); + Add([ + '{$modeswitch advancedrecords}', + 'type', + ' TRec = record', + ' V1: word;', + ' class var', + ' C1: word;', + ' end;', + 'var', + ' r: TRec = (V1:2; C1: 4);', + 'begin', + '']); + CheckResolverException(sIdentifierXIsNotAnInstanceField,nIdentifierXIsNotAnInstanceField); +end; + procedure TTestResolver.TestAdvRecord_LocalForwardType; begin StartProgram(false); @@ -7947,7 +8002,7 @@ begin 'type', ' TRec = record', ' constructor Create(w: word);', - ' class function DoSome: TRec;', + ' class function DoSome: TRec; static;', ' end;', 'constructor TRec.Create(w: word);', 'begin', @@ -8027,14 +8082,17 @@ begin '{$modeswitch advancedrecords}', 'type', ' TRec = record', - ' class procedure {#a}Create;', - ' class constructor Create;', + ' class var w: word;', + ' class procedure {#a}Create; static;', + ' class constructor Create; static;', ' end;', 'class constructor TRec.Create;', 'begin', + ' w:=w+1;', 'end;', 'class procedure TRec.Create;', 'begin', + ' w:=w+1;', 'end;', 'begin', ' TRec.{@a}Create;', @@ -8231,8 +8289,8 @@ begin 'type', ' {#A}TRec = record', ' {#A_i}i: longint;', - ' class function {#A_CreateA}Create: TRec;', - ' class function {#A_CreateB}Create(i: longint): TRec;', + ' class function {#A_CreateA}Create: TRec; static;', + ' class function {#A_CreateB}Create(i: longint): TRec; static;', ' end;', 'function {#F}F: TRec;', 'begin', @@ -8313,6 +8371,53 @@ begin ParseProgram; end; +procedure TTestResolver.TestAdvRecord_InFunctionFail; +begin + StartProgram(false); + Add([ + '{$modeswitch advancedrecords}', + 'procedure DoIt;', + 'type', + ' TBird = record', + ' class var i: word;', + ' end;', + 'var', + ' b: TBird;', + 'begin', + 'end;', + 'begin']); + CheckParserException(sErrRecordVariablesNotAllowed,nErrRecordVariablesNotAllowed); +end; + +procedure TTestResolver.TestAdvRecord_SubClass; +begin + StartProgram(false); + Add([ + '{$modeswitch AdvancedRecords}', + 'type', + ' TObject = class end;', + ' TPoint = record', + ' type', + ' TBird = class', + ' procedure DoIt;', + ' class procedure Glob;', + ' end;', + ' procedure DoIt(b: TBird);', + ' end;', + 'procedure TPoint.TBird.DoIt;', + 'begin', + 'end;', + 'class procedure TPoint.TBird.Glob;', + 'begin', + 'end;', + 'procedure TPoint.DoIt(b: TBird);', + 'begin', + 'end;', + 'begin', + '']); + ParseProgram; +end; + procedure TTestResolver.TestClass; begin StartProgram(false);