All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.backend.jvm.lower.CollectionStubMethodLowering.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * 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.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.isFromJava
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.declarations.buildValueParameter
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.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.AbstractTypeCheckerContext
import org.jetbrains.kotlin.utils.addToStdlib.cast

internal val collectionStubMethodLowering = makeIrFilePhase(
    ::CollectionStubMethodLowering,
    name = "CollectionStubMethod",
    description = "Generate Collection stub methods"
)

internal class CollectionStubMethodLowering(val context: JvmBackendContext) : ClassLoweringPass {
    private val collectionStubComputer = context.collectionStubComputer

    private data class NameAndArity(
        val name: Name,
        val typeParametersCount: Int,
        val valueParametersCount: Int
    )

    private val IrSimpleFunction.nameAndArity
        get() = NameAndArity(name, typeParameters.size, valueParameters.size)

    override fun lower(irClass: IrClass) {
        if (irClass.isInterface) {
            return
        }

        val methodStubsToGenerate = generateRelevantStubMethods(irClass)
        if (methodStubsToGenerate.isEmpty()) return

        // 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 (abstractMethods, nonAbstractMethods) = irClass.functions.partition { it.modality == Modality.ABSTRACT && it.isFakeOverride }
        val nonAbstractMethodsByNameAndArity = nonAbstractMethods.groupBy { it.nameAndArity }
        val abstractMethodsByNameAndArity = abstractMethods.groupBy { it.nameAndArity }

        for (stub in methodStubsToGenerate) {
            val stubNameAndArity = stub.nameAndArity
            val relevantMembers = nonAbstractMethodsByNameAndArity[stubNameAndArity].orEmpty()
            val existingOverrides = relevantMembers.filter { isEffectivelyOverriddenBy(stub, it) }

            if (existingOverrides.isNotEmpty()) {
                existingOverrides.forEach {
                    // 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.
                    // However, we still need to keep track of the original overrides
                    // so that special built-in signature mapping doesn't confuse it with a method
                    // that actually requires signature patching.
                    context.recordOverridesWithoutStubs(it)
                    it.overriddenSymbols += stub.overriddenSymbols
                }
                // We don't add a throwing stub if it's effectively overridden by an existing function.
                continue
            }

            // Generated stub might still override some abstract member(s), which affects resulting method signature.
            val overriddenAbstractMethods = abstractMethodsByNameAndArity[stubNameAndArity].orEmpty()
                .filter { isEffectivelyOverriddenBy(it, stub) }
            stub.overriddenSymbols += overriddenAbstractMethods.map { it.symbol }

            // Some stub members require special handling.
            // In both 'remove' and 'removeAt' cases there are no other member functions with same name in built-in mutable collection
            // classes, so it's safe to check for the member name itself.
            when (stub.name.asString()) {
                "remove" -> {
                    //  - 'remove' member functions:
                    //          kotlin.collections.MutableCollection#remove(E): Boolean
                    //          kotlin.collections.MutableMap#remove(K): V?
                    //      We've checked that corresponding 'remove(T)' member function is not present in the class.
                    //      We should add a member function that overrides, respectively:
                    //          java.util.Collection#remove(Object): boolean
                    //          java.util.Map#remove(K): V
                    //      This corresponds to replacing value parameter types with 'Any?'.
                    irClass.declarations.add(stub.apply {
                        valueParameters = valueParameters.map {
                            it.copyWithCustomTypeSubstitution(this) { context.irBuiltIns.anyNType }
                        }
                    })
                }
                "removeAt" -> {
                    //  - 'removeAt' member function:
                    //          kotlin.collections.MutableList#removeAt(Int): E
                    //      We've checked that corresponding 'removeAt(Int)' member function is not present in the class
                    //      (if it IS present, special bridges for 'remove(I)' would be generated later in BridgeLowering).
                    //      We can't add 'removeAt' here, because it would be different from what old back-end generates
                    //      and can break existing Java and/or Kotlin code.
                    //      We should add a member function that overrides
                    //          java.util.List#remove(int): E
                    //      and throws UnsupportedOperationException, just like any other stub.
                    //      Also, we should generate a bridge for it if required.
                    val removeIntFun = createRemoveAtStub(stub, stub.returnType, IrDeclarationOrigin.IR_BUILTINS_STUB)
                    irClass.declarations.add(removeIntFun)
                    val removeIntBridgeFun = createRemoveAtStub(stub, context.irBuiltIns.anyNType, IrDeclarationOrigin.BRIDGE)
                    if (removeIntBridgeFun.toJvmSignature() != removeIntFun.toJvmSignature()) {
                        irClass.declarations.add(removeIntBridgeFun)
                    }
                }
                else ->
                    irClass.declarations.add(stub)
            }
        }
    }

