Skip to content

Commit 37fce6a

Browse files
committed
Restore implicit wildcard types
The Kotlin compiler represents types like List<out CharSequence> internally as List<CharSequence> due to the fact that List's type parameter is covariant, and similarly Comparable<in CharSequence> where Comparable's type parameter is contravariant. However it restores use-site variance when emitting class files, so we must do the same thing for compatability with Java code. Note this is a partial solution because it will also add wildcards to Java .class files that *could* have a variance / wildcard but don't -- for example, a Java method could really take an invariant Comparable<CharSequence>, which is only achievable in Kotlin via the @JvmSuppressWildcards annotation. We also don't yet support @JvmSuppressWildcards given on a surrounding class or function.
1 parent 4b2b6fa commit 37fce6a

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,9 @@ open class KotlinFileExtractor(
497497
else
498498
null
499499
} ?: vp.type
500-
val substitutedType = typeSubstitution?.let { it(maybeErasedType, TypeContext.OTHER, pluginContext) } ?: maybeErasedType
500+
val typeWithWildcards = addJavaLoweringWildcards(maybeErasedType, true)
501+
val substitutedType = typeSubstitution?.let { it(typeWithWildcards, TypeContext.OTHER, pluginContext) } ?: typeWithWildcards
502+
501503
val id = useValueParameter(vp, parent)
502504
if (extractTypeAccess) {
503505
extractTypeAccessRecursive(substitutedType, location, id, -1)
@@ -704,7 +706,7 @@ open class KotlinFileExtractor(
704706

705707
val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature!! }
706708

707-
val adjustedReturnType = getAdjustedReturnType(f)
709+
val adjustedReturnType = addJavaLoweringWildcards(getAdjustedReturnType(f), false)
708710
val substReturnType = typeSubstitution?.let { it(adjustedReturnType, TypeContext.RETURN, pluginContext) } ?: adjustedReturnType
709711

710712
val locId = locOverride ?: getLocation(f, classTypeArgsIncludingOuterClasses)

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

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.github.codeql.utils.versions.isRawType
66
import com.semmle.extractor.java.OdasaOutput
77
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
88
import org.jetbrains.kotlin.backend.common.ir.allOverridden
9+
import org.jetbrains.kotlin.backend.common.ir.isFinalClass
910
import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf
1011
import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation
1112
import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor
@@ -850,6 +851,49 @@ open class KotlinUsesExtractor(
850851
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableCollection")) ||
851852
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableList"))
852853

854+
855+
private fun wildcardAdditionAllowed(v: Variance, t: IrType, inParameterContext: Boolean) =
856+
when {
857+
t.hasAnnotation(FqName("kotlin.jvm.JvmWildcard")) -> true
858+
!inParameterContext -> false // By default, wildcards are only automatically added for method parameters.
859+
t.hasAnnotation(FqName("kotlin.jvm.JvmSuppressWildcards")) -> false
860+
v == Variance.IN_VARIANCE -> !(t.isNullableAny() || t.isAny())
861+
v == Variance.OUT_VARIANCE -> ((t as? IrSimpleType)?.classOrNull?.owner?.isFinalClass) != true
862+
else -> false
863+
}
864+
865+
private fun addJavaLoweringArgumentWildcards(p: IrTypeParameter, t: IrTypeArgument, inParameterContext: Boolean): IrTypeArgument =
866+
(t as? IrTypeProjection)?.let {
867+
val newBase = addJavaLoweringWildcards(it.type, inParameterContext)
868+
val newVariance =
869+
if (it.variance == Variance.INVARIANT &&
870+
p.variance != Variance.INVARIANT &&
871+
wildcardAdditionAllowed(p.variance, it.type, inParameterContext))
872+
p.variance
873+
else
874+
it.variance
875+
if (newBase !== it.type || newVariance != it.variance)
876+
makeTypeProjection(newBase, newVariance)
877+
else
878+
null
879+
} ?: t
880+
881+
fun addJavaLoweringWildcards(t: IrType, inParameterContext: Boolean): IrType =
882+
(t as? IrSimpleType)?.let {
883+
val typeParams = it.classOrNull?.owner?.typeParameters ?: return t
884+
val newArgs = typeParams.zip(it.arguments).map { pair ->
885+
addJavaLoweringArgumentWildcards(
886+
pair.first,
887+
pair.second,
888+
inParameterContext
889+
)
890+
}
891+
return if (newArgs.zip(it.arguments).all { pair -> pair.first === pair.second })
892+
t
893+
else
894+
it.toBuilder().also { builder -> builder.arguments = newArgs }.buildSimpleType()
895+
} ?: t
896+
853897
/*
854898
* This is the normal getFunctionLabel function to use. If you want
855899
* to refer to the function in its source class then
@@ -956,8 +1000,10 @@ open class KotlinUsesExtractor(
9561000
// Collection.remove(Object) because Collection.remove(Collection::E) in the Kotlin universe.
9571001
// If this has happened, erase the type again to get the correct Java signature.
9581002
val maybeAmendedForCollections = if (overridesCollectionsMethod) eraseCollectionsMethodParameterType(it.value.type, name, it.index) else it.value.type
1003+
// Add any wildcard types that the Kotlin compiler would add in the Java lowering of this function:
1004+
val withAddedWildcards = addJavaLoweringWildcards(maybeAmendedForCollections, true)
9591005
// Now substitute any class type parameters in:
960-
val maybeSubbed = maybeAmendedForCollections.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext)
1006+
val maybeSubbed = withAddedWildcards.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext)
9611007
// Finally, mimic the Java extractor's behaviour by naming functions with type parameters for their erased types;
9621008
// those without type parameters are named for the generic type.
9631009
val maybeErased = if (functionTypeParameters.isEmpty()) maybeSubbed else erase(maybeSubbed)
@@ -969,6 +1015,8 @@ open class KotlinUsesExtractor(
9691015
pluginContext.irBuiltIns.unitType
9701016
else
9711017
erase(returnType.substituteTypeAndArguments(substitutionMap, TypeContext.RETURN, pluginContext))
1018+
// Note that `addJavaLoweringWildcards` is not required here because the return type used to form the function
1019+
// label is always erased.
9721020
val returnTypeId = useType(labelReturnType, TypeContext.RETURN).javaResult.id
9731021
// This suffix is added to generic methods (and constructors) to match the Java extractor's behaviour.
9741022
// Comments in that extractor indicates it didn't want the label of the callable to clash with the raw

0 commit comments

Comments
 (0)