Skip to content

Commit 2d57d3a

Browse files
committed
Implement array type variance lowering
Kotlin permits introducing a `? extends ...` wildcard against an Array even though the class is final, so long as its argument itself can be extended (i.e. isn't final or is another array type satisfying this condition). Contravariant arrays get lowered to Object[], and are subject to automatic `extends` wildcard introduction, unless their element type was already Any.
1 parent 483281e commit 2d57d3a

File tree

5 files changed

+265
-6
lines changed

5 files changed

+265
-6
lines changed

java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -706,14 +706,31 @@ open class KotlinUsesExtractor(
706706
)
707707

708708
(s.isBoxedArray && s.arguments.isNotEmpty()) || s.isPrimitiveArray() -> {
709-
var dimensions = 1
710-
var isPrimitiveArray = s.isPrimitiveArray()
711-
val componentType = s.getArrayElementType(pluginContext.irBuiltIns)
712-
var elementType = componentType
709+
710+
fun replaceComponentTypeWithAny(t: IrSimpleType, dimensions: Int): IrSimpleType =
711+
if (dimensions == 0)
712+
pluginContext.irBuiltIns.anyType as IrSimpleType
713+
else
714+
t.toBuilder().also { it.arguments = (it.arguments[0] as IrTypeProjection)
715+
.let { oldArg ->
716+
listOf(makeTypeProjection(replaceComponentTypeWithAny(oldArg.type as IrSimpleType, dimensions - 1), oldArg.variance))
717+
}
718+
}.buildSimpleType()
719+
720+
var componentType = s.getArrayElementType(pluginContext.irBuiltIns)
721+
var isPrimitiveArray = false
722+
var dimensions = 0
723+
var elementType: IrType = s
713724
while (elementType.isBoxedArray || elementType.isPrimitiveArray()) {
714725
dimensions++
715-
if(elementType.isPrimitiveArray())
726+
if (elementType.isPrimitiveArray())
716727
isPrimitiveArray = true
728+
if (((elementType as IrSimpleType).arguments.singleOrNull() as? IrTypeProjection)?.variance == Variance.IN_VARIANCE) {
729+
// Because Java's arrays are covariant, Kotlin will render Array<in X> as Object[], Array<Array<in X>> as Object[][] etc.
730+
componentType = replaceComponentTypeWithAny(s, dimensions - 1)
731+
elementType = pluginContext.irBuiltIns.anyType as IrSimpleType
732+
break
733+
}
717734
elementType = elementType.getArrayElementType(pluginContext.irBuiltIns)
718735
}
719736

@@ -926,13 +943,33 @@ open class KotlinUsesExtractor(
926943
private val jvmWildcardAnnotation = FqName("kotlin.jvm.JvmWildcard")
927944
private val jvmWildcardSuppressionAnnotaton = FqName("kotlin.jvm.JvmSuppressWildcards")
928945

946+
private fun arrayExtendsAdditionAllowed(t: IrSimpleType): Boolean =
947+
// Note the array special case includes Array<*>, which does permit adding `? extends ...` (making `? extends Object[]` in that case)
948+
// Surprisingly Array<in X> does permit this as well, though the contravariant array lowers to Object[] so this ends up `? extends Object[]` as well.
949+
t.arguments[0].let {
950+
when (it) {
951+
is IrTypeProjection -> when (it.variance) {
952+
Variance.INVARIANT -> false
953+
Variance.IN_VARIANCE -> !(it.type.isAny() || it.type.isNullableAny())
954+
Variance.OUT_VARIANCE -> extendsAdditionAllowed(it.type)
955+
}
956+
else -> true
957+
}
958+
}
959+
960+
private fun extendsAdditionAllowed(t: IrType) =
961+
if (t.isBoxedArray)
962+
arrayExtendsAdditionAllowed(t as IrSimpleType)
963+
else
964+
((t as? IrSimpleType)?.classOrNull?.owner?.isFinalClass) != true
965+
929966
private fun wildcardAdditionAllowed(v: Variance, t: IrType, addByDefault: Boolean) =
930967
when {
931968
t.hasAnnotation(jvmWildcardAnnotation) -> true
932969
!addByDefault -> false
933970
t.hasAnnotation(jvmWildcardSuppressionAnnotaton) -> false
934971
v == Variance.IN_VARIANCE -> !(t.isNullableAny() || t.isAny())
935-
v == Variance.OUT_VARIANCE -> ((t as? IrSimpleType)?.classOrNull?.owner?.isFinalClass) != true
972+
v == Variance.OUT_VARIANCE -> extendsAdditionAllowed(t)
936973
else -> false
937974
}
938975

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public class User {
2+
3+
public static void test() {
4+
5+
TakesArrayList tal = new TakesArrayList();
6+
tal.invarArray(null);
7+
// Using one method suffices to get the extractor to describe all the methods defined on takesArrayList.
8+
9+
}
10+
11+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
public class TakesArrayList {
2+
3+
// There is a Java user to flag up any problems, as if the .class file differs from my
4+
// claimed types above we'll see two overloads and two parameter types instead of one.
5+
6+
// Test how Array types with a variance should be lowered:
7+
fun invarArray(a: Array<CharSequence>) { }
8+
fun outArray(a: Array<out CharSequence>) { }
9+
fun inArray(a: Array<in CharSequence>) { }
10+
11+
fun invarInvarArray(a: Array<Array<CharSequence>>) { }
12+
fun invarOutArray(a: Array<Array<out CharSequence>>) { }
13+
fun invarInArray(a: Array<Array<in CharSequence>>) { }
14+
15+
fun outInvarArray(a: Array<out Array<CharSequence>>) { }
16+
fun outOutArray(a: Array<out Array<out CharSequence>>) { }
17+
fun outInArray(a: Array<out Array<in CharSequence>>) { }
18+
19+
fun inInvarArray(a: Array<in Array<CharSequence>>) { }
20+
fun inOutArray(a: Array<in Array<out CharSequence>>) { }
21+
fun inInArray(a: Array<in Array<in CharSequence>>) { }
22+
23+
// Test how Array type arguments with a variance should acquire implicit wildcards:
24+
fun invarArrayList(l: List<Array<CharSequence>>) { }
25+
fun outArrayList(l: List<Array<out CharSequence>>) { }
26+
fun inArrayList(l: List<Array<in CharSequence>>) { }
27+
28+
// Check the cases of nested arrays:
29+
fun invarInvarArrayList(l: List<Array<Array<CharSequence>>>) { }
30+
fun invarOutArrayList(l: List<Array<Array<out CharSequence>>>) { }
31+
fun invarInArrayList(l: List<Array<Array<in CharSequence>>>) { }
32+
fun outInvarArrayList(l: List<Array<out Array<CharSequence>>>) { }
33+
fun outOutArrayList(l: List<Array<out Array<out CharSequence>>>) { }
34+
fun outInArrayList(l: List<Array<out Array<in CharSequence>>>) { }
35+
fun inInvarArrayList(l: List<Array<in Array<CharSequence>>>) { }
36+
fun inOutArrayList(l: List<Array<in Array<out CharSequence>>>) { }
37+
fun inInArrayList(l: List<Array<in Array<in CharSequence>>>) { }
38+
39+
// Now check all of that again for Comparable, whose type parameter is contravariant:
40+
fun invarArrayComparable(c: Comparable<Array<CharSequence>>) { }
41+
fun outArrayComparable(c: Comparable<Array<out CharSequence>>) { }
42+
fun inArrayComparable(c: Comparable<Array<in CharSequence>>) { }
43+
44+
fun invarInvarArrayComparable(c: Comparable<Array<Array<CharSequence>>>) { }
45+
fun invarOutArrayComparable(c: Comparable<Array<Array<out CharSequence>>>) { }
46+
fun invarInArrayComparable(c: Comparable<Array<Array<in CharSequence>>>) { }
47+
fun outInvarArrayComparable(c: Comparable<Array<out Array<CharSequence>>>) { }
48+
fun outOutArrayComparable(c: Comparable<Array<out Array<out CharSequence>>>) { }
49+
fun outInArrayComparable(c: Comparable<Array<out Array<in CharSequence>>>) { }
50+
fun inInvarArrayComparable(c: Comparable<Array<in Array<CharSequence>>>) { }
51+
fun inOutArrayComparable(c: Comparable<Array<in Array<out CharSequence>>>) { }
52+
fun inInArrayComparable(c: Comparable<Array<in Array<in CharSequence>>>) { }
53+
54+
// ... duplicate all of that for a final array element (I choose String as a final type), which sometimes suppresses addition of `? extends ...`
55+
fun invarArrayListFinal(l: List<Array<String>>) { }
56+
fun outArrayListFinal(l: List<Array<out String>>) { }
57+
fun inArrayListFinal(l: List<Array<in String>>) { }
58+
59+
fun invarInvarArrayListFinal(l: List<Array<Array<String>>>) { }
60+
fun invarOutArrayListFinal(l: List<Array<Array<out String>>>) { }
61+
fun invarInArrayListFinal(l: List<Array<Array<in String>>>) { }
62+
fun outInvarArrayListFinal(l: List<Array<out Array<String>>>) { }
63+
fun outOutArrayListFinal(l: List<Array<out Array<out String>>>) { }
64+
fun outInArrayListFinal(l: List<Array<out Array<in String>>>) { }
65+
fun inInvarArrayListFinal(l: List<Array<in Array<String>>>) { }
66+
fun inOutArrayListFinal(l: List<Array<in Array<out String>>>) { }
67+
fun inInArrayListFinal(l: List<Array<in Array<in String>>>) { }
68+
69+
fun invarArrayComparableFinal(c: Comparable<Array<String>>) { }
70+
fun outArrayComparableFinal(c: Comparable<Array<out String>>) { }
71+
fun inArrayComparableFinal(c: Comparable<Array<in String>>) { }
72+
73+
fun invarInvarArrayComparableFinal(c: Comparable<Array<Array<String>>>) { }
74+
fun invarOutArrayComparableFinal(c: Comparable<Array<Array<out String>>>) { }
75+
fun invarInArrayComparableFinal(c: Comparable<Array<Array<in String>>>) { }
76+
fun outInvarArrayComparableFinal(c: Comparable<Array<out Array<String>>>) { }
77+
fun outOutArrayComparableFinal(c: Comparable<Array<out Array<out String>>>) { }
78+
fun outInArrayComparableFinal(c: Comparable<Array<out Array<in String>>>) { }
79+
fun inInvarArrayComparableFinal(c: Comparable<Array<in Array<String>>>) { }
80+
fun inOutArrayComparableFinal(c: Comparable<Array<in Array<out String>>>) { }
81+
fun inInArrayComparableFinal(c: Comparable<Array<in Array<in String>>>) { }
82+
83+
// ... and duplicate it once more with Any as the array element, which can suppress adding `? super ...`
84+
fun invarArrayListAny(l: List<Array<Any>>) { }
85+
fun outArrayListAny(l: List<Array<out Any>>) { }
86+
fun inArrayListAny(l: List<Array<in Any>>) { }
87+
88+
fun invarInvarArrayListAny(l: List<Array<Array<Any>>>) { }
89+
fun invarOutArrayListAny(l: List<Array<Array<out Any>>>) { }
90+
fun invarInArrayListAny(l: List<Array<Array<in Any>>>) { }
91+
fun outInvarArrayListAny(l: List<Array<out Array<Any>>>) { }
92+
fun outOutArrayListAny(l: List<Array<out Array<out Any>>>) { }
93+
fun outInArrayListAny(l: List<Array<out Array<in Any>>>) { }
94+
fun inInvarArrayListAny(l: List<Array<in Array<Any>>>) { }
95+
fun inOutArrayListAny(l: List<Array<in Array<out Any>>>) { }
96+
fun inInArrayListAny(l: List<Array<in Array<in Any>>>) { }
97+
98+
fun invarArrayComparableAny(c: Comparable<Array<Any>>) { }
99+
fun outArrayComparableAny(c: Comparable<Array<out Any>>) { }
100+
fun inArrayComparableAny(c: Comparable<Array<in Any>>) { }
101+
102+
fun invarInvarArrayComparableAny(c: Comparable<Array<Array<Any>>>) { }
103+
fun invarOutArrayComparableAny(c: Comparable<Array<Array<out Any>>>) { }
104+
fun invarInArrayComparableAny(c: Comparable<Array<Array<in Any>>>) { }
105+
fun outInvarArrayComparableAny(c: Comparable<Array<out Array<Any>>>) { }
106+
fun outOutArrayComparableAny(c: Comparable<Array<out Array<out Any>>>) { }
107+
fun outInArrayComparableAny(c: Comparable<Array<out Array<in Any>>>) { }
108+
fun inInvarArrayComparableAny(c: Comparable<Array<in Array<Any>>>) { }
109+
fun inOutArrayComparableAny(c: Comparable<Array<in Array<out Any>>>) { }
110+
fun inInArrayComparableAny(c: Comparable<Array<in Array<in Any>>>) { }
111+
112+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
broken
2+
#select
3+
| inArray | Object[] |
4+
| inArrayComparable | Comparable<? super Object[]> |
5+
| inArrayComparableAny | Comparable<? super Object[]> |
6+
| inArrayComparableFinal | Comparable<? super Object[]> |
7+
| inArrayList | List<? extends Object[]> |
8+
| inArrayListAny | List<Object[]> |
9+
| inArrayListFinal | List<? extends Object[]> |
10+
| inInArray | Object[] |
11+
| inInArrayComparable | Comparable<? super Object[]> |
12+
| inInArrayComparableAny | Comparable<? super Object[]> |
13+
| inInArrayComparableFinal | Comparable<? super Object[]> |
14+
| inInArrayList | List<? extends Object[]> |
15+
| inInArrayListAny | List<? extends Object[]> |
16+
| inInArrayListFinal | List<? extends Object[]> |
17+
| inInvarArray | Object[] |
18+
| inInvarArrayComparable | Comparable<? super Object[]> |
19+
| inInvarArrayComparableAny | Comparable<? super Object[]> |
20+
| inInvarArrayComparableFinal | Comparable<? super Object[]> |
21+
| inInvarArrayList | List<? extends Object[]> |
22+
| inInvarArrayListAny | List<? extends Object[]> |
23+
| inInvarArrayListFinal | List<? extends Object[]> |
24+
| inOutArray | Object[] |
25+
| inOutArrayComparable | Comparable<? super Object[]> |
26+
| inOutArrayComparableAny | Comparable<? super Object[]> |
27+
| inOutArrayComparableFinal | Comparable<? super Object[]> |
28+
| inOutArrayList | List<? extends Object[]> |
29+
| inOutArrayListAny | List<? extends Object[]> |
30+
| inOutArrayListFinal | List<? extends Object[]> |
31+
| invarArray | CharSequence[] |
32+
| invarArrayComparable | Comparable<? super CharSequence[]> |
33+
| invarArrayComparableAny | Comparable<? super Object[]> |
34+
| invarArrayComparableFinal | Comparable<? super String[]> |
35+
| invarArrayList | List<CharSequence[]> |
36+
| invarArrayListAny | List<Object[]> |
37+
| invarArrayListFinal | List<String[]> |
38+
| invarInArray | Object[][] |
39+
| invarInArrayComparable | Comparable<? super Object[][]> |
40+
| invarInArrayComparableAny | Comparable<? super Object[][]> |
41+
| invarInArrayComparableFinal | Comparable<? super Object[][]> |
42+
| invarInArrayList | List<Object[][]> |
43+
| invarInArrayListAny | List<Object[][]> |
44+
| invarInArrayListFinal | List<Object[][]> |
45+
| invarInvarArray | CharSequence[][] |
46+
| invarInvarArrayComparable | Comparable<? super CharSequence[][]> |
47+
| invarInvarArrayComparableAny | Comparable<? super Object[][]> |
48+
| invarInvarArrayComparableFinal | Comparable<? super String[][]> |
49+
| invarInvarArrayList | List<CharSequence[][]> |
50+
| invarInvarArrayListAny | List<Object[][]> |
51+
| invarInvarArrayListFinal | List<String[][]> |
52+
| invarOutArray | CharSequence[][] |
53+
| invarOutArrayComparable | Comparable<? super CharSequence[][]> |
54+
| invarOutArrayComparableAny | Comparable<? super Object[][]> |
55+
| invarOutArrayComparableFinal | Comparable<? super String[][]> |
56+
| invarOutArrayList | List<CharSequence[][]> |
57+
| invarOutArrayListAny | List<Object[][]> |
58+
| invarOutArrayListFinal | List<String[][]> |
59+
| outArray | CharSequence[] |
60+
| outArrayComparable | Comparable<? super CharSequence[]> |
61+
| outArrayComparableAny | Comparable<? super Object[]> |
62+
| outArrayComparableFinal | Comparable<? super String[]> |
63+
| outArrayList | List<? extends CharSequence[]> |
64+
| outArrayListAny | List<? extends Object[]> |
65+
| outArrayListFinal | List<String[]> |
66+
| outInArray | Object[][] |
67+
| outInArrayComparable | Comparable<? super Object[][]> |
68+
| outInArrayComparableAny | Comparable<? super Object[][]> |
69+
| outInArrayComparableFinal | Comparable<? super Object[][]> |
70+
| outInArrayList | List<? extends Object[][]> |
71+
| outInArrayListAny | List<Object[][]> |
72+
| outInArrayListFinal | List<? extends Object[][]> |
73+
| outInvarArray | CharSequence[][] |
74+
| outInvarArrayComparable | Comparable<? super CharSequence[][]> |
75+
| outInvarArrayComparableAny | Comparable<? super Object[][]> |
76+
| outInvarArrayComparableFinal | Comparable<? super String[][]> |
77+
| outInvarArrayList | List<CharSequence[][]> |
78+
| outInvarArrayListAny | List<Object[][]> |
79+
| outInvarArrayListFinal | List<String[][]> |
80+
| outOutArray | CharSequence[][] |
81+
| outOutArrayComparable | Comparable<? super CharSequence[][]> |
82+
| outOutArrayComparableAny | Comparable<? super Object[][]> |
83+
| outOutArrayComparableFinal | Comparable<? super String[][]> |
84+
| outOutArrayList | List<? extends CharSequence[][]> |
85+
| outOutArrayListAny | List<? extends Object[][]> |
86+
| outOutArrayListFinal | List<String[][]> |
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import java
2+
3+
class InterestingMethod extends Method {
4+
InterestingMethod() { this.getDeclaringType().getName() = "TakesArrayList" }
5+
}
6+
7+
query predicate broken(string methodName) {
8+
methodName = any(InterestingMethod m).getName() and
9+
count(Type t, InterestingMethod m | methodName = m.getName() and t = m.getAParamType() | t) != 1
10+
}
11+
12+
from InterestingMethod m
13+
select m.getName(), m.getAParamType().toString()

0 commit comments

Comments
 (0)