Skip to content

Commit 1708719

Browse files
authored
Merge pull request github#9343 from smowton/smowton/fix/align-kotlin-java-generic-types
Kotlin: extract methods defined on collections types with their Java signatures
2 parents cd1800e + 49d9d8e commit 1708719

File tree

6 files changed

+591
-15
lines changed

6 files changed

+591
-15
lines changed

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,17 @@ open class KotlinFileExtractor(
491491
private fun extractValueParameter(vp: IrValueParameter, parent: Label<out DbCallable>, idx: Int, typeSubstitution: TypeSubstitution?, parentSourceDeclaration: Label<out DbCallable>, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, extractTypeAccess: Boolean, locOverride: Label<DbLocation>? = null): TypeResults {
492492
with("value parameter", vp) {
493493
val location = locOverride ?: getLocation(vp, classTypeArgsIncludingOuterClasses)
494+
val maybeErasedType = (vp.parent as? IrFunction)?.let {
495+
if (overridesCollectionsMethodWithAlteredParameterTypes(it))
496+
eraseCollectionsMethodParameterType(vp.type, it.name.asString(), idx)
497+
else
498+
null
499+
} ?: vp.type
494500
val id = useValueParameter(vp, parent)
495501
if (extractTypeAccess) {
496-
extractTypeAccessRecursive(typeSubstitution?.let { it(vp.type, TypeContext.OTHER, pluginContext) } ?: vp.type, location, id, -1)
502+
extractTypeAccessRecursive(typeSubstitution?.let { it(maybeErasedType, TypeContext.OTHER, pluginContext) } ?: maybeErasedType, location, id, -1)
497503
}
498-
return extractValueParameter(id, vp.type, vp.name.asString(), location, parent, idx, typeSubstitution, useValueParameter(vp, parentSourceDeclaration), vp.isVararg)
504+
return extractValueParameter(id, maybeErasedType, vp.name.asString(), location, parent, idx, typeSubstitution, useValueParameter(vp, parentSourceDeclaration), vp.isVararg)
499505
}
500506
}
501507

@@ -524,7 +530,8 @@ open class KotlinFileExtractor(
524530
pluginContext.irBuiltIns.unitType,
525531
extensionReceiverParameter = null,
526532
functionTypeParameters = listOf(),
527-
classTypeArgsIncludingOuterClasses = listOf()
533+
classTypeArgsIncludingOuterClasses = listOf(),
534+
overridesCollectionsMethod = false
528535
)
529536
val clinitId = tw.getLabelFor<DbMethod>(clinitLabel)
530537
val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
@@ -1395,12 +1402,6 @@ open class KotlinFileExtractor(
13951402
result
13961403
}
13971404

1398-
val javaLangObject by lazy {
1399-
val result = pluginContext.referenceClass(FqName("java.lang.Object"))?.owner
1400-
result?.let { extractExternalClassLater(it) }
1401-
result
1402-
}
1403-
14041405
val objectCloneMethod by lazy {
14051406
val result = javaLangObject?.declarations?.find {
14061407
it is IrFunction && it.name.asString() == "clone"

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

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation
1010
import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor
1111
import org.jetbrains.kotlin.builtins.StandardNames
1212
import org.jetbrains.kotlin.descriptors.*
13+
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
1314
import org.jetbrains.kotlin.ir.declarations.*
1415
import org.jetbrains.kotlin.ir.expressions.*
1516
import org.jetbrains.kotlin.ir.symbols.*
1617
import org.jetbrains.kotlin.ir.types.*
1718
import org.jetbrains.kotlin.ir.types.impl.*
1819
import org.jetbrains.kotlin.ir.util.*
20+
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature
1921
import org.jetbrains.kotlin.load.java.JvmAbi
2022
import org.jetbrains.kotlin.name.FqName
2123
import org.jetbrains.kotlin.name.Name
@@ -32,6 +34,17 @@ open class KotlinUsesExtractor(
3234
val pluginContext: IrPluginContext,
3335
val globalExtensionState: KotlinExtractorGlobalState
3436
) {
37+
38+
val javaLangObject by lazy {
39+
val result = pluginContext.referenceClass(FqName("java.lang.Object"))?.owner
40+
result?.let { extractExternalClassLater(it) }
41+
result
42+
}
43+
44+
val javaLangObjectType by lazy {
45+
javaLangObject?.typeWith()
46+
}
47+
3548
fun usePackage(pkg: String): Label<out DbPackage> {
3649
return extractPackage(pkg)
3750
}
@@ -790,6 +803,52 @@ open class KotlinUsesExtractor(
790803
else -> null
791804
}
792805

806+
val javaUtilCollection by lazy {
807+
val result = pluginContext.referenceClass(FqName("java.util.Collection"))?.owner
808+
result?.let { extractExternalClassLater(it) }
809+
result
810+
}
811+
812+
val wildcardCollectionType by lazy {
813+
javaUtilCollection?.let {
814+
it.symbol.typeWithArguments(listOf(IrStarProjectionImpl))
815+
}
816+
}
817+
818+
private fun makeCovariant(t: IrTypeArgument) =
819+
t.typeOrNull?.let { makeTypeProjection(it, Variance.OUT_VARIANCE) } ?: t
820+
821+
private fun makeArgumentsCovariant(t: IrType) = (t as? IrSimpleType)?.let {
822+
t.toBuilder().also { b -> b.arguments = b.arguments.map(this::makeCovariant) }.buildSimpleType()
823+
} ?: t
824+
825+
fun eraseCollectionsMethodParameterType(t: IrType, collectionsMethodName: String, paramIdx: Int) =
826+
when(collectionsMethodName) {
827+
"contains", "remove", "containsKey", "containsValue", "get", "indexOf", "lastIndexOf" -> javaLangObjectType
828+
"getOrDefault" -> if (paramIdx == 0) javaLangObjectType else null
829+
"containsAll", "removeAll", "retainAll" -> wildcardCollectionType
830+
// Kotlin defines these like addAll(Collection<E>); Java uses addAll(Collection<? extends E>)
831+
"putAll", "addAll" -> makeArgumentsCovariant(t)
832+
else -> null
833+
} ?: t
834+
835+
private fun overridesFunctionDefinedOn(f: IrFunction, packageName: String, className: String) =
836+
(f as? IrSimpleFunction)?.let {
837+
it.overriddenSymbols.any { overridden ->
838+
overridden.owner.parentClassOrNull?.let { defnClass ->
839+
defnClass.name.asString() == className &&
840+
defnClass.packageFqName?.asString() == packageName
841+
} ?: false
842+
}
843+
} ?: false
844+
845+
@OptIn(ObsoleteDescriptorBasedAPI::class)
846+
fun overridesCollectionsMethodWithAlteredParameterTypes(f: IrFunction) =
847+
BuiltinMethodsWithSpecialGenericSignature.getOverriddenBuiltinFunctionWithErasedValueParametersInJava(f.descriptor) != null ||
848+
(f.name.asString() == "putAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableMap")) ||
849+
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableCollection")) ||
850+
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableList"))
851+
793852
/*
794853
* This is the normal getFunctionLabel function to use. If you want
795854
* to refer to the function in its source class then
@@ -811,8 +870,19 @@ open class KotlinUsesExtractor(
811870
* `java.lang.Throwable`, which isn't what we want. So we have to
812871
* allow it to be passed in.
813872
*/
873+
@OptIn(ObsoleteDescriptorBasedAPI::class)
814874
fun getFunctionLabel(f: IrFunction, maybeParentId: Label<out DbElement>?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) =
815-
getFunctionLabel(f.parent, maybeParentId, getFunctionShortName(f).nameInDB, f.valueParameters, f.returnType, f.extensionReceiverParameter, getFunctionTypeParameters(f), classTypeArgsIncludingOuterClasses)
875+
getFunctionLabel(
876+
f.parent,
877+
maybeParentId,
878+
getFunctionShortName(f).nameInDB,
879+
f.valueParameters,
880+
f.returnType,
881+
f.extensionReceiverParameter,
882+
getFunctionTypeParameters(f),
883+
classTypeArgsIncludingOuterClasses,
884+
overridesCollectionsMethodWithAlteredParameterTypes(f)
885+
)
816886

817887
/*
818888
* This function actually generates the label for a function.
@@ -838,6 +908,9 @@ open class KotlinUsesExtractor(
838908
functionTypeParameters: List<IrTypeParameter>,
839909
// The type arguments of enclosing classes of the function.
840910
classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?,
911+
// If true, this method implements a Java Collections interface (Collection, Map or List) and may need
912+
// parameter erasure to match the way this class will appear to an external consumer of the .class file.
913+
overridesCollectionsMethod: Boolean,
841914
// The prefix used in the label. "callable", unless a property label is created, then it's "property".
842915
prefix: String = "callable"
843916
): String {
@@ -856,14 +929,19 @@ open class KotlinUsesExtractor(
856929
enclosingClass?.let { notNullClass -> makeTypeGenericSubstitutionMap(notNullClass, notNullArgs) }
857930
}
858931
}
859-
val getIdForFunctionLabel = { it: IrValueParameter ->
860-
// Mimic the Java extractor's behaviour: functions with type parameters are named for their erased types;
932+
val getIdForFunctionLabel = { it: IndexedValue<IrValueParameter> ->
933+
// Kotlin rewrites certain Java collections types adding additional generic constraints-- for example,
934+
// Collection.remove(Object) because Collection.remove(Collection::E) in the Kotlin universe.
935+
// If this has happened, erase the type again to get the correct Java signature.
936+
val maybeAmendedForCollections = if (overridesCollectionsMethod) eraseCollectionsMethodParameterType(it.value.type, name, it.index) else it.value.type
937+
// Now substitute any class type parameters in:
938+
val maybeSubbed = maybeAmendedForCollections.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext)
939+
// Finally, mimic the Java extractor's behaviour by naming functions with type parameters for their erased types;
861940
// those without type parameters are named for the generic type.
862-
val maybeSubbed = it.type.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext)
863941
val maybeErased = if (functionTypeParameters.isEmpty()) maybeSubbed else erase(maybeSubbed)
864942
"{${useType(maybeErased).javaResult.id}}"
865943
}
866-
val paramTypeIds = allParams.joinToString(separator = ",", transform = getIdForFunctionLabel)
944+
val paramTypeIds = allParams.withIndex().joinToString(separator = ",", transform = getIdForFunctionLabel)
867945
val labelReturnType =
868946
if (name == "<init>")
869947
pluginContext.irBuiltIns.unitType
@@ -1325,7 +1403,7 @@ open class KotlinUsesExtractor(
13251403
val returnType = getter?.returnType ?: setter?.valueParameters?.singleOrNull()?.type ?: pluginContext.irBuiltIns.unitType
13261404
val typeParams = getFunctionTypeParameters(func)
13271405

1328-
getFunctionLabel(p.parent, parentId, p.name.asString(), listOf(), returnType, ext, typeParams, classTypeArgsIncludingOuterClasses, "property")
1406+
getFunctionLabel(p.parent, parentId, p.name.asString(), listOf(), returnType, ext, typeParams, classTypeArgsIncludingOuterClasses, overridesCollectionsMethod = false, prefix = "property")
13291407
}
13301408
}
13311409

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import java.util.Map;
2+
import java.util.AbstractMap;
3+
import java.util.Collection;
4+
import java.util.AbstractCollection;
5+
import java.util.List;
6+
import java.util.AbstractList;
7+
8+
public class Test {
9+
10+
public static void test(
11+
Map<String, String> p1,
12+
AbstractMap<String, String> p2,
13+
Collection<String> p3,
14+
AbstractCollection<String> p4,
15+
List<String> p5,
16+
AbstractList<String> p6) {
17+
18+
// Use a method of each to ensure method prototypes are extracted:
19+
p1.remove("Hello");
20+
p2.remove("Hello");
21+
p3.remove("Hello");
22+
p4.remove("Hello");
23+
p5.remove("Hello");
24+
p6.remove("Hello");
25+
26+
}
27+
28+
}

0 commit comments

Comments
 (0)