diff --git a/CHANGELOG.md b/CHANGELOG.md index 904c0b4bc..201e452f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Various intrinsic routines had incorrect signatures around dynamic and open arrays. - False positives around platform-dependent binary expressions in `PlatformDependentTruncation`. +- Incorrect type resolution around array property accesses. ## [1.12.0] - 2024-12-02 diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java index d3d4dc29a..11922e7b2 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java @@ -782,26 +782,31 @@ private void handleParenthesizedExpression(ParenthesizedExpressionNode parenthes } } - private boolean handleDefaultArrayProperties(ArrayAccessorNode accessor) { - if (!declarations.isEmpty() - && declarations.stream().allMatch(NameResolver::isDefaultArrayProperty)) { - Type type = currentType; - if (type.isClassReference()) { - type = ((ClassReferenceType) type).classType(); - } else if (type.isProcedural()) { - type = ((ProceduralType) type).returnType(); - } + private void addExplicitDefaultArrayPropertyDeclarations() { + if (!declarations.stream().allMatch(NameResolver::isDefaultArrayProperty)) { + return; + } - if (type.isStruct()) { - StructType structType = (StructType) TypeUtils.findBaseType(type); - NameDeclaration declaration = Iterables.getLast(declarations); - ((StructTypeImpl) structType) - .findDefaultArrayProperties().stream() - .filter(property -> property.getName().equalsIgnoreCase(declaration.getName())) - .forEach(declarations::add); - } + Type type = currentType; + if (type.isClassReference()) { + type = ((ClassReferenceType) type).classType(); + } else if (type.isProcedural()) { + type = ((ProceduralType) type).returnType(); + } - // An explicit array property access can be handled by argument disambiguation. + if (type.isStruct()) { + StructType structType = (StructType) TypeUtils.findBaseType(type); + NameDeclaration declaration = Iterables.getLast(declarations); + ((StructTypeImpl) structType) + .findDefaultArrayProperties().stream() + .filter(property -> property.getName().equalsIgnoreCase(declaration.getName())) + .forEach(declarations::add); + } + } + + private boolean handleDefaultArrayProperties(ArrayAccessorNode accessor) { + if (!declarations.isEmpty() && isArrayProperty(Iterables.getLast(declarations))) { + addExplicitDefaultArrayPropertyDeclarations(); return false; } 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 c483e0e63..18e803567 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 @@ -870,6 +870,20 @@ void testHiddenDefaultProperties() { verifyUsages(11, 14, reference(27, 25)); } + @Test + void testArrayProperties() { + execute("properties/ArrayProperties.pas"); + verifyUsages(9, 13, reference(29, 23)); + verifyUsages(16, 10, reference(28, 2)); + } + + @Test + void testClassArrayProperties() { + execute("properties/ClassArrayProperties.pas"); + verifyUsages(9, 13, reference(29, 24)); + verifyUsages(16, 10, reference(28, 2)); + } + @Test void testDefaultArrayProperties() { execute("properties/DefaultArrayProperties.pas"); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/ArrayProperties.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/ArrayProperties.pas new file mode 100644 index 000000000..edc15141a --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/ArrayProperties.pas @@ -0,0 +1,32 @@ +unit ArrayProperties; + +interface + +implementation + +type + TStringHelper = record helper for string + function IsEmpty: Boolean; + end; + + TFoo = class + property Baz[I: Integer]: string; + end; + +procedure Consume(S: string); +begin + // do nothing +end; + +procedure Consume(C: Char); +begin + // do nothing +end; + +function Test(Foo: TFoo): Boolean; +begin + Consume(Foo.Baz[0]); + Result := Foo.Baz[0].IsEmpty; +end; + +end. \ No newline at end of file diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/ClassArrayProperties.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/ClassArrayProperties.pas new file mode 100644 index 000000000..25d1fcbbd --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/ClassArrayProperties.pas @@ -0,0 +1,32 @@ +unit ClassArrayProperties; + +interface + +implementation + +type + TStringHelper = record helper for string + function IsEmpty: Boolean; + end; + + TFoo = class + class property Baz[I: Integer]: string; + end; + +procedure Consume(S: string); +begin + // do nothing +end; + +procedure Consume(C: Char); +begin + // do nothing +end; + +function Test(Foo: TFoo): Boolean; +begin + Consume(TFoo.Baz[0]); + Result := TFoo.Baz[0].IsEmpty; +end; + +end. \ No newline at end of file