blob: 241fee54ec4f40d4a2613c4181abc972fc11090a [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.compiler.plugins.kotlin.lower
import androidx.compose.compiler.plugins.kotlin.hasComposableAnnotation
import androidx.compose.compiler.plugins.kotlin.isComposableAnnotation
import androidx.compose.compiler.plugins.kotlin.lower.decoys.isDecoy
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContextImpl
import org.jetbrains.kotlin.backend.common.peek
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrTypeParametersContainer
import org.jetbrains.kotlin.ir.declarations.copyAttributes
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.kotlin.ir.expressions.IrWhen
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrIfThenElseImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeAbbreviation
import org.jetbrains.kotlin.ir.types.IrTypeArgument
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.impl.IrTypeAbbreviationImpl
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.types.typeOrNull
import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper
import org.jetbrains.kotlin.ir.util.SymbolRemapper
import org.jetbrains.kotlin.ir.util.SymbolRenamer
import org.jetbrains.kotlin.ir.util.TypeRemapper
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.isFunction
import org.jetbrains.kotlin.ir.util.packageFqName
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.ir.util.remapTypes
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.Variance
internal class DeepCopyIrTreeWithRemappedComposableTypes(
private val context: IrPluginContext,
private val symbolRemapper: DeepCopySymbolRemapper,
private val typeRemapper: TypeRemapper,
symbolRenamer: SymbolRenamer = SymbolRenamer.DEFAULT
) : DeepCopyPreservingMetadata(symbolRemapper, typeRemapper, symbolRenamer) {
override fun visitSimpleFunction(declaration: IrSimpleFunction): IrSimpleFunction {
if (declaration.symbol.isRemappedAndBound()) {
return symbolRemapper.getReferencedSimpleFunction(declaration.symbol).owner
}
if (declaration.symbol.isBoundButNotRemapped()) {
symbolRemapper.visitSimpleFunction(declaration)
}
return super.visitSimpleFunction(declaration).also {
it.overriddenSymbols.forEach { symbol ->
if (!symbol.isBound) {
// symbol will be rebound by deep copy on later iteration
return@forEach
}
val overriddenFn = symbol.owner
if (overriddenFn.origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB) {
// this is external function that is in a different compilation unit,
// so we potentially need to update composable types for it.
// if the function is in the current module, it should be updated eventually
// by this deep copy pass.
if (overriddenFn.needsComposableRemapping() && !overriddenFn.isDecoy()) {
overriddenFn.remapTypes(typeRemapper)
}
}
}
it.correspondingPropertySymbol = declaration.correspondingPropertySymbol
}
}
override fun visitProperty(declaration: IrProperty): IrProperty {
return super.visitProperty(declaration).also {
it.copyAttributes(declaration)
}
}
override fun visitFile(declaration: IrFile): IrFile {
includeFileNameInExceptionTrace(declaration) {
return super.visitFile(declaration)
}
}
override fun visitWhen(expression: IrWhen): IrWhen {
if (expression is IrIfThenElseImpl) {
return IrIfThenElseImpl(
expression.startOffset,
expression.endOffset,
expression.type.remapType(),
mapStatementOrigin(expression.origin),
).also {
expression.branches.mapTo(it.branches) { branch ->
branch.transform()
}
}.copyAttributes(expression)
}
return super.visitWhen(expression)
}
override fun visitConstructorCall(expression: IrConstructorCall): IrConstructorCall {
if (!expression.symbol.isBound)
(context as IrPluginContextImpl).linker.getDeclaration(expression.symbol)
val ownerFn = expression.symbol.owner as? IrConstructor
// If we are calling an external constructor, we want to "remap" the types of its signature
// as well, since if it they are @Composable it will have its unmodified signature. These
// types won't be traversed by default by the DeepCopyIrTreeWithSymbols so we have to
// do it ourself here.
if (
ownerFn != null &&
ownerFn.origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB &&
ownerFn.needsComposableRemapping()
) {
if (symbolRemapper.getReferencedConstructor(ownerFn.symbol) == ownerFn.symbol) {
// Not remapped yet, so remap now.
// Remap only once to avoid IdSignature clash (on k/js 1.7.20).
symbolRemapper.visitConstructor(ownerFn)
super.visitConstructor(ownerFn).also {
it.patchDeclarationParents(ownerFn.parent)
}
}
val newCallee = symbolRemapper.getReferencedConstructor(ownerFn.symbol)
return IrConstructorCallImpl(
expression.startOffset, expression.endOffset,
expression.type.remapType(),
newCallee,
expression.typeArgumentsCount,
expression.constructorTypeArgumentsCount,
expression.valueArgumentsCount,
mapStatementOrigin(expression.origin)
).apply {
copyRemappedTypeArgumentsFrom(expression)
transformValueArguments(expression)
}.copyAttributes(expression)
}
return super.visitConstructorCall(expression)
}
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrTypeOperatorCall {
if (expression.operator != IrTypeOperator.SAM_CONVERSION) {
return super.visitTypeOperator(expression)
}
/*
* SAM_CONVERSION types from IR stubs are not remapped normally, as the fun interface is
* technically not a function type. This part goes over types involved in SAM_CONVERSION and
* ensures that parameter/return types of IR stubs are remapped correctly.
* Classes extending fun interfaces with composable types will be processed by visitFunction
* above as normal.
*/
val type = expression.typeOperand
val clsSymbol = type.classOrNull ?: return super.visitTypeOperator(expression)
// Unbound symbols indicate they are in the current module and have not been
// processed by copier yet.
if (
clsSymbol.isBound &&
clsSymbol.owner.origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB &&
// Only process fun interfaces with @Composable types
clsSymbol.owner.isFun &&
clsSymbol.functions.any { it.owner.needsComposableRemapping() }
) {
// We always assume the current subtree has not been copied yet.
// If the old symbol is the same as in remapper, it means we never reached it, so
// we have to remap it now.
if (clsSymbol == symbolRemapper.getReferencedClass(clsSymbol)) {
symbolRemapper.visitClass(clsSymbol.owner)
clsSymbol.owner.transform().also {
it.patchDeclarationParents(clsSymbol.owner.parent)
}
}
}
return super.visitTypeOperator(expression)
}
private fun IrFunction.needsComposableRemapping(): Boolean {
if (
needsComposableRemapping(dispatchReceiverParameter?.type) ||
needsComposableRemapping(extensionReceiverParameter?.type) ||
needsComposableRemapping(returnType)
) return true
for (param in valueParameters) {
if (needsComposableRemapping(param.type)) return true
}
return false
}
private fun needsComposableRemapping(type: IrType?): Boolean {
if (type == null) return false
if (type !is IrSimpleType) return false
if (type.hasComposableAnnotation()) return true
if (type.arguments.any { needsComposableRemapping(it.typeOrNull) }) return true
return false
}
override fun visitDelegatingConstructorCall(
expression: IrDelegatingConstructorCall
): IrDelegatingConstructorCall {
val owner = expression.symbol.owner
// If we are calling an external constructor, we want to "remap" the types of its signature
// as well, since if it they are @Composable it will have its unmodified signature. These
// types won't be traversed by default by the DeepCopyIrTreeWithSymbols so we have to
// do it ourself here.
if (
owner.origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB &&
owner.needsComposableRemapping() &&
symbolRemapper.getReferencedConstructor(expression.symbol) == owner.symbol
) {
symbolRemapper.visitConstructor(owner)
visitConstructor(owner).patchDeclarationParents(owner.parent)
}
return super.visitDelegatingConstructorCall(expression)
}
override fun visitCall(expression: IrCall): IrCall {
val ownerFn = expression.symbol.owner as? IrSimpleFunction
val containingClass = ownerFn?.parentClassOrNull
// Any virtual calls on composable functions we want to make sure we update the call to
// the right function base class (of n+1 arity). The most often virtual call to make on
// a function instance is `invoke`, which we *already* do in the ComposeParamTransformer.
// There are others that can happen though as well, such as `equals` and `hashCode`. In this
// case, we want to update those calls as well.
if (
containingClass != null &&
ownerFn.origin == IrDeclarationOrigin.FAKE_OVERRIDE && (
// Fake override refers to composable if container is synthetic composable (K2)
// or function type is composable (K1)
containingClass.defaultType.isSyntheticComposableFunction() || (
containingClass.defaultType.isFunction() &&
expression.dispatchReceiver?.type?.hasComposableAnnotation() == true
)
)
) {
val realParams = containingClass.typeParameters.size - 1
// with composer and changed
val newArgsSize = realParams + 1 + changedParamCount(realParams, 0)
val newFnClass = context.function(newArgsSize).owner
var newFn = newFnClass
.functions
.first { it.name == ownerFn.name }
if (symbolRemapper.getReferencedSimpleFunction(newFn.symbol) == newFn.symbol) {
// Not remapped yet, so remap now.
// Remap only once to avoid IdSignature clash (on k/js 1.7.20).
symbolRemapper.visitSimpleFunction(newFn)
newFn = super.visitSimpleFunction(newFn).also { fn ->
fn.overriddenSymbols = ownerFn.overriddenSymbols.map { it }
fn.dispatchReceiverParameter = ownerFn.dispatchReceiverParameter
fn.extensionReceiverParameter = ownerFn.extensionReceiverParameter
newFn.valueParameters.forEach { p ->
fn.addValueParameter(p.name.identifier, p.type)
}
fn.patchDeclarationParents(newFnClass)
assert(fn.body == null) { "expected body to be null" }
}
}
val newCallee = symbolRemapper.getReferencedSimpleFunction(newFn.symbol)
return shallowCopyCall(expression, newCallee).apply {
copyRemappedTypeArgumentsFrom(expression)
transformValueArguments(expression)
}
}
// If we are calling an external function, we want to "remap" the types of its signature
// as well, since if it is @Composable it will have its unmodified signature. These
// functions won't be traversed by default by the DeepCopyIrTreeWithSymbols so we have to
// do it ourself here.
//
// When an external declaration for a property getter/setter is transformed, we need to
// also transform the corresponding property so that we maintain the relationship
// `getterFun.correspondingPropertySymbol.owner.getter == getterFun`. If we do not
// maintain this relationship inline class getters will be incorrectly compiled.
if (
ownerFn != null &&
ownerFn.origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB
) {
if (ownerFn.correspondingPropertySymbol != null) {
val property = ownerFn.correspondingPropertySymbol!!.owner
// avoid java properties since they go through a different lowering and it is
// also impossible for them to have composable types
if (property.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB &&
property.getter?.needsComposableRemapping() == true
) {
if (symbolRemapper.getReferencedProperty(property.symbol) == property.symbol) {
// Not remapped yet, so remap now.
// Remap only once to avoid IdSignature clash (on k/js 1.7.20).
symbolRemapper.visitProperty(property)
visitProperty(property).also {
it.getter?.correspondingPropertySymbol = it.symbol
it.setter?.correspondingPropertySymbol = it.symbol
it.patchDeclarationParents(ownerFn.parent)
it.copyAttributes(property)
}
}
}
} else if (ownerFn.needsComposableRemapping()) {
if (symbolRemapper.getReferencedSimpleFunction(ownerFn.symbol) == ownerFn.symbol) {
// Not remapped yet, so remap now.
// Remap only once to avoid IdSignature clash (on k/js 1.7.20).
symbolRemapper.visitSimpleFunction(ownerFn)
visitSimpleFunction(ownerFn).also {
it.correspondingPropertySymbol = null
it.patchDeclarationParents(ownerFn.parent)
}
}
}
val newCallee = symbolRemapper.getReferencedSimpleFunction(ownerFn.symbol)
return shallowCopyCall(expression, newCallee).apply {
copyRemappedTypeArgumentsFrom(expression)
transformValueArguments(expression)
}
}
if (
ownerFn != null &&
ownerFn.needsComposableRemapping()
) {
if (symbolRemapper.getReferencedSimpleFunction(ownerFn.symbol) == ownerFn.symbol) {
visitSimpleFunction(ownerFn).also {
it.overriddenSymbols = ownerFn.overriddenSymbols.map { override ->
if (override.isBound) {
visitSimpleFunction(override.owner).apply {
patchDeclarationParents(override.owner.parent)
}.symbol
} else {
override
}
}
it.patchDeclarationParents(ownerFn.parent)
}
}
val newCallee = symbolRemapper.getReferencedSimpleFunction(ownerFn.symbol)
return shallowCopyCall(expression, newCallee).apply {
copyRemappedTypeArgumentsFrom(expression)
transformValueArguments(expression)
}
}
return super.visitCall(expression)
}
private fun IrSimpleFunctionSymbol.isBoundButNotRemapped(): Boolean {
return this.isBound && symbolRemapper.getReferencedFunction(this) == this
}
private fun IrSimpleFunctionSymbol.isRemappedAndBound(): Boolean {
val symbol = symbolRemapper.getReferencedFunction(this)
return symbol.isBound && symbol != this
}
/* copied verbatim from DeepCopyIrTreeWithSymbols, except with newCallee as a parameter */
private fun shallowCopyCall(expression: IrCall, newCallee: IrSimpleFunctionSymbol): IrCall {
return IrCallImpl(
expression.startOffset, expression.endOffset,
expression.type.remapType(),
newCallee,
expression.typeArgumentsCount,
expression.valueArgumentsCount,
mapStatementOrigin(expression.origin),
symbolRemapper.getReferencedClassOrNull(expression.superQualifierSymbol)
).apply {
copyRemappedTypeArgumentsFrom(expression)
}.copyAttributes(expression)
}
/* copied verbatim from DeepCopyIrTreeWithSymbols */
private fun IrMemberAccessExpression<*>.copyRemappedTypeArgumentsFrom(
other: IrMemberAccessExpression<*>
) {
assert(typeArgumentsCount == other.typeArgumentsCount) {
"Mismatching type arguments: $typeArgumentsCount vs ${other.typeArgumentsCount} "
}
for (i in 0 until typeArgumentsCount) {
putTypeArgument(i, other.getTypeArgument(i)?.remapType())
}
}
/* copied verbatim from DeepCopyIrTreeWithSymbols */
private fun <T : IrMemberAccessExpression<*>> T.transformValueArguments(original: T) {
transformReceiverArguments(original)
for (i in 0 until original.valueArgumentsCount) {
putValueArgument(i, original.getValueArgument(i)?.transform())
}
}
/* copied verbatim from DeepCopyIrTreeWithSymbols */
private fun <T : IrMemberAccessExpression<*>> T.transformReceiverArguments(original: T): T =
apply {
dispatchReceiver = original.dispatchReceiver?.transform()
extensionReceiver = original.extensionReceiver?.transform()
}
}
class ComposerTypeRemapper(
private val context: IrPluginContext,
private val symbolRemapper: SymbolRemapper,
private val composerType: IrType
) : TypeRemapper {
lateinit var deepCopy: IrElementTransformerVoid
private val scopeStack = mutableListOf<IrTypeParametersContainer>()
override fun enterScope(irTypeParametersContainer: IrTypeParametersContainer) {
scopeStack.add(irTypeParametersContainer)
}
override fun leaveScope() {
scopeStack.pop()
}
private fun IrType.isFunction(): Boolean {
val cls = classOrNull ?: return false
val name = cls.owner.name.asString()
if (!name.startsWith("Function")) return false
val packageFqName = cls.owner.packageFqName
return packageFqName == StandardNames.BUILT_INS_PACKAGE_FQ_NAME ||
packageFqName == KotlinFunctionsBuiltInsPackageFqName
}
private fun IrType.isComposableFunction(): Boolean {
return isSyntheticComposableFunction() || (isFunction() && hasComposableAnnotation())
}
override fun remapType(type: IrType): IrType {
if (type !is IrSimpleType) return type
if (!type.isComposableFunction()) return underlyingRemapType(type)
// do not convert types for decoys
if (scopeStack.peek()?.isDecoy() == true) {
return underlyingRemapType(type)
}
val oldIrArguments = type.arguments
val realParams = oldIrArguments.size - 1
var extraArgs = listOf(
// composer param
makeTypeProjection(
composerType,
Variance.INVARIANT
)
)
val changedParams = changedParamCount(realParams, 1)
extraArgs = extraArgs + (0 until changedParams).map {
makeTypeProjection(context.irBuiltIns.intType, Variance.INVARIANT)
}
val newIrArguments =
oldIrArguments.subList(0, oldIrArguments.size - 1) +
extraArgs +
oldIrArguments.last()
val newArgSize = oldIrArguments.size - 1 + extraArgs.size
val functionCls = context.function(newArgSize)
return IrSimpleTypeImpl(
null,
functionCls,
type.nullability,
newIrArguments.map { remapTypeArgument(it) },
type.annotations.filter { !it.isComposableAnnotation() }.map {
it.transform(deepCopy, null) as IrConstructorCall
},
null
)
}
private fun underlyingRemapType(type: IrSimpleType): IrType {
return IrSimpleTypeImpl(
null,
symbolRemapper.getReferencedClassifier(type.classifier),
type.nullability,
type.arguments.map { remapTypeArgument(it) },
type.annotations.map { it.transform(deepCopy, null) as IrConstructorCall },
type.abbreviation?.remapTypeAbbreviation()
)
}
private fun remapTypeArgument(typeArgument: IrTypeArgument): IrTypeArgument =
if (typeArgument is IrTypeProjection)
makeTypeProjection(this.remapType(typeArgument.type), typeArgument.variance)
else
typeArgument
private fun IrTypeAbbreviation.remapTypeAbbreviation() =
IrTypeAbbreviationImpl(
symbolRemapper.getReferencedTypeAlias(typeAlias),
hasQuestionMark,
arguments.map { remapTypeArgument(it) },
annotations
)
}
private val KotlinFunctionsBuiltInsPackageFqName = StandardNames.BUILT_INS_PACKAGE_FQ_NAME
.child(Name.identifier("jvm"))
.child(Name.identifier("functions"))