Skip to content

Commit 9a3cf50

Browse files
cirrasfourls
authored andcommitted
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 2fa0766 commit 9a3cf50

File tree

4 files changed

+67
-5
lines changed

4 files changed

+67
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3838
- False positives on enums that are never referenced by name (but have used values) in `UnusedType`.
3939
- Name resolution failures in legacy initialization sections referencing the implementation section.
4040
- Name resolution failures whena accessing ancestors of enclosing types from nested type methods.
41+
- Name resolution failures on invocations of methods with generic open array parameters.
4142
- Incorrect file position calculation for multiline string tokens.
4243
- Analysis errors around `type of` type declarations.
4344

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
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.ArrayConstructorType;
36+
import org.sonar.plugins.communitydelphi.api.type.Type.CollectionType;
3537

3638
/**
3739
* Stores information about an invocation candidate, used for overload resolution. Based directly
@@ -197,11 +199,20 @@ public static InvocationCandidate implicitSpecialization(
197199
Map<Type, Type> argumentsByTypeParameters = new HashMap<>();
198200
for (int i = 0; i < argumentTypes.size(); ++i) {
199201
Type parameterType = routineDeclaration.getParameter(i).getType();
202+
Type argumentType = argumentTypes.get(i);
203+
204+
if (parameterType.isOpenArray()) {
205+
Type argumentElementType = getArrayElementType(argumentType);
206+
if (argumentElementType != null) {
207+
parameterType = ((CollectionType) parameterType).elementType();
208+
argumentType = argumentElementType;
209+
}
210+
}
211+
200212
if (!parameterType.isTypeParameter()) {
201213
continue;
202214
}
203215

204-
Type argumentType = argumentTypes.get(i);
205216
Type existingMapping = argumentsByTypeParameters.get(parameterType);
206217

207218
if (existingMapping != null && !existingMapping.is(argumentType)) {
@@ -230,6 +241,33 @@ public static InvocationCandidate implicitSpecialization(
230241
return new InvocationCandidate(specialized);
231242
}
232243

244+
private static Type getArrayElementType(Type type) {
245+
if (type.isArray()) {
246+
return ((CollectionType) type).elementType();
247+
} else if (type.isArrayConstructor()) {
248+
return getHomogeneousElementType((ArrayConstructorType) type);
249+
} else {
250+
return null;
251+
}
252+
}
253+
254+
private static Type getHomogeneousElementType(ArrayConstructorType type) {
255+
List<Type> elementTypes = type.elementTypes();
256+
if (elementTypes.isEmpty()) {
257+
return null;
258+
}
259+
260+
Type result = elementTypes.get(0);
261+
for (Type elementType : elementTypes) {
262+
if (!elementType.is(result)) {
263+
result = null;
264+
break;
265+
}
266+
}
267+
268+
return result;
269+
}
270+
233271
@Override
234272
public boolean equals(Object o) {
235273
if (this == o) {

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
@@ -1053,7 +1053,8 @@ void testGenericParameterizedMethods() {
10531053
@Test
10541054
void testGenericImplicitSpecializations() {
10551055
execute("generics/ImplicitSpecialization.pas");
1056-
verifyUsages(14, 14, reference(19, 20), reference(26, 2), reference(27, 2), reference(28, 2));
1056+
verifyUsages(15, 14, reference(21, 20), reference(37, 2), reference(38, 2), reference(39, 2));
1057+
verifyUsages(16, 14, reference(26, 20), reference(48, 2), reference(49, 2), reference(50, 2));
10571058
}
10581059

10591060
@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)