Skip to content

Commit dc7d07f

Browse files
committed
Extract correct implied wildcards for Java classes and @JvmSuppressWildcards-annotated entities
For Java classes this means following the structure of the underlying Java type to determine where the wildcard was really present and where the Java signature ruled it out. The annotation tracking simply means looking for @JvmSuppressWildcards on any surrounding class or function to turn off wildcard introduction by default.
1 parent 37fce6a commit dc7d07f

File tree

2 files changed

+52
-17
lines changed

2 files changed

+52
-17
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -497,9 +497,9 @@ open class KotlinFileExtractor(
497497
else
498498
null
499499
} ?: vp.type
500-
val typeWithWildcards = addJavaLoweringWildcards(maybeErasedType, true)
500+
val javaType = ((vp.parent as? IrFunction)?.let { getJavaMethod(it) })?.valueParameters?.getOrNull(idx)?.type
501+
val typeWithWildcards = addJavaLoweringWildcards(maybeErasedType, !hasWildcardSuppressionAnnotation(vp), javaType)
501502
val substitutedType = typeSubstitution?.let { it(typeWithWildcards, TypeContext.OTHER, pluginContext) } ?: typeWithWildcards
502-
503503
val id = useValueParameter(vp, parent)
504504
if (extractTypeAccess) {
505505
extractTypeAccessRecursive(substitutedType, location, id, -1)
@@ -533,7 +533,9 @@ open class KotlinFileExtractor(
533533
extensionReceiverParameter = null,
534534
functionTypeParameters = listOf(),
535535
classTypeArgsIncludingOuterClasses = listOf(),
536-
overridesCollectionsMethod = false
536+
overridesCollectionsMethod = false,
537+
javaSignature = null,
538+
addParameterWildcardsByDefault = false
537539
)
538540
val clinitId = tw.getLabelFor<DbMethod>(clinitLabel)
539541
val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
@@ -706,7 +708,7 @@ open class KotlinFileExtractor(
706708

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

709-
val adjustedReturnType = addJavaLoweringWildcards(getAdjustedReturnType(f), false)
711+
val adjustedReturnType = addJavaLoweringWildcards(getAdjustedReturnType(f), false, getJavaMethod(f)?.returnType)
710712
val substReturnType = typeSubstitution?.let { it(adjustedReturnType, TypeContext.RETURN, pluginContext) } ?: adjustedReturnType
711713

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

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

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.semmle.extractor.java.OdasaOutput
77
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
88
import org.jetbrains.kotlin.backend.common.ir.allOverridden
99
import org.jetbrains.kotlin.backend.common.ir.isFinalClass
10+
import org.jetbrains.kotlin.backend.common.lower.parents
1011
import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf
1112
import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation
1213
import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor
@@ -21,6 +22,8 @@ import org.jetbrains.kotlin.ir.types.impl.*
2122
import org.jetbrains.kotlin.ir.util.*
2223
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature
2324
import org.jetbrains.kotlin.load.java.JvmAbi
25+
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
26+
import org.jetbrains.kotlin.load.java.structure.*
2427
import org.jetbrains.kotlin.name.FqName
2528
import org.jetbrains.kotlin.name.Name
2629
import org.jetbrains.kotlin.name.SpecialNames
@@ -852,23 +855,30 @@ open class KotlinUsesExtractor(
852855
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableList"))
853856

854857

855-
private fun wildcardAdditionAllowed(v: Variance, t: IrType, inParameterContext: Boolean) =
858+
private val jvmWildcardAnnotation = FqName("kotlin.jvm.JvmWildcard")
859+
private val jvmWildcardSuppressionAnnotaton = FqName("kotlin.jvm.JvmSuppressWildcards")
860+
861+
private fun wildcardAdditionAllowed(v: Variance, t: IrType, addByDefault: Boolean) =
856862
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
863+
t.hasAnnotation(jvmWildcardAnnotation) -> true
864+
!addByDefault -> false
865+
t.hasAnnotation(jvmWildcardSuppressionAnnotaton) -> false
860866
v == Variance.IN_VARIANCE -> !(t.isNullableAny() || t.isAny())
861867
v == Variance.OUT_VARIANCE -> ((t as? IrSimpleType)?.classOrNull?.owner?.isFinalClass) != true
862868
else -> false
863869
}
864870

865-
private fun addJavaLoweringArgumentWildcards(p: IrTypeParameter, t: IrTypeArgument, inParameterContext: Boolean): IrTypeArgument =
871+
private fun addJavaLoweringArgumentWildcards(p: IrTypeParameter, t: IrTypeArgument, addByDefault: Boolean, javaType: JavaType?): IrTypeArgument =
866872
(t as? IrTypeProjection)?.let {
867-
val newBase = addJavaLoweringWildcards(it.type, inParameterContext)
873+
val newBase = addJavaLoweringWildcards(it.type, addByDefault, javaType)
868874
val newVariance =
869875
if (it.variance == Variance.INVARIANT &&
870876
p.variance != Variance.INVARIANT &&
871-
wildcardAdditionAllowed(p.variance, it.type, inParameterContext))
877+
// The next line forbids inferring a wildcard type when we have a corresponding Java type with conflicting variance.
878+
// For example, Java might declare f(Comparable<CharSequence> cs), in which case we shouldn't add a `? super ...`
879+
// wildcard. Note if javaType is unknown (e.g. this is a Kotlin source element), we assume wildcards should be added.
880+
(javaType?.let { jt -> jt is JavaWildcardType && jt.isExtends == (p.variance == Variance.OUT_VARIANCE) } != false) &&
881+
wildcardAdditionAllowed(p.variance, it.type, addByDefault))
872882
p.variance
873883
else
874884
it.variance
@@ -878,14 +888,22 @@ open class KotlinUsesExtractor(
878888
null
879889
} ?: t
880890

881-
fun addJavaLoweringWildcards(t: IrType, inParameterContext: Boolean): IrType =
891+
fun getJavaTypeArgument(jt: JavaType, idx: Int) =
892+
when(jt) {
893+
is JavaClassifierType -> jt.typeArguments.getOrNull(idx)
894+
is JavaArrayType -> if (idx == 0) jt.componentType else null
895+
else -> null
896+
}
897+
898+
fun addJavaLoweringWildcards(t: IrType, addByDefault: Boolean, javaType: JavaType?): IrType =
882899
(t as? IrSimpleType)?.let {
883900
val typeParams = it.classOrNull?.owner?.typeParameters ?: return t
884-
val newArgs = typeParams.zip(it.arguments).map { pair ->
901+
val newArgs = typeParams.zip(it.arguments).mapIndexed { idx, pair ->
885902
addJavaLoweringArgumentWildcards(
886903
pair.first,
887904
pair.second,
888-
inParameterContext
905+
addByDefault,
906+
javaType?.let { jt -> getJavaTypeArgument(jt, idx) }
889907
)
890908
}
891909
return if (newArgs.zip(it.arguments).all { pair -> pair.first === pair.second })
@@ -927,6 +945,14 @@ open class KotlinUsesExtractor(
927945
return otherKeySet.returnType.codeQlWithHasQuestionMark(false)
928946
}
929947

948+
@OptIn(ObsoleteDescriptorBasedAPI::class)
949+
fun getJavaMethod(f: IrFunction) = (f.descriptor.source as? JavaSourceElement)?.javaElement as? JavaMethod
950+
951+
fun hasWildcardSuppressionAnnotation(d: IrDeclaration) =
952+
d.hasAnnotation(jvmWildcardSuppressionAnnotaton) ||
953+
// Note not using `parentsWithSelf` as that only works if `d` is an IrDeclarationParent
954+
d.parents.any { (it as? IrAnnotationContainer)?.hasAnnotation(jvmWildcardSuppressionAnnotaton) == true }
955+
930956
/*
931957
* There are some pairs of classes (e.g. `kotlin.Throwable` and
932958
* `java.lang.Throwable`) which are really just 2 different names
@@ -947,7 +973,9 @@ open class KotlinUsesExtractor(
947973
f.extensionReceiverParameter,
948974
getFunctionTypeParameters(f),
949975
classTypeArgsIncludingOuterClasses,
950-
overridesCollectionsMethodWithAlteredParameterTypes(f)
976+
overridesCollectionsMethodWithAlteredParameterTypes(f),
977+
getJavaMethod(f),
978+
!hasWildcardSuppressionAnnotation(f)
951979
)
952980

953981
/*
@@ -977,6 +1005,11 @@ open class KotlinUsesExtractor(
9771005
// If true, this method implements a Java Collections interface (Collection, Map or List) and may need
9781006
// parameter erasure to match the way this class will appear to an external consumer of the .class file.
9791007
overridesCollectionsMethod: Boolean,
1008+
// The Java signature of this callable, if known.
1009+
javaSignature: JavaMethod?,
1010+
// If true, Java wildcards implied by Kotlin type parameter variance should be added by default to this function's value parameters' types.
1011+
// (Return-type wildcard addition is always off by default)
1012+
addParameterWildcardsByDefault: Boolean,
9801013
// The prefix used in the label. "callable", unless a property label is created, then it's "property".
9811014
prefix: String = "callable"
9821015
): String {
@@ -1001,7 +1034,7 @@ open class KotlinUsesExtractor(
10011034
// If this has happened, erase the type again to get the correct Java signature.
10021035
val maybeAmendedForCollections = if (overridesCollectionsMethod) eraseCollectionsMethodParameterType(it.value.type, name, it.index) else it.value.type
10031036
// Add any wildcard types that the Kotlin compiler would add in the Java lowering of this function:
1004-
val withAddedWildcards = addJavaLoweringWildcards(maybeAmendedForCollections, true)
1037+
val withAddedWildcards = addJavaLoweringWildcards(maybeAmendedForCollections, addParameterWildcardsByDefault, javaSignature?.let { sig -> sig.valueParameters[it.index].type })
10051038
// Now substitute any class type parameters in:
10061039
val maybeSubbed = withAddedWildcards.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext)
10071040
// Finally, mimic the Java extractor's behaviour by naming functions with type parameters for their erased types;
@@ -1473,7 +1506,7 @@ open class KotlinUsesExtractor(
14731506
val returnType = getter?.returnType ?: setter?.valueParameters?.singleOrNull()?.type ?: pluginContext.irBuiltIns.unitType
14741507
val typeParams = getFunctionTypeParameters(func)
14751508

1476-
getFunctionLabel(p.parent, parentId, p.name.asString(), listOf(), returnType, ext, typeParams, classTypeArgsIncludingOuterClasses, overridesCollectionsMethod = false, prefix = "property")
1509+
getFunctionLabel(p.parent, parentId, p.name.asString(), listOf(), returnType, ext, typeParams, classTypeArgsIncludingOuterClasses, overridesCollectionsMethod = false, javaSignature = null, addParameterWildcardsByDefault = false, prefix = "property")
14771510
}
14781511
}
14791512

0 commit comments

Comments
 (0)