Implement stub methods generation for Kotlin Immutable Collection classes.
This change is to fill the gap between Kotlin Collection classes(immutable) and Java Collection classes(mutable), to avoid calling an unsupported operation like remove() on an immutable class in jvm.
This commit is contained in:
committed by
Georgy Bronnikov
parent
8c3cef97bd
commit
afcbd76c9e
@@ -155,6 +155,22 @@ abstract class Symbols<out T : CommonBackendContext>(val context: T, private val
|
||||
|
||||
val arrays = primitiveArrays.values + unsignedArrays.values + array
|
||||
|
||||
val collection = symbolTable.referenceClass(builtIns.collection)
|
||||
val set = symbolTable.referenceClass(builtIns.set)
|
||||
val list = symbolTable.referenceClass(builtIns.list)
|
||||
val map = symbolTable.referenceClass(builtIns.map)
|
||||
val mapEntry = symbolTable.referenceClass(builtIns.mapEntry)
|
||||
val iterable = symbolTable.referenceClass(builtIns.iterable)
|
||||
val listIterator = symbolTable.referenceClass(builtIns.listIterator)
|
||||
val mutableCollection = symbolTable.referenceClass(builtIns.mutableCollection)
|
||||
val mutableSet = symbolTable.referenceClass(builtIns.mutableSet)
|
||||
val mutableList = symbolTable.referenceClass(builtIns.mutableList)
|
||||
val mutableMap = symbolTable.referenceClass(builtIns.mutableMap)
|
||||
val mutableMapEntry = symbolTable.referenceClass(builtIns.mutableMapEntry)
|
||||
val mutableIterable = symbolTable.referenceClass(builtIns.mutableIterable)
|
||||
val mutableIterator = symbolTable.referenceClass(builtIns.mutableIterator)
|
||||
val mutableListIterator = symbolTable.referenceClass(builtIns.mutableListIterator)
|
||||
|
||||
abstract val copyRangeTo: Map<ClassDescriptor, IrSimpleFunctionSymbol>
|
||||
|
||||
abstract val coroutineImpl: IrClassSymbol
|
||||
|
||||
@@ -119,3 +119,28 @@ fun IrType.substitute(substitutionMap: Map<IrTypeParameterSymbol, IrType>): IrTy
|
||||
newAnnotations
|
||||
)
|
||||
}
|
||||
|
||||
private fun getImmediateSupertypes(irClass: IrClass): List<IrSimpleType> {
|
||||
val originalSupertypes = irClass.superTypes
|
||||
val args = irClass.defaultType.arguments.mapNotNull { (it as? IrTypeProjection)?.type }
|
||||
return originalSupertypes
|
||||
.filter { it.classOrNull != null }
|
||||
.map { superType ->
|
||||
superType.substitute(superType.classOrNull!!.owner.typeParameters, args) as IrSimpleType
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectAllSupertypes(irClass: IrClass, result: MutableSet<IrSimpleType>) {
|
||||
val immediateSupertypes = getImmediateSupertypes(irClass)
|
||||
result.addAll(immediateSupertypes)
|
||||
for (supertype in immediateSupertypes) {
|
||||
supertype.classOrNull.let { collectAllSupertypes(it!!.owner, result) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllSupertypes(irClass: IrClass): MutableSet<IrSimpleType> {
|
||||
val result = HashSet<IrSimpleType>()
|
||||
|
||||
collectAllSupertypes(irClass, result)
|
||||
return result
|
||||
}
|
||||
@@ -139,6 +139,7 @@ val jvmPhases = namedIrFilePhase<JvmBackendContext>(
|
||||
objectClassPhase then
|
||||
makeInitializersPhase(JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER, true) then
|
||||
syntheticAccessorPhase then
|
||||
collectionStubMethodLowering then
|
||||
bridgePhase then
|
||||
jvmOverloadsAnnotationPhase then
|
||||
jvmStaticAnnotationPhase then
|
||||
|
||||
+5
-2
@@ -168,7 +168,7 @@ private class BridgeLowering(val context: JvmBackendContext) : ClassLoweringPass
|
||||
addBridge(
|
||||
irClass, targetForCommonBridges, method, signaturesToSkip,
|
||||
defaultValueGenerator = null,
|
||||
isSpecial = false
|
||||
isSpecial = targetForCommonBridges.isCollectionStub()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -214,7 +214,7 @@ private class BridgeLowering(val context: JvmBackendContext) : ClassLoweringPass
|
||||
isSpecial: Boolean,
|
||||
isSynthetic: Boolean
|
||||
): IrSimpleFunction {
|
||||
val modality = if (isSpecial) Modality.FINAL else Modality.OPEN
|
||||
val modality = if (isSpecial && !target.isCollectionStub()) Modality.FINAL else Modality.OPEN
|
||||
val origin = if (isSynthetic) IrDeclarationOrigin.BRIDGE else IrDeclarationOrigin.BRIDGE_SPECIAL
|
||||
|
||||
val visibility = if (signatureFunction.visibility === Visibilities.INTERNAL) Visibilities.PUBLIC else signatureFunction.visibility
|
||||
@@ -429,6 +429,9 @@ private data class SignatureWithSource(val signature: Method, val source: IrSimp
|
||||
fun IrSimpleFunction.overriddenInClasses(): Sequence<IrSimpleFunction> =
|
||||
allOverridden().filter { !(it.parent.safeAs<IrClass>()?.isInterface ?: true) }
|
||||
|
||||
fun IrSimpleFunction.isCollectionStub(): Boolean =
|
||||
origin == IrDeclarationOrigin.IR_BUILTINS_STUB
|
||||
|
||||
// TODO: At present, there is no reliable way to distinguish Java imports from Kotlin cross-module imports.
|
||||
val ORIGINS_FROM_JAVA = setOf(IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB, IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB)
|
||||
|
||||
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.jvm.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.irThrow
|
||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
import org.jetbrains.kotlin.ir.builders.irBlockBody
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.ir.builders.irString
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
internal val collectionStubMethodLowering = makeIrFilePhase(
|
||||
::CollectionStubMethodLowering,
|
||||
name = "CollectionStubMethod",
|
||||
description = "Generate Collection stub methods"
|
||||
)
|
||||
|
||||
private class CollectionStubMethodLowering(val context: JvmBackendContext) : ClassLoweringPass {
|
||||
|
||||
private val state = context.state
|
||||
|
||||
private val typeMapper = state.typeMapper
|
||||
|
||||
override fun lower(irClass: IrClass) {
|
||||
if (irClass.isInterface) {
|
||||
return
|
||||
}
|
||||
|
||||
val methodStubsToGenerate = generateRelevantStubMethods(irClass)
|
||||
|
||||
// We don't need to generate stub for existing methods, but for FAKE_OVERRIDE methods with ABSTRACT modality,
|
||||
// it means an abstract function in superclass that is not implemented yet,
|
||||
// stub generation is still needed to avoid invocation error.
|
||||
val existingMethodSignatures = irClass.functions.filterNot {
|
||||
it.modality == Modality.ABSTRACT && it.origin == IrDeclarationOrigin.FAKE_OVERRIDE
|
||||
}.associateBy { it.toSignature() }
|
||||
|
||||
for (member in methodStubsToGenerate) {
|
||||
val existingMethod = existingMethodSignatures[member.toSignature()]
|
||||
if (existingMethod != null) {
|
||||
// In the case that we find a defined method that matches the stub signature, we add the overridden symbols to that
|
||||
// defined method, so that bridge lowering can still generate correct bridge for that method
|
||||
existingMethod.overriddenSymbols.addAll(member.overriddenSymbols)
|
||||
} else {
|
||||
irClass.declarations.add(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: replace with new typeMapper using no descriptor
|
||||
private fun IrSimpleFunction.toSignature() = typeMapper.mapAsmMethod(this.descriptor).toString()
|
||||
|
||||
private fun createStubMethod(function: IrSimpleFunction, irClass: IrClass, substitutionMap: Map<IrTypeParameterSymbol, IrType>): IrSimpleFunction {
|
||||
return buildFun {
|
||||
name = function.name
|
||||
returnType = function.returnType.substitute(substitutionMap)
|
||||
visibility = function.visibility
|
||||
origin = IrDeclarationOrigin.IR_BUILTINS_STUB
|
||||
modality = Modality.OPEN
|
||||
}.apply {
|
||||
// Replace Function metadata with the data from class
|
||||
// Add the abstract function symbol to stub function for bridge lowering
|
||||
overriddenSymbols.add(function.symbol)
|
||||
parent = irClass
|
||||
dispatchReceiverParameter = function.dispatchReceiverParameter?.copyWithSubstitution(this, substitutionMap)
|
||||
extensionReceiverParameter = function.extensionReceiverParameter?.copyWithSubstitution(this, substitutionMap)
|
||||
for (parameter in function.valueParameters) {
|
||||
valueParameters.add(parameter.copyWithSubstitution(this, substitutionMap))
|
||||
}
|
||||
val exception = context.getTopLevelClass(FqName("java.lang.UnsupportedOperationException"))
|
||||
// Function body consist only of throwing UnsupportedOperationException statement
|
||||
body = context.createIrBuilder(function.symbol).irBlockBody {
|
||||
+irThrow(
|
||||
irCall(
|
||||
exception.owner.constructors.single {
|
||||
it.valueParameters.size == 1 && it.valueParameters.single().type.isNullableString()
|
||||
}
|
||||
).apply {
|
||||
putValueArgument(0, irString("Operation is not supported for read-only collection"))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy value parameter with type substitution
|
||||
private fun IrValueParameter.copyWithSubstitution(target: IrSimpleFunction, substitutionMap: Map<IrTypeParameterSymbol, IrType>)
|
||||
: IrValueParameter {
|
||||
val descriptor = WrappedValueParameterDescriptor(this.descriptor.annotations)
|
||||
return IrValueParameterImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
IrDeclarationOrigin.IR_BUILTINS_STUB,
|
||||
IrValueParameterSymbolImpl(descriptor),
|
||||
name,
|
||||
index,
|
||||
type.substitute(substitutionMap),
|
||||
varargElementType?.substitute(substitutionMap),
|
||||
isCrossinline,
|
||||
isNoinline
|
||||
).apply {
|
||||
descriptor.bind(this)
|
||||
parent = target
|
||||
}
|
||||
}
|
||||
|
||||
// Compute a substitution map for type parameters between source class (Mutable Collection classes) to
|
||||
// target class (class currently in lowering phase), this map is later used for substituting type parameters in generated functions
|
||||
private fun computeSubstitutionMap(readOnlyClass: IrClass, mutableClass: IrClass, targetClass: IrClass)
|
||||
: Map<IrTypeParameterSymbol, IrType> {
|
||||
// We find the most specific type for the immutable collection class from the inheritance chain of target class
|
||||
// Perform type substitution along searching, then use the type arguments obtained from the most specific type
|
||||
// for type substitution.
|
||||
val readOnlyClassType = getAllSupertypes(targetClass).findMostSpecificTypeForClass(readOnlyClass.symbol)
|
||||
val readOnlyClassTypeArguments = (readOnlyClassType as IrSimpleType).arguments.mapNotNull { (it as? IrTypeProjection)?.type }
|
||||
|
||||
if (readOnlyClassTypeArguments.isEmpty() || readOnlyClassTypeArguments.size != mutableClass.typeParameters.size) {
|
||||
throw IllegalStateException(
|
||||
"Type argument mismatch between immutable class ${readOnlyClass.fqNameWhenAvailable}" +
|
||||
" and mutable class ${mutableClass.fqNameWhenAvailable}, when processing" +
|
||||
"class ${targetClass.fqNameWhenAvailable}"
|
||||
)
|
||||
}
|
||||
return mutableClass.typeParameters.map { it.symbol }.zip(readOnlyClassTypeArguments).toMap()
|
||||
}
|
||||
|
||||
private data class StubsForCollectionClass(
|
||||
val readOnlyClass: IrClassSymbol,
|
||||
val mutableClass: IrClassSymbol,
|
||||
val mutableOnlyMethods: Collection<IrSimpleFunction>
|
||||
)
|
||||
|
||||
private val preComputedStubs: Collection<StubsForCollectionClass> by lazy {
|
||||
with(context.ir.symbols) {
|
||||
listOf(
|
||||
collection to mutableCollection,
|
||||
set to mutableSet,
|
||||
list to mutableList,
|
||||
map to mutableMap,
|
||||
mapEntry to mutableMapEntry,
|
||||
iterable to mutableIterable,
|
||||
iterator to mutableIterator,
|
||||
listIterator to mutableListIterator
|
||||
).map { (readOnlyClass, mutableClass) ->
|
||||
val readOnlyMethodSignatures = readOnlyClass.functions.map { it.owner.toSignature() }.toHashSet()
|
||||
val mutableMethods = mutableClass.functions
|
||||
.map { it.owner }
|
||||
.filter { it.toSignature() !in readOnlyMethodSignatures }
|
||||
.toHashSet()
|
||||
StubsForCollectionClass(readOnlyClass, mutableClass, mutableMethods)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute stubs that should be generated, compare based on signature
|
||||
private fun generateRelevantStubMethods(irClass: IrClass): Set<IrSimpleFunction> {
|
||||
val ourStubsForCollectionClasses = preComputedStubs.filter { (readOnlyClass, mutableClass) ->
|
||||
irClass.superTypes.any { supertypeSymbol ->
|
||||
val supertype = supertypeSymbol.classOrNull?.owner
|
||||
// We need to generate stub methods for following 2 cases:
|
||||
// current class's direct super type is a java class or kotlin interface, and is an subtype of an immutable collection
|
||||
supertype != null
|
||||
&& (supertype.comesFromJava() || supertype.isInterface)
|
||||
&& supertypeSymbol.isSubtypeOfClass(readOnlyClass)
|
||||
&& !irClass.symbol.isSubtypeOfClass(mutableClass)
|
||||
}
|
||||
}
|
||||
|
||||
// do a second filtering to ensure only most relevant classes are included.
|
||||
val redundantClasses = ourStubsForCollectionClasses.filter { (readOnlyClass) ->
|
||||
ourStubsForCollectionClasses.any { readOnlyClass != it.readOnlyClass && it.readOnlyClass.isSubtypeOfClass(readOnlyClass) }
|
||||
}.map { it.readOnlyClass }
|
||||
|
||||
// perform type substitution and type erasure here
|
||||
return ourStubsForCollectionClasses.filter { (readOnlyClass) ->
|
||||
readOnlyClass !in redundantClasses
|
||||
}.flatMap { (readOnlyClass, mutableClass, mutableOnlyMethods) ->
|
||||
val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass)
|
||||
mutableOnlyMethods.map { function ->
|
||||
createStubMethod(function, irClass, substitutionMap)
|
||||
}
|
||||
}.toHashSet()
|
||||
}
|
||||
|
||||
fun IrClass.comesFromJava() = origin in ORIGINS_FROM_JAVA
|
||||
|
||||
private fun Collection<IrType>.findMostSpecificTypeForClass(classifier: IrClassSymbol): IrType {
|
||||
val types = this.filter { it.classifierOrNull == classifier }
|
||||
if (types.isEmpty()) error("No supertype of $classifier in $this")
|
||||
if (types.size == 1) return types.first()
|
||||
// Find the first type in the list such that it's a subtype of every other type in that list
|
||||
return types.first { type ->
|
||||
types.all { other -> type.isSubtypeOfClass(other.classOrNull!!) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
class MyCollection<T>: Collection<T> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
class MyList<T>: List<T> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
class MyListIterator<T> : ListIterator<T> {
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
class MyList<T>(val v: T): List<T> {
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
open class Super<T>(val v: T) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
class MyMap<K, V>: Map<K, V> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
class MyMapEntry<K, V>: Map.Entry<K, V> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
class MyList: List<String> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// SKIP_JDK6
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// FULL_JDK
|
||||
// WITH_RUNTIME
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
interface Addable {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
open class SetStringImpl {
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// WITH_RUNTIME
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// SKIP_JDK6
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// FULL_JDK
|
||||
// WITH_RUNTIME
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
class MyCollection<T> : Collection<List<Iterator<T>>> {
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// FILE: Test.java
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// FILE: B.java
|
||||
|
||||
Reference in New Issue
Block a user