diff --git a/CHANGELOG.md b/CHANGELOG.md index 747a00569..dcdc51d5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Incorrect return types for `Length`, `High`, and `Low` on open/dynamic arrays depending on the compiler version and toolchain. +- Name resolution failures on explicit references to default array properties with overloads on + ancestor types. ## [1.11.0] - 2024-11-04 diff --git a/delphi-checks/src/test/java/au/com/integradev/delphi/checks/ExplicitDefaultPropertyReferenceCheckTest.java b/delphi-checks/src/test/java/au/com/integradev/delphi/checks/ExplicitDefaultPropertyReferenceCheckTest.java index 353a776c7..f3606a3b0 100644 --- a/delphi-checks/src/test/java/au/com/integradev/delphi/checks/ExplicitDefaultPropertyReferenceCheckTest.java +++ b/delphi-checks/src/test/java/au/com/integradev/delphi/checks/ExplicitDefaultPropertyReferenceCheckTest.java @@ -83,4 +83,26 @@ void testExplicitDefaultPropertyAccessOnSelfShouldNotAddIssue() { .appendImpl("end;")) .verifyNoIssues(); } + + @Test + void testExplicitDefaultPropertyAccessOnOverloadedParentPropertyShouldAddIssue() { + CheckVerifier.newVerifier() + .withCheck(new ExplicitDefaultPropertyReferenceCheck()) + .onFile( + new DelphiTestUnitBuilder() + .appendDecl("type") + .appendDecl(" TFoo = class") + .appendDecl(" property Baz[Index: Integer]: TObject; default;") + .appendDecl(" end;") + .appendDecl(" TBar = class(TFoo)") + .appendDecl(" property Baz[Name: string]: TObject; default;") + .appendDecl(" end;") + .appendImpl("procedure Test(Bar: TBar);") + .appendImpl("var") + .appendImpl(" Obj: TObject;") + .appendImpl("begin") + .appendImpl(" Obj := Bar.Baz[0]; // Noncompliant") + .appendImpl("end;")) + .verifyIssues(); + } } 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 7f8d979f1..d3d4dc29a 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 @@ -783,7 +783,24 @@ private void handleParenthesizedExpression(ParenthesizedExpressionNode parenthes } private boolean handleDefaultArrayProperties(ArrayAccessorNode accessor) { - if (!declarations.isEmpty() && isArrayProperty(Iterables.getLast(declarations))) { + 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(); + } + + 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); + } + // An explicit array property access can be handled by argument disambiguation. return false; } @@ -830,6 +847,11 @@ private static boolean isArrayProperty(NameDeclaration declaration) { && ((PropertyNameDeclaration) declaration).isArrayProperty(); } + private static boolean isDefaultArrayProperty(NameDeclaration declaration) { + return isArrayProperty(declaration) + && ((PropertyNameDeclaration) declaration).isDefaultProperty(); + } + void disambiguateImplicitEmptyArgumentList() { if (declarations.stream().noneMatch(RoutineNameDeclaration.class::isInstance)) { return; 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 7fd8792fa..c483e0e63 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,22 @@ void testHiddenDefaultProperties() { verifyUsages(11, 14, reference(27, 25)); } + @Test + void testDefaultArrayProperties() { + execute("properties/DefaultArrayProperties.pas"); + verifyUsages(12, 14, reference(44, 15), reference(48, 19)); + verifyUsages(18, 14, reference(45, 15), reference(49, 19)); + verifyUsages(19, 14, reference(46, 15), reference(50, 19)); + } + + @Test + void testDefaultClassArrayProperties() { + execute("properties/DefaultClassArrayProperties.pas"); + verifyUsages(12, 20, reference(46, 16), reference(50, 24)); + verifyUsages(18, 20, reference(47, 16), reference(51, 24)); + verifyUsages(19, 20, reference(48, 16), reference(52, 24)); + } + @Test void testSimpleOverloads() { execute("overloads/Simple.pas"); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/DefaultArrayProperties.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/DefaultArrayProperties.pas new file mode 100644 index 000000000..cad3f8ffe --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/DefaultArrayProperties.pas @@ -0,0 +1,53 @@ +unit DefaultArrayProperties; + +interface + +implementation + +type + TEnum = (Flarp); + + TFoo = class + function GetBaz(I: Integer): Integer; + property Baz[I: Integer]: Integer read GetBaz; default; + end; + + TBar = class(TFoo) + function GetBaz(S: string): string; overload; + function GetBaz(E: TEnum): TEnum; overload; + property Baz[S: string]: string read GetBaz; default; + property Baz[E: TEnum]: TEnum read GetBaz; default; + end; + +function TFoo.GetBaz(I: Integer): Integer; +begin + Result := I; +end; + +function TBar.GetBaz(S: string): string; +begin + Result := S; +end; + +function TBar.GetBaz(E: TEnum): TEnum; +begin + Result := E; +end; + +function BarFunc: TBar; +begin + Result := TBar.Create; +end; + +procedure Test(Bar: TBar); +begin + var A := Bar.Baz[123]; + var B := Bar.Baz['123']; + var C := Bar.Baz[Flarp]; + + var D := BarFunc.Baz[123]; + var E := BarFunc.Baz['123']; + var F := BarFunc.Baz[Flarp]; +end; + +end. \ No newline at end of file diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/DefaultClassArrayProperties.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/DefaultClassArrayProperties.pas new file mode 100644 index 000000000..6486da79f --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/DefaultClassArrayProperties.pas @@ -0,0 +1,56 @@ +unit DefaultClassArrayProperties; + +interface + +implementation + +type + TEnum = (Flarp); + + TFoo = class + class function GetBaz(I: Integer): Integer; static; + class property Baz[I: Integer]: Integer read GetBaz; default; + end; + + TBar = class(TFoo) + class function GetBaz(S: string): string; overload; static; + class function GetBaz(E: TEnum): TEnum; overload; static; + class property Baz[S: string]: string read GetBaz; default; + class property Baz[E: TEnum]: TEnum read GetBaz; default; + end; + + TBarClass = class of TBar; + +class function TFoo.GetBaz(I: Integer): Integer; +begin + Result := I; +end; + +class function TBar.GetBaz(S: string): string; +begin + Result := S; +end; + +class function TBar.GetBaz(E: TEnum): TEnum; +begin + Result := E; +end; + +function BarClassFunc: TBarClass; +begin + Result := TBar; +end; + +procedure Test; +begin + var A := TBar.Baz[123]; + var B := TBar.Baz['123']; + var C := TBar.Baz[Flarp]; + + var D := BarClassFunc.Baz[123]; + var E := BarClassFunc.Baz['123']; + var F := BarClassFunc.Baz[Flarp]; +end; + + +end. \ No newline at end of file