    private fun createRemoveAtStub(
        removeAtStub: IrSimpleFunction,
        stubReturnType: IrType,
        stubOrigin: IrDeclarationOrigin
    ): IrSimpleFunction {
        return context.irFactory.buildFun {
            name = Name.identifier("remove")
            returnType = stubReturnType
            visibility = removeAtStub.visibility
            origin = stubOrigin
            modality = Modality.OPEN
        }.apply {
            // NB stub method for 'remove(int)' doesn't override any built-in Kotlin declaration
            parent = removeAtStub.parent
            dispatchReceiverParameter = removeAtStub.dispatchReceiverParameter?.copyWithCustomTypeSubstitution(this) { it }
            extensionReceiverParameter = null
            valueParameters = removeAtStub.valueParameters.map { stubParameter ->
                stubParameter.copyWithCustomTypeSubstitution(this) { it }
            }
            body = createThrowingStubBody(this)
        }
    }

    private fun IrSimpleFunction.toJvmSignature(): String =
        context.methodSignatureMapper.mapAsmMethod(this).toString()

    private fun createStubMethod(
        function: IrSimpleFunction,
        irClass: IrClass,
        substitutionMap: Map
    ): IrSimpleFunction {
        return context.irFactory.buildFun {
            name = function.name
            returnType = liftStubMethodReturnType(function).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 = listOf(function.symbol)
            parent = irClass
            dispatchReceiverParameter = function.dispatchReceiverParameter?.copyWithSubstitution(this, substitutionMap)
            extensionReceiverParameter = function.extensionReceiverParameter?.copyWithSubstitution(this, substitutionMap)
            valueParameters = function.valueParameters.map { it.copyWithSubstitution(this, substitutionMap) }
            body = createThrowingStubBody(this)
        }
    }

    private fun liftStubMethodReturnType(function: IrSimpleFunction) =
        when (function.name.asString()) {
            "iterator" ->
                context.ir.symbols.iterator.typeWithArguments(function.returnType.cast().arguments)
            "listIterator" ->
                context.ir.symbols.listIterator.typeWithArguments(function.returnType.cast().arguments)
            "subList" ->
                context.ir.symbols.list.typeWithArguments(function.returnType.cast().arguments)
            else ->
                function.returnType
        }

    private fun createThrowingStubBody(function: IrSimpleFunction) =
        context.createIrBuilder(function.symbol).irBlockBody {
            // Function body consist only of throwing UnsupportedOperationException statement
            +irCall(this@CollectionStubMethodLowering.context.ir.symbols.throwUnsupportedOperationException)
                .apply {
                    putValueArgument(0, irString("Operation is not supported for read-only collection"))
                }
        }

    private fun isEffectivelyOverriddenBy(superFun: IrSimpleFunction, overridingFun: IrSimpleFunction): Boolean {
        // Function 'f0' is overridden by function 'f1' if all of the following conditions are met,
        // assuming type parameter Ti of 'f1' is "equal" to type parameter Si of 'f0':
        //  - names are same;
        //  - 'f1' has the same number of type parameters,
        //    and upper bounds for type parameters are equivalent;
        //  - 'f1' has the same number of value parameters,
        //    and types for value parameters are equivalent;
        //  - 'f1' return type is a subtype of 'f0' return type.

        if (superFun.name != overridingFun.name) return false
        if (superFun.typeParameters.size != overridingFun.typeParameters.size) return false
        if (superFun.valueParameters.size != overridingFun.valueParameters.size) return false

        val typeChecker = createTypeChecker(superFun, overridingFun)

        // Note that type parameters equivalence check doesn't really happen on collection stubs
        // (because members of Kotlin built-in collection classes don't have type parameters of their own),
        // but we keep it here for the sake of consistency.
        if (!areTypeParametersEquivalent(overridingFun, superFun, typeChecker)) return false

        if (!areValueParametersEquivalent(overridingFun, superFun, typeChecker)) return false
        if (!isReturnTypeOverrideCompliant(overridingFun, superFun, typeChecker)) return false

        return true
    }

    private fun createTypeChecker(overrideFun: IrSimpleFunction, parentFun: IrSimpleFunction): AbstractTypeCheckerContext =
        IrTypeCheckerContextWithAdditionalAxioms(context.irBuiltIns, overrideFun.typeParameters, parentFun.typeParameters)

    private fun areTypeParametersEquivalent(
        overrideFun: IrSimpleFunction,
        parentFun: IrSimpleFunction,
        typeChecker: AbstractTypeCheckerContext
    ): Boolean =
        overrideFun.typeParameters.zip(parentFun.typeParameters)
            .all { (typeParameter1, typeParameter2) ->
                typeParameter1.superTypes.zip(typeParameter2.superTypes)
                    .all { (supertype1, supertype2) ->
                        AbstractTypeChecker.equalTypes(typeChecker, supertype1, supertype2)
                    }
            }

    private fun areValueParametersEquivalent(
        overrideFun: IrSimpleFunction,
        parentFun: IrSimpleFunction,
        typeChecker: AbstractTypeCheckerContext
    ): Boolean =
        overrideFun.valueParameters.zip(parentFun.valueParameters)
            .all { (valueParameter1, valueParameter2) ->
                AbstractTypeChecker.equalTypes(typeChecker, valueParameter1.type, valueParameter2.type)
            }

    internal fun isReturnTypeOverrideCompliant(
        overrideFun: IrSimpleFunction,
        parentFun: IrSimpleFunction,
        typeChecker: AbstractTypeCheckerContext
    ): Boolean =
        AbstractTypeChecker.isSubtypeOf(typeChecker, overrideFun.returnType, parentFun.returnType)

    // Copy value parameter with type substitution
    private fun IrValueParameter.copyWithSubstitution(
        target: IrSimpleFunction,
        substitutionMap: Map
    ): IrValueParameter =
        copyWithCustomTypeSubstitution(target) { it.substitute(substitutionMap) }

    private fun IrValueParameter.copyWithCustomTypeSubstitution(
        target: IrSimpleFunction,
        substituteType: (IrType) -> IrType
    ): IrValueParameter {
        val parameter = this
        return buildValueParameter(target) {
            origin = IrDeclarationOrigin.IR_BUILTINS_STUB
            name = parameter.name
            index = parameter.index
            type = substituteType(parameter.type)
            varargElementType = parameter.varargElementType?.let { substituteType(it) }
            isCrossInline = parameter.isCrossinline
            isNoinline = parameter.isNoinline
        }
    }

    // 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 {
        // 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 = getAllSubstitutedSupertypes(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()
    }

    // Compute stubs that should be generated, compare based on signature
    private fun generateRelevantStubMethods(irClass: IrClass): List {
        val classStubFuns = collectionStubComputer.stubsForCollectionClasses(irClass)
            .flatMap { createStubFuns(irClass, it) }

        val superClassStubSignatures = computeStubsForSuperClasses(irClass)
            .flatMap { createStubFuns(irClass, it) }
            .mapTo(HashSet()) { it.toJvmSignature() }

        return classStubFuns.filter { it.toJvmSignature() !in superClassStubSignatures }
    }

    private fun createStubFuns(irClass: IrClass, stubs: StubsForCollectionClass): List {
        val (readOnlyClass, mutableClass, candidatesForStubs) = stubs
        val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass)
        return candidatesForStubs.map { function ->
            createStubMethod(function, irClass, substitutionMap)
        }
    }

    private fun computeStubsForSuperClasses(irClass: IrClass): List {
        val immediateSuperClass = irClass.superClass ?: return emptyList()
        return immediateSuperClass.superClassChain
            .flatMap { superClass -> computeStubsForSuperClass(superClass) }
            .toList()
    }

    private class FilteredStubsForCollectionClass(
        override val readOnlyClass: IrClassSymbol,
        override val mutableClass: IrClassSymbol,
        override val candidatesForStubs: Collection
    ) : StubsForCollectionClass

    private fun computeStubsForSuperClass(superClass: IrClass): List {
        val superClassStubs = collectionStubComputer.stubsForCollectionClasses(superClass)

        if (superClass.modality != Modality.ABSTRACT) return superClassStubs

        // An abstract superclass might contain an abstract function declaration that effectively overrides a stub to be generated,
        // and thus have no non-abstract stub.
        // This calculation happens for each abstract class multiple times. TODO memoize.

        val abstractFunsByNameAndArity = superClass.functions
            .filter { !it.isFakeOverride && it.modality == Modality.ABSTRACT }
            .groupBy { it.nameAndArity }

        if (abstractFunsByNameAndArity.isEmpty()) return superClassStubs

        return superClassStubs.map {
            val (readOnlyClass, mutableClass, candidatesForStubs) = it
            // NB here we should build a stub in substitution context of the given superclass.
            // Resulting stub can be different from a stub created in substitution context of the "current" class
            // in case of (partially) specialized generic superclass.
            val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, superClass)
            val filteredCandidates = candidatesForStubs.filter { candidateFun ->
                val stubMethod = createStubMethod(candidateFun, superClass, substitutionMap)
                val stubNameAndArity = stubMethod.nameAndArity
                abstractFunsByNameAndArity[stubNameAndArity].orEmpty().none { abstractFun ->
                    isEffectivelyOverriddenBy(stubMethod, abstractFun)
                }
            }
            FilteredStubsForCollectionClass(readOnlyClass, mutableClass, filteredCandidates)
        }
    }

    private fun Collection.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!!) }
        }
    }

    private val IrClass.superClass: IrClass?
        get() = superTypes.mapNotNull { it.getClass() }.singleOrNull { !it.isJvmInterface }

    private val IrClass.superClassChain: Sequence
        get() = generateSequence(this) { it.superClass }
}

internal interface StubsForCollectionClass {
    val readOnlyClass: IrClassSymbol
    val mutableClass: IrClassSymbol
    val candidatesForStubs: Collection
}

internal operator fun StubsForCollectionClass.component1() = readOnlyClass
internal operator fun StubsForCollectionClass.component2() = mutableClass
internal operator fun StubsForCollectionClass.component3() = candidatesForStubs

internal class CollectionStubComputer(val context: JvmBackendContext) {

    private class LazyStubsForCollectionClass(
        override val readOnlyClass: IrClassSymbol,
        override val mutableClass: IrClassSymbol
    ) : StubsForCollectionClass {
        override val candidatesForStubs: Collection by lazy {
            // Old back-end generates stubs for 'class A : C', where
            //  'C' is some "read-only collection" interface from kotlin.collections,
            //  'MC' is a corresponding "mutable collection" interface from kotlin.collections,
            // by building fake overrides for a special 'class X : A(), MC'
            // and taking fake overrides that override members from 'MC'.
            //
            // Here we are looking at this problem from a slightly different angle:
            // we select suitable member functions 'f' in 'MC' that might potentially require stubs (we are here!),
            // and then we generate stubs for functions 'f' that are not effectively overridden by members of 'A'
            // (this happens in the lowering itself).
            //
            // In order for this to be equivalent to the old back-end approach,
            // we should take 'f' in 'MC' such that any of the following conditions is true:
            //  - 'f' is declared in 'MC' - that is, 'f' itself is not a fake override;
            //  - 'f' is abstract and doesn't override anything from 'C'.
            //
            // NB1 it also covers default methods from JDK collection classes case
            // (since that's the only way a member function in 'MC' might be non-abstract).
            //
            // NB2 the scheme of stub method generation in the old back-end depends too much on
            // which particular declarations are present in 'MC'.
            // Some of these declarations are redundant from the stub generation point of view -
            // for example, 'kotlin.collections.MutableListIterator' contains the following (redundant) declarations:
            //      override fun next(): T
            //      override fun hasNext(): Boolean
            // which cause stubs for 'next' and 'hasNext' to be generated in a 'abstract class A : ListIterator'.
            // See https://youtrack.jetbrains.com/issue/KT-36724.
            // In the ideal world, it should be enough to check that
            // the given member function 'f' from 'MC' doesn't override anything from 'C'.

            mutableClass.functions
                .map { it.owner }
                .filter { memberFun ->
                    !memberFun.isFakeOverride ||
                            (memberFun.modality == Modality.ABSTRACT && memberFun.overriddenSymbols.none { overriddenFun ->
                                overriddenFun.owner.parentAsClass.symbol == readOnlyClass
                            })
                }
                .toHashSet()
        }
    }

    private val preComputedStubs: Collection by lazy {
        with(context.ir.symbols) {
            listOf(
                LazyStubsForCollectionClass(collection, mutableCollection),
                LazyStubsForCollectionClass(set, mutableSet),
                LazyStubsForCollectionClass(list, mutableList),
                LazyStubsForCollectionClass(map, mutableMap),
                LazyStubsForCollectionClass(mapEntry, mutableMapEntry),
                LazyStubsForCollectionClass(iterable, mutableIterable),
                LazyStubsForCollectionClass(iterator, mutableIterator),
                LazyStubsForCollectionClass(listIterator, mutableListIterator)
            )
        }
    }

    private val stubsCache = mutableMapOf>()

    fun stubsForCollectionClasses(irClass: IrClass): List =
        stubsCache.getOrPut(irClass) {
            computeStubsForCollectionClasses(irClass)
        }

    private fun computeStubsForCollectionClasses(irClass: IrClass): List {
        if (irClass.isFromJava()) return emptyList()
        val stubs = preComputedStubs.filter { (readOnlyClass, mutableClass) ->
            !irClass.symbol.isSubtypeOfClass(mutableClass) &&
                    irClass.superTypes.any { it.isSubtypeOfClass(readOnlyClass) }
        }
        return stubs.filter { (readOnlyClass) ->
            stubs.none { readOnlyClass != it.readOnlyClass && it.readOnlyClass.isSubtypeOfClass(readOnlyClass) }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy