Skip to content

Commit 28b6e26

Browse files
committed
Kotlin: reintroduce pointless wildcards when a Java declaration explicitly uses them
For example, Java code might use `HasOutVariance<? extends String>`, or `HasInVariance<? super Object>`, both of which are needless wildcards and which the Kotlin extractor would previously have refused to reintroduce due to their not specifying a larger type than their bound. However this led to inconsistency with Java extraction, which extracts the type as it appears in source. This seems to particularly happen with generated code, e.g. the output of the Kotlin protobuf compiler.
1 parent fac383a commit 28b6e26

File tree

7 files changed

+38
-3
lines changed

7 files changed

+38
-3
lines changed

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -960,11 +960,13 @@ open class KotlinUsesExtractor(
960960
((t as? IrSimpleType)?.classOrNull?.owner?.isFinalClass) != true
961961
}
962962

963-
private fun wildcardAdditionAllowed(v: Variance, t: IrType, addByDefault: Boolean) =
963+
private fun wildcardAdditionAllowed(v: Variance, t: IrType, addByDefault: Boolean, javaVariance: Variance?) =
964964
when {
965965
t.hasAnnotation(jvmWildcardAnnotation) -> true
966966
!addByDefault -> false
967967
t.hasAnnotation(jvmWildcardSuppressionAnnotation) -> false
968+
// If a Java declaration specifies a variance, introduce it even if it's pointless (e.g. ? extends FinalClass, or ? super Object)
969+
javaVariance == v -> true
968970
v == Variance.IN_VARIANCE -> !(t.isNullableAny() || t.isAny())
969971
v == Variance.OUT_VARIANCE -> extendsAdditionAllowed(t)
970972
else -> false
@@ -973,14 +975,21 @@ open class KotlinUsesExtractor(
973975
private fun addJavaLoweringArgumentWildcards(p: IrTypeParameter, t: IrTypeArgument, addByDefault: Boolean, javaType: JavaType?): IrTypeArgument =
974976
(t as? IrTypeProjection)?.let {
975977
val newBase = addJavaLoweringWildcards(it.type, addByDefault, javaType)
978+
// Note javaVariance == null means we don't have a Java type to conform to -- for example if this is a Kotlin source definition.
979+
val javaVariance = javaType?.let { jType ->
980+
when (jType) {
981+
is JavaWildcardType -> if (jType.isExtends) Variance.OUT_VARIANCE else Variance.IN_VARIANCE
982+
else -> Variance.INVARIANT
983+
}
984+
}
976985
val newVariance =
977986
if (it.variance == Variance.INVARIANT &&
978987
p.variance != Variance.INVARIANT &&
979988
// The next line forbids inferring a wildcard type when we have a corresponding Java type with conflicting variance.
980989
// For example, Java might declare f(Comparable<CharSequence> cs), in which case we shouldn't add a `? super ...`
981990
// wildcard. Note if javaType is unknown (e.g. this is a Kotlin source element), we assume wildcards should be added.
982-
(javaType?.let { jt -> jt is JavaWildcardType && jt.isExtends == (p.variance == Variance.OUT_VARIANCE) } != false) &&
983-
wildcardAdditionAllowed(p.variance, it.type, addByDefault))
991+
(javaVariance == null || javaVariance == p.variance) &&
992+
wildcardAdditionAllowed(p.variance, it.type, addByDefault, javaVariance))
984993
p.variance
985994
else
986995
it.variance
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
public class Test {
2+
3+
// This gets mapped to kotlin.Iterable<out T>, meaning we must reintroduce the use-site extends variance to get a type consistent with Java.
4+
public static void needlessExtends(Iterable<? extends String> l) { }
5+
6+
// This type is defined KotlinConsumer<in T>, meaning we must reintroduce the use-site extends variance to get a type consistent with Java.
7+
public static void needlessSuper(KotlinConsumer<? super Object> l) { }
8+
9+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public class KotlinConsumer<in T> { }
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| Test.java:4:22:4:36 | needlessExtends | file:///modules/java.base/java/lang/Iterable.class:0:0:0:0 | Iterable<? extends String> |
2+
| Test.java:7:22:7:34 | needlessSuper | build1/KotlinConsumer.class:0:0:0:0 | KotlinConsumer<? super Object> |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from create_database_utils import *
2+
3+
os.mkdir('build1')
4+
os.mkdir('build2')
5+
run_codeql_database_create(["kotlinc kConsumer.kt -d build1", "javac Test.java -cp build1 -d build2", "kotlinc user.kt -cp build1:build2"], lang="java")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import java
2+
3+
from Method m
4+
where m.fromSource()
5+
select m, m.getAParamType()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fun f() {
2+
Test.needlessExtends(null)
3+
Test.needlessSuper(null)
4+
}

0 commit comments

Comments
 (0)