Skip to content

Commit 1b942de

Browse files
committed
Fix implicit generic specialization of open array types
Previously, our implicit specialization required **exact** mappings of types to top-level type parameters, but open arrays can go one level deep with the type parameter as the element type.
1 parent 6de9761 commit 1b942de

File tree

4 files changed

+36
-5
lines changed

4 files changed

+36
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
- False positives on enumerator method `MoveNext` in `UnusedRoutine`.
3636
- False positives on enumerator property `Current` in `UnusedProperty`.
3737
- Name resolution failures in legacy initialization sections referencing the implementation section.
38+
- Name resolution failures on invocations of methods with generic open array parameters.
3839
- Incorrect file position calculation for multiline string tokens.
3940

4041
## [1.15.0] - 2025-04-03

delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/InvocationCandidate.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.sonar.plugins.communitydelphi.api.type.IntrinsicType;
3333
import org.sonar.plugins.communitydelphi.api.type.Parameter;
3434
import org.sonar.plugins.communitydelphi.api.type.Type;
35+
import org.sonar.plugins.communitydelphi.api.type.Type.CollectionType;
3536

3637
/**
3738
* Stores information about an invocation candidate, used for overload resolution. Based directly
@@ -197,11 +198,17 @@ public static InvocationCandidate implicitSpecialization(
197198
Map<Type, Type> argumentsByTypeParameters = new HashMap<>();
198199
for (int i = 0; i < argumentTypes.size(); ++i) {
199200
Type parameterType = routineDeclaration.getParameter(i).getType();
201+
Type argumentType = argumentTypes.get(i);
202+
203+
if (parameterType.isOpenArray() && argumentType.isArray()) {
204+
parameterType = ((CollectionType) parameterType).elementType();
205+
argumentType = ((CollectionType) argumentType).elementType();
206+
}
207+
200208
if (!parameterType.isTypeParameter()) {
201209
continue;
202210
}
203211

204-
Type argumentType = argumentTypes.get(i);
205212
Type existingMapping = argumentsByTypeParameters.get(parameterType);
206213

207214
if (existingMapping != null && !existingMapping.is(argumentType)) {

delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,8 @@ void testGenericParameterizedMethods() {
10461046
@Test
10471047
void testGenericImplicitSpecializations() {
10481048
execute("generics/ImplicitSpecialization.pas");
1049-
verifyUsages(14, 14, reference(19, 20), reference(26, 2), reference(27, 2), reference(28, 2));
1049+
verifyUsages(15, 14, reference(21, 20), reference(37, 2), reference(38, 2), reference(39, 2));
1050+
verifyUsages(16, 14, reference(26, 20), reference(48, 2), reference(49, 2), reference(50, 2));
10501051
}
10511052

10521053
@Test

delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/generics/ImplicitSpecialization.pas

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ interface
44

55
type
66
TConsumable = class
7-
constructor Create; override;
7+
constructor Create;
88
procedure GetEaten;
99
class function Pizza: TConsumable;
1010
end;
1111

1212
TConsumer = class
13-
procedure Test(Consumable: TConsumable);
14-
procedure Consume<T: TConsumable>(Tasty: T);
13+
procedure Test(Consumable: TConsumable); overload;
14+
procedure Test(Consumables: array of TConsumable); overload;
15+
procedure Consume<T: TConsumable>(Consumable: T); overload;
16+
procedure Consume<T: TConsumable>(Consumables: array of T); overload;
1517
end;
1618

1719
implementation
@@ -21,11 +23,31 @@ procedure TConsumer.Consume<T>(Consumable: T);
2123
Consumable.GetEaten;
2224
end;
2325

26+
procedure TConsumer.Consume<T>(Consumables: array of T);
27+
var
28+
Consumable: T;
29+
begin
30+
for Consumable in Consumables do begin
31+
Consume(Consumable);
32+
end;
33+
end;
34+
2435
procedure TConsumer.Test(Consumable: TConsumable);
2536
begin
2637
Consume(Consumable);
2738
Consume(TConsumable.Pizza);
2839
Consume(TConsumable.Create);
2940
end;
3041

42+
procedure TConsumer.Test(Consumables: array of TConsumable);
43+
var
44+
DynamicConsumables: TArray<TConsumable>;
45+
begin
46+
DynamicConsumables := [TConsumable.Create];
47+
48+
Consume(Consumables);
49+
Consume(DynamicConsumables);
50+
Consume([TConsumable.Pizza]);
51+
end;
52+
3153
end.

0 commit comments

Comments
 (0)