diff --git a/CHANGELOG.md b/CHANGELOG.md index 425e4fcf6..01d5dc357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Parsing errors on attributes preceding visibility sections. - Parsing errors on attributes preceding `var` sections in type bodies. +- Parsing errors on anonymous enum and subrange types in record variant tags. +- Parsing errors on anonymous array, set and procedural types in generic arguments. - Errors when parsing coverage reports if duplicate file names were present in the project. ## [1.21.0] - 2026-09-04 diff --git a/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g b/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g index 5053f75ca..a34ba1af4 100644 --- a/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g +++ b/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g @@ -722,8 +722,12 @@ recordType : RECORD^ visibilitySection* re ; recordVariantSection : CASE^ recordVariantTag OF recordVariant+ ; -recordVariantTag : (nameDeclaration ':')? typeReference - -> ^(TkRecordVariantTag nameDeclaration? typeReference) +recordVariantTag : (nameDeclaration ':')? recordVariantTagType + -> ^(TkRecordVariantTag nameDeclaration? recordVariantTagType) + ; +recordVariantTagType : enumType + | subRangeType + | typeReference ; recordVariant : expressionList ':' '(' fieldDecl* recordVariantSection? ')' ';'? -> ^(TkRecordVariantItem expressionList fieldDecl* recordVariantSection? ';'?) @@ -787,8 +791,13 @@ genericConstraint : typeReference -> ^(TkTypeConstraint^ | UNMANAGED^ ; -genericArguments : '<' typeReferenceOrStringOrFile (',' typeReferenceOrStringOrFile)* '>' - -> ^(TkGenericArguments '<' typeReferenceOrStringOrFile (',' typeReferenceOrStringOrFile)* '>') +genericArguments : '<' genericArgument (',' genericArgument)* '>' + -> ^(TkGenericArguments '<' genericArgument (',' genericArgument)* '>') + ; +genericArgument : arrayType + | setType + | procedureType + | typeReferenceOrStringOrFile ; routineNameGenericArguments : '<' typeReferenceOrStringOrFile (commaOrSemicolon typeReferenceOrStringOrFile)* '>' -> ^(TkGenericArguments '<' typeReferenceOrStringOrFile (commaOrSemicolon typeReferenceOrStringOrFile)* '>') diff --git a/delphi-frontend/src/test/java/au/com/integradev/delphi/antlr/GrammarTest.java b/delphi-frontend/src/test/java/au/com/integradev/delphi/antlr/GrammarTest.java index e4bcb2106..803215357 100644 --- a/delphi-frontend/src/test/java/au/com/integradev/delphi/antlr/GrammarTest.java +++ b/delphi-frontend/src/test/java/au/com/integradev/delphi/antlr/GrammarTest.java @@ -362,6 +362,11 @@ void testSemicolonSeparatedGenericArguments() { assertParsed("SemicolonSeparatedGenericArguments.pas"); } + @Test + void testAnonymousTypeGenericArguments() { + assertParsed("AnonymousTypeGenericArguments.pas"); + } + @Test void testGreaterThanEqualAmbiguity() { assertParsed("GreaterThanEqualAmbiguity.pas"); diff --git a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java index d7d06946b..e73d49998 100644 --- a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java +++ b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java @@ -137,6 +137,21 @@ void testRecordVariants() { verifyUsages(24, 10, reference(35, 2)); } + @Test + void testRecordVariantAnonymousTagType() { + execute("RecordVariantAnonymousTagType.pas"); + verifyUsages(7, 9, reference(18, 6)); + verifyUsages(7, 16, reference(8, 6)); + verifyUsages(7, 23, reference(9, 6), reference(18, 14)); + } + + @Test + void testAnonymousArrayGenericArgument() { + execute("AnonymousArrayGenericArgument.pas"); + verifyUsages(6, 2, reference(12, 23)); + verifyUsages(7, 2, reference(12, 34)); + } + @Test void testInheritedInvocations() { execute("InheritedInvocations.pas"); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/AnonymousTypeGenericArguments.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/AnonymousTypeGenericArguments.pas new file mode 100644 index 000000000..15273bb57 --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/AnonymousTypeGenericArguments.pas @@ -0,0 +1,19 @@ +unit AnonymousTypeGenericArguments; + +interface + +type + TFoo = class + StaticArrays: TArray; + DynamicArrays: TArray; + Sets: TArray; + Procedures: TArray; + Functions: TArray; + Methods: TArray; + References: TArray; + Mixed: TDictionary; + end; + +implementation + +end. \ No newline at end of file diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/VariantRecord.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/VariantRecord.pas index f04b97ad8..805062ca9 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/VariantRecord.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/VariantRecord.pas @@ -18,6 +18,18 @@ VariantRecordWithMethods = record 1: (High, Low: Longint); end; + VariantRecordWithAnonymousEnumTag = record + case Tag: (vtWord, vtLongint) of + vtWord: (Data: array[0..3] of Word); + vtLongint: (High, Low: Longint); + end; + + VariantRecordWithSubRangeTag = record + case Tag: 0..1 of + 0: (Data: array[0..3] of Word); + 1: (High, Low: Longint); + end; + implementation diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/AnonymousArrayGenericArgument.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/AnonymousArrayGenericArgument.pas new file mode 100644 index 000000000..f05606a43 --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/AnonymousArrayGenericArgument.pas @@ -0,0 +1,17 @@ +unit AnonymousArrayGenericArgument; + +interface + +type + TIndex = (iFirst, iSecond); + TElement = Double; + TBox = class + end; + + TFoo = class + Values: TBox; + end; + +implementation + +end. \ No newline at end of file diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/RecordVariantAnonymousTagType.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/RecordVariantAnonymousTagType.pas new file mode 100644 index 000000000..ad617c570 --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/RecordVariantAnonymousTagType.pas @@ -0,0 +1,21 @@ +unit RecordVariantAnonymousTagType; + +interface + +type + TRecord = record + case Kind: (rkInt, rkDbl) of + rkInt: (Int: Integer); + rkDbl: (Dbl: Double); + end; + +implementation + +procedure Test; +var + Rec: TRecord; +begin + Rec.Kind := rkDbl; +end; + +end. \ No newline at end of file