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

androidx.compose.compiler.plugins.kotlin.lower.ComposableTargetAnnotationsTransformer.kt Maven / Gradle / Ivy

/*
 * Copyright 2021 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.ComposeClassIds
import androidx.compose.compiler.plugins.kotlin.ComposeFqNames
import androidx.compose.compiler.plugins.kotlin.KtxNameConventions
import androidx.compose.compiler.plugins.kotlin.ModuleMetrics
import androidx.compose.compiler.plugins.kotlin.analysis.ComposeWritableSlices
import androidx.compose.compiler.plugins.kotlin.analysis.StabilityInferencer
import androidx.compose.compiler.plugins.kotlin.inference.ApplierInferencer
import androidx.compose.compiler.plugins.kotlin.inference.ErrorReporter
import androidx.compose.compiler.plugins.kotlin.inference.Item
import androidx.compose.compiler.plugins.kotlin.inference.LazyScheme
import androidx.compose.compiler.plugins.kotlin.inference.LazySchemeStorage
import androidx.compose.compiler.plugins.kotlin.inference.NodeAdapter
import androidx.compose.compiler.plugins.kotlin.inference.NodeKind
import androidx.compose.compiler.plugins.kotlin.inference.Open
import androidx.compose.compiler.plugins.kotlin.inference.Scheme
import androidx.compose.compiler.plugins.kotlin.inference.Token
import androidx.compose.compiler.plugins.kotlin.inference.TypeAdapter
import androidx.compose.compiler.plugins.kotlin.inference.deserializeScheme
import androidx.compose.compiler.plugins.kotlin.inference.mergeWith
import androidx.compose.compiler.plugins.kotlin.irTrace
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrLocalDelegatedProperty
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.declarations.name
import org.jetbrains.kotlin.ir.expressions.IrBlock
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrContainerExpression
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
import org.jetbrains.kotlin.ir.expressions.IrGetField
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.IrReturn
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.interpreter.getLastOverridden
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeArgument
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.classFqName
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.impl.buildSimpleType
import org.jetbrains.kotlin.ir.types.impl.toBuilder
import org.jetbrains.kotlin.ir.types.isAny
import org.jetbrains.kotlin.ir.types.isClassWithFqName
import org.jetbrains.kotlin.ir.types.isNullableAny
import org.jetbrains.kotlin.ir.types.typeOrNull
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isFunction
import org.jetbrains.kotlin.ir.util.isNullConst
import org.jetbrains.kotlin.ir.util.isTypeParameter
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.statements
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid

/**
 * This transformer walks the IR tree to infer the applier annotations such as ComposableTarget,
 * ComposableOpenTarget, and
 */
class ComposableTargetAnnotationsTransformer(
    context: IrPluginContext,
    symbolRemapper: ComposableSymbolRemapper,
    metrics: ModuleMetrics,
    stabilityInferencer: StabilityInferencer
) : AbstractComposeLowering(context, symbolRemapper, metrics, stabilityInferencer) {
    private val ComposableTargetClass = symbolRemapper.getReferencedClassOrNull(
        getTopLevelClassOrNull(ComposeClassIds.ComposableTarget)
    )
    private val ComposableOpenTargetClass = symbolRemapper.getReferencedClassOrNull(
        getTopLevelClassOrNull(ComposeClassIds.ComposableOpenTarget)
    )
    private val ComposableInferredTargetClass = symbolRemapper.getReferencedClassOrNull(
        getTopLevelClassOrNull(ComposeClassIds.ComposableInferredTarget)
    )

    /**
     * A map of element to the owning function of the element.
     */
    private val ownerMap = mutableMapOf()

    /**
     * Map of a parameter symbol to its function and parameter index.
     */
    private val parameterOwners = mutableMapOf>()

    /**
     * A map of variables to their corresponding inference node.
     */
    private val variableDeclarations = mutableMapOf()

    private var currentOwner: IrFunction? = null
    private var currentFile: IrFile? = null

    private val transformer get() = this

    private fun lineInfoOf(element: IrElement?): String {
        val file = currentFile
        if (element != null && file != null) {
            return " ${file.name}:${
                file.fileEntry.getLineNumber(element.startOffset) + 1
            }:${
                file.fileEntry.getColumnNumber(element.startOffset) + 1
            }"
        }
        return ""
    }

    private val infer = ApplierInferencer(
        typeAdapter = object : TypeAdapter {
            val current = mutableMapOf()
            override fun declaredSchemaOf(type: InferenceFunction): Scheme =
                type.toDeclaredScheme().also {
                    type.recordScheme(it)
                }

            override fun currentInferredSchemeOf(type: InferenceFunction): Scheme? =
                if (type.schemeIsUpdatable) current[type] ?: declaredSchemaOf(type) else null

            override fun updatedInferredScheme(type: InferenceFunction, scheme: Scheme) {
                type.recordScheme(scheme)
                type.updateScheme(scheme)
                current[type] = scheme
            }
        },
        nodeAdapter = object : NodeAdapter {
            override fun containerOf(node: InferenceNode): InferenceNode =
                ownerMap[node.element]?.let {
                    inferenceNodeOf(it, transformer)
                } ?: (node as? InferenceResolvedParameter)?.referenceContainer ?: node

            override fun kindOf(node: InferenceNode): NodeKind = node.kind

            override fun schemeParameterIndexOf(
                node: InferenceNode,
                container: InferenceNode
            ): Int = node.parameterIndex(container)

            override fun typeOf(node: InferenceNode): InferenceFunction? = node.function

            override fun referencedContainerOf(node: InferenceNode): InferenceNode? =
                node.referenceContainer
        },
        lazySchemeStorage = object : LazySchemeStorage {
            // The transformer is transitory so we can just store this in a map.
            val map = mutableMapOf()
            override fun getLazyScheme(node: InferenceNode): LazyScheme? = map[node]
            override fun storeLazyScheme(node: InferenceNode, value: LazyScheme) {
                map[node] = value
            }
        },
        errorReporter = object : ErrorReporter {
            override fun reportCallError(node: InferenceNode, expected: String, received: String) {
                // Ignored, should be reported by the front-end
            }

            override fun reportParameterError(
                node: InferenceNode,
                index: Int,
                expected: String,
                received: String
            ) {
                // Ignored, should be reported by the front-end
            }

            override fun log(node: InferenceNode?, message: String) {
                val element = node?.element
                if (!metrics.isEmpty)
                    metrics.log("applier inference${lineInfoOf(element)}: $message")
            }
        }
    )

    override fun lower(module: IrModuleFragment) {
        // Only transform if the attributes being inferred are in the runtime
        if (
            ComposableTargetClass != null &&
            ComposableInferredTargetClass != null &&
            ComposableOpenTargetClass != null
        ) {
            module.transformChildrenVoid(this)
        }
    }

    override fun visitFile(declaration: IrFile): IrFile {
        includeFileNameInExceptionTrace(declaration) {
            currentFile = declaration
            return super.visitFile(declaration).also { currentFile = null }
        }
    }

    override fun visitFunction(declaration: IrFunction): IrStatement {
        if (
            declaration.hasSchemeSpecified() ||
            (!declaration.isComposable && !declaration.hasComposableParameter()) ||
            declaration.hasOverlyWideParameters() ||
            declaration.hasOpenTypeParameters()
        ) {
            return super.visitFunction(declaration)
        }
        val oldOwner = currentOwner
        currentOwner = declaration
        var currentParameter = 0
        fun recordParameter(parameter: IrValueParameter) {
            if (parameter.type.isOrHasComposableLambda) {
                parameterOwners[parameter.symbol] = declaration to currentParameter++
            }
        }
        declaration.valueParameters.forEach { recordParameter(it) }
        declaration.extensionReceiverParameter?.let { recordParameter(it) }

        val result = super.visitFunction(declaration)
        currentOwner = oldOwner
        return result
    }

    override fun visitVariable(declaration: IrVariable): IrStatement {
        if (declaration.type.isOrHasComposableLambda) {
            currentOwner?.let { ownerMap[declaration] = it }

            val initializerNode = declaration.initializer
            if (initializerNode != null) {
                val initializer = resolveExpressionOrNull(initializerNode)
                    ?: InferenceElementExpression(transformer, initializerNode)
                val variable = InferenceVariable(this, declaration)
                variableDeclarations[declaration.symbol] = variable
                infer.visitVariable(variable, initializer)
            }
        }

        return super.visitVariable(declaration)
    }

    override fun visitLocalDelegatedProperty(declaration: IrLocalDelegatedProperty): IrStatement {
        val result = super.visitLocalDelegatedProperty(declaration)
        if (declaration.type.isOrHasComposableLambda) {
            // Find the inference variable for the delegate which should have been created
            // when visiting the delegate node. If not, then ignore this declaration
            val variable = variableDeclarations[declaration.delegate.symbol] ?: return result

            // Allow the variable to be found from the getter as this is what is used to access
            // the variable, not the delegate directly.
            variableDeclarations[declaration.getter.symbol] = variable
        }
        return result
    }

    override fun visitCall(expression: IrCall): IrExpression {
        val owner = currentOwner
        if (
            owner == null || (
                    !expression.isComposableCall() &&
                    !expression.hasComposableArguments()
                ) || when (expression.symbol.owner.fqNameWhenAvailable) {
                    ComposeFqNames.getCurrentComposerFullName,
                    ComposeFqNames.composableLambdaFullName -> true
                    else -> false
                }

        ) {
            return super.visitCall(expression)
        }
        ownerMap[expression] = owner
        val result = super.visitCall(expression)

        val target = (
            if (
                expression.isInvoke() ||
                expression.dispatchReceiver?.type?.isSamComposable == true
            ) {
                expression.dispatchReceiver?.let {
                    resolveExpressionOrNull(it)
                }
            } else resolveExpressionOrNull(expression)
            ) ?: InferenceCallTargetNode(this, expression)
        if (target.isOverlyWide()) return result

        val arguments = expression.arguments.filterIndexed { index, argument ->
            argument?.let {
                it.isComposableLambda || it.isComposableParameter || (
                    if (
                    // There are three cases where the expression type is not good enough here,
                    // one, the type is a default parameter and there is no actual expression
                    // and, two, when the expression is a SAM conversion where the type is
                    // too specific (it is the class) and we need the SAM interface, and three
                    // the value is null for a nullable type.
                    (
                        argument is IrContainerExpression &&
                            argument.origin == IrStatementOrigin.DEFAULT_VALUE
                        ) || (
                            argument is IrBlock
                        ) || (
                            argument.isNullConst()
                        )
                ) {
                    // If the parameter is a default value, grab the type from the function
                    // being called.
                    expression.symbol.owner.valueParameters.let { parameters ->
                        if (index < parameters.size) parameters[index].type else null
                    }
                } else it.type)?.isOrHasComposableLambda == true
            } == true
        }.filterNotNull().toMutableList()
        fun recordArgument(argument: IrExpression?) {
            if (
                argument != null && (
                    argument.isComposableLambda ||
                    argument.isComposableParameter ||
                    argument.type.isOrHasComposableLambda
                )
            ) {
                arguments.add(argument)
            }
        }

        recordArgument(expression.extensionReceiver)

        infer.visitCall(
            call = inferenceNodeOf(expression, transformer),
            target = target,
            arguments = arguments.map {
                resolveExpressionOrNull(it) ?: inferenceNodeOf(it, transformer)
            }
        )

        return result
    }

    private fun inferenceParameterOrNull(getValue: IrGetValue): InferenceResolvedParameter? =
        parameterOwners[getValue.symbol]?.let {
            InferenceResolvedParameter(
                getValue,
                inferenceFunctionOf(it.first),
                inferenceNodeOf(it.first, this),
                it.second
            )
        }

    /**
     * Resolve references to local variables and parameters.
     */
    private fun resolveExpressionOrNull(expression: IrElement?): InferenceNode? =
        when (expression) {
            is IrGetValue ->
                // Get the inference node for referencing a local variable or parameter if this
                // expression does.
                inferenceParameterOrNull(expression) ?: variableDeclarations[expression.symbol]
            is IrCall ->
                // If this call is a call to the getter of a local delegate get the inference
                // node of the delegate.
                variableDeclarations[expression.symbol]
            else -> null
        }

    val List.target: Item get() =
        firstOrNull { it.isComposableTarget }?.let { constructor ->
            constructor.firstParameterOrNull()?.let { Token(it) }
        } ?: firstOrNull { it.isComposableOpenTarget }?.let { constructor ->
            constructor.firstParameterOrNull()?.let { Open(it) }
        } ?: firstOrNull { it.isComposableTargetMarked }?.let { constructor ->
            val fqName = constructor.symbol.owner.parentAsClass.fqNameWhenAvailable
            fqName?.let {
                Token(it.asString())
            }
        } ?: Open(-1, isUnspecified = true)

    val IrFunction.scheme: Scheme? get() =
        annotations.firstOrNull { it.isComposableInferredTarget }?.let { constructor ->
            constructor.firstParameterOrNull()?.let {
                deserializeScheme(it)
            }
        }

    fun IrFunction.hasSchemeSpecified(): Boolean =
        annotations.any {
            it.isComposableTarget || it.isComposableOpenTarget || it.isComposableInferredTarget ||
                it.isComposableTargetMarked
        }

    fun IrType.toScheme(defaultTarget: Item): Scheme =
        when {
            this is IrSimpleType && isFunction() -> arguments
            else -> emptyList()
        }.let { typeArguments ->
            val target = annotations.target.let {
                if (it.isUnspecified) defaultTarget else it
            }

            fun toScheme(argument: IrTypeArgument): Scheme? =
                if (argument is IrTypeProjection && argument.type.isOrHasComposableLambda)
                    argument.type.toScheme(defaultTarget)
                else null

            val parameters = typeArguments.takeUpTo(typeArguments.size - 1).mapNotNull { argument ->
                toScheme(argument)
            }

            val result = typeArguments.lastOrNull()?.let { argument ->
                toScheme(argument)
            }

            Scheme(target, parameters, result)
        }

    private val IrElement?.isComposableLambda: Boolean get() = when (this) {
        is IrFunctionExpression -> function.isComposable
        is IrCall -> isComposableSingletonGetter() || hasTransformedLambda()
        is IrGetField -> symbol.owner.initializer?.findTransformedLambda() != null
        else -> false
    }

    private val IrElement?.isComposableParameter: Boolean get() = when (this) {
        is IrGetValue -> parameterOwners[symbol] != null && type.isComposable
        else -> false
    }

    internal fun IrCall.hasTransformedLambda() =
        context.irTrace[ComposeWritableSlices.HAS_TRANSFORMED_LAMBDA, this] == true

    private fun IrElement.findTransformedLambda(): IrFunctionExpression? =
        when (this) {
            is IrCall -> arguments.firstNotNullOfOrNull { it?.findTransformedLambda() }
            is IrGetField -> symbol.owner.initializer?.findTransformedLambda()
            is IrBody -> statements.firstNotNullOfOrNull { it.findTransformedLambda() }
            is IrReturn -> value.findTransformedLambda()
            is IrFunctionExpression -> if (isTransformedLambda()) this else null
            else -> null
        }

    private fun IrFunctionExpression.isTransformedLambda() =
        context.irTrace[ComposeWritableSlices.IS_TRANSFORMED_LAMBDA, this] == true

    internal fun IrElement.transformedLambda(): IrFunctionExpression =
        findTransformedLambda() ?: error("Could not find the lambda for ${dump()}")

    // If this function throws an error it is because the IR transformation for singleton functions
    // changed. This function should be updated to account for the change.
    internal fun IrCall.singletonFunctionExpression(): IrFunctionExpression =
        symbol.owner.body?.findTransformedLambda()
            ?: error("Could not find the singleton lambda for ${dump()}")

    private fun Item.toAnnotation(): IrConstructorCall? =
        if (ComposableTargetClass != null && ComposableOpenTargetClass != null) {
            when (this) {
                is Token -> annotation(ComposableTargetClass).also {
                    it.putValueArgument(0, irConst(value))
                }
                is Open ->
                    if (index < 0) null else annotation(
                        ComposableOpenTargetClass
                    ).also {
                        it.putValueArgument(0, irConst(index))
                    }
            }
        } else null

    private fun Item.toAnnotations(): List =
        toAnnotation()?.let { listOf(it) } ?: emptyList()

    private fun Scheme.toAnnotations(): List =
        if (ComposableInferredTargetClass != null) {
            listOf(
                annotation(ComposableInferredTargetClass).also {
                    it.putValueArgument(0, irConst(serialize()))
                }
            )
        } else emptyList()

    private fun annotation(classSymbol: IrClassSymbol) =
        IrConstructorCallImpl(
            UNDEFINED_OFFSET,
            UNDEFINED_OFFSET,
            classSymbol.defaultType,
            classSymbol.constructors.first(),
            0,
            0,
            1,
            null
        )

    private fun filteredAnnotations(annotations: List) = annotations
        .filter {
            !it.isComposableTarget &&
                !it.isComposableOpenTarget &&
                !it.isComposableInferredTarget
        }

    fun updatedAnnotations(annotations: List, target: Item) =
        filteredAnnotations(annotations) + target.toAnnotations()

    fun updatedAnnotations(annotations: List, scheme: Scheme) =
        filteredAnnotations(annotations) + scheme.toAnnotations()

    fun inferenceFunctionOf(function: IrFunction) =
        InferenceFunctionDeclaration(this, function)

    fun inferenceFunctionTypeOf(type: IrType) =
        InferenceFunctionType(this, type)

    /**
     * A function is composable if it has a composer parameter added by the
     * [ComposerParamTransformer] or it still has the @Composable annotation which
     * can be because it is external and hasn't been transformed as the symbol remapper
     * only remaps what is referenced as a symbol this method might not have been
     * referenced directly in this module.
     */
    private val IrFunction.isComposable get() =
        valueParameters.any { it.name == KtxNameConventions.COMPOSER_PARAMETER } ||
            annotations.hasAnnotation(ComposeFqNames.Composable)

    private val IrType.isSamComposable get() =
        samOwnerOrNull()?.isComposable == true

    private val IrType.isComposableLambda get() =
        (this.classFqName == ComposeFqNames.composableLambdaType) ||
        (this as? IrSimpleType)
        ?.arguments
        ?.any {
            it.typeOrNull?.classFqName == ComposeFqNames.Composer
        } == true

    internal val IrType.isOrHasComposableLambda: Boolean get() =
        isComposableLambda || isSamComposable ||
            (this as? IrSimpleType)?.arguments?.any {
                it.typeOrNull?.isOrHasComposableLambda == true
            } == true

    private val IrType.isComposable get() = isComposableLambda || isSamComposable

    private fun IrFunction.hasComposableParameter() =
        valueParameters.any { it.type.isComposable }

    private fun IrCall.hasComposableArguments() =
        arguments.any { argument ->
            argument?.type?.let { type ->
                (type.isOrHasComposableLambda || type.isSamComposable)
            } == true
        }
}

/**
 * An [InferenceFunction] is an abstraction to allow inference to translate a type into a scheme
 * and update the declaration of a type if inference determines a more accurate scheme.
 */
sealed class InferenceFunction(
    val transformer: ComposableTargetAnnotationsTransformer
) {
    /**
     * The name of the function. This is only supplied for debugging.
     */
    abstract val name: String

    /**
     * Can the scheme be updated. If not, tell inference not to track it.
     */
    abstract val schemeIsUpdatable: Boolean

    /**
     * Record a scheme for the function in metrics (if applicable).
     */
    open fun recordScheme(scheme: Scheme) { }

    /**
     * The scheme has changed so the corresponding attributes should be updated to match the
     * scheme provided.
     */
    abstract fun updateScheme(scheme: Scheme)

    /**
     * Return a declared scheme for the function.
     */
    abstract fun toDeclaredScheme(defaultTarget: Item = Open(0)): Scheme

    /**
     * Return true if this is a type with overly wide parameter types such as Any or
     * unconstrained or insufficiently constrained type parameters.
     */
    open fun isOverlyWide(): Boolean = false

    /**
     * Helper routine to produce an updated annotations list.
     */
    fun updatedAnnotations(annotations: List, target: Item) =
        transformer.updatedAnnotations(annotations, target)

    /**
     * Helper routine to produce an updated annotations list.
     */
    fun updatedAnnotations(annotations: List, scheme: Scheme) =
        transformer.updatedAnnotations(annotations, scheme)
}

/**
 * An [InferenceFunctionDeclaration] refers to the type implied by a function declaration.
 *
 * Storing [Scheme] information is complicated by the current IR transformer limitation that
 * annotations added to types are not serialized. Instead of updating the parameter types
 * directly (for example adding a annotation to the IrType of the parameter declaration) the
 * [Scheme] is serialized into a string and stored on the function declaration.
 */
class InferenceFunctionDeclaration(
    transformer: ComposableTargetAnnotationsTransformer,
    val function: IrFunction
) : InferenceFunction(transformer) {
    override val name: String get() = function.name.toString()

    override val schemeIsUpdatable: Boolean get() = true

    override fun recordScheme(scheme: Scheme) {
        if (!scheme.allAnonymous()) {
            with(transformer) {
                metricsFor(function).recordScheme(scheme.toString())
            }
        }
    }

    override fun updateScheme(scheme: Scheme) {
        if (scheme.shouldSerialize) {
            function.annotations = updatedAnnotations(function.annotations, scheme)
        } else {
            function.annotations = updatedAnnotations(function.annotations, scheme.target)
            parameters().zip(scheme.parameters) { parameter, parameterScheme ->
                parameter.updateScheme(parameterScheme)
            }
        }
    }

    override fun toDeclaredScheme(defaultTarget: Item): Scheme = with(transformer) {
        function.scheme ?: function.toScheme(defaultTarget)
    }

    private fun IrFunction.toScheme(defaultTarget: Item): Scheme = with(transformer) {
        val target = function.annotations.target.let { target ->
            if (target.isUnspecified && function.body == null) {
                defaultTarget
            } else if (target.isUnspecified) {
                // Default to the target specified at the file scope, if one.
                function.file.annotations.target
            } else target
        }
        val effectiveDefault =
            if (function.body == null) defaultTarget
            else Open(-1, isUnspecified = true)
        val result = function.returnType.let { resultType ->
            if (resultType.isOrHasComposableLambda)
                resultType.toScheme(effectiveDefault)
            else null
        }

        Scheme(
            target,
            parameters().map { it.toDeclaredScheme(effectiveDefault) },
            result
        ).let { scheme ->
            ancestorScheme(defaultTarget)?.let { scheme.mergeWith(listOf(it)) } ?: scheme
        }
    }

    private fun IrFunction.ancestorScheme(defaultTarget: Item): Scheme? =
        if (this is IrSimpleFunction && this.overriddenSymbols.isNotEmpty()) {
            getLastOverridden().toScheme(defaultTarget)
        } else null

    override fun hashCode(): Int = function.hashCode() * 31
    override fun equals(other: Any?) =
        other is InferenceFunctionDeclaration && other.function == function

    private fun parameters(): List =
        with(transformer) {
            function.valueParameters.filter { it.type.isOrHasComposableLambda }.map { parameter ->
                InferenceFunctionParameter(transformer, parameter)
            }.let { parameters ->
                function.extensionReceiverParameter?.let {
                    if (it.type.isOrHasComposableLambda) {
                        parameters + listOf(InferenceFunctionParameter(transformer, it))
                    } else parameters
                } ?: parameters
            }
        }

    private val Scheme.shouldSerialize get(): Boolean = parameters.isNotEmpty()
    private fun Scheme.allAnonymous(): Boolean = target.isAnonymous &&
        (result == null || result.allAnonymous()) &&
        parameters.all { it.allAnonymous() }
}

/**
 * An [InferenceFunctionCallType] is the type abstraction for a call. This is used for [IrCall]
 * because it has the substituted types for generic types and the function's symbol has the original
 * unsubstituted types. It is important, for example for calls to [let], that the arguments
 * and result are after generic resolution so calls like `content?.let { it.invoke() }` correctly
 * infer that the scheme is `[0[0]]` if `content` is a of type `(@Composable () -> Unit)?. This
 * can only be determined after the generic parameters have been substituted.
 */
class InferenceFunctionCallType(
    transformer: ComposableTargetAnnotationsTransformer,
    private val call: IrCall
) : InferenceFunction(transformer) {
    override val name: String get() = "Call(${call.symbol.owner.name})"

    override val schemeIsUpdatable: Boolean get() = false

    override fun toDeclaredScheme(defaultTarget: Item): Scheme =
        with(transformer) {
            val target = call.symbol.owner.annotations.target.let { target ->
                if (target.isUnspecified) defaultTarget else target
            }
            val parameters = call.arguments.filterNotNull().filter {
                 it.type.isOrHasComposableLambda
            }.map {
                it.type.toScheme(defaultTarget)
            }.toMutableList()
            fun recordParameter(expression: IrExpression?) {
                if (expression != null && expression.type.isOrHasComposableLambda) {
                    parameters.add(expression.type.toScheme(defaultTarget))
                }
            }
            recordParameter(call.extensionReceiver)
            val result = if (call.type.isOrHasComposableLambda)
                call.type.toScheme(defaultTarget)
            else null
            Scheme(target, parameters, result)
        }

    override fun isOverlyWide(): Boolean =
        call.symbol.owner.hasOverlyWideParameters()

    override fun updateScheme(scheme: Scheme) {
        // Ignore the updated scheme for the call as it can always be re-inferred.
    }
}

/**
 * Produce the scheme from a function type.
 */
class InferenceFunctionType(
    transformer: ComposableTargetAnnotationsTransformer,
    private val type: IrType
) : InferenceFunction(transformer) {
    override val name: String get() = ""
    override val schemeIsUpdatable: Boolean get() = false
    override fun toDeclaredScheme(defaultTarget: Item): Scheme = with(transformer) {
        type.toScheme(defaultTarget)
    }

    override fun updateScheme(scheme: Scheme) {
        // Cannot update the scheme of a type yet. This is worked around for parameters by recording
        // the inferred scheme for the parameter in a serialized scheme for the function. For other
        // types we don't need to record the inference we just need the declared scheme.
    }
}

/**
 * The type of a function parameter. The parameter of a function needs to update where it came from.
 */
class InferenceFunctionParameter(
    transformer: ComposableTargetAnnotationsTransformer,
    val parameter: IrValueParameter
) : InferenceFunction(transformer) {
    override val name: String get() = ""
    override fun hashCode(): Int = parameter.hashCode() * 31
    override fun equals(other: Any?) =
        other is InferenceFunctionParameter && other.parameter == parameter

    override val schemeIsUpdatable: Boolean get() = false

    override fun toDeclaredScheme(defaultTarget: Item): Scheme = with(transformer) {
        val samAnnotations = parameter.type.samOwnerOrNull()?.annotations ?: emptyList()
        val annotations = parameter.type.annotations + samAnnotations
        val target = annotations.target.let { if (it.isUnspecified) defaultTarget else it }
        parameter.type.toScheme(target)
    }

    override fun updateScheme(scheme: Scheme) {
        // Note that this is currently not called. Type annotations are serialized into an
        // ComposableInferredAnnotation. This is kept here as example of how the type should
        // be updated once such a modification is correctly serialized by Kotlin.
        val type = parameter.type
        if (type is IrSimpleType) {
            val newType = type.toBuilder().apply {
                annotations = updatedAnnotations(annotations, scheme.target)
            }.buildSimpleType()
            parameter.type = newType
        }
    }
}

/**
 * A wrapper around IrElement to return the information requested by inference.
 */
sealed class InferenceNode {
    /**
     * The element being wrapped
     */
    abstract val element: IrElement

    /**
     * The node kind of the node
     */
    abstract val kind: NodeKind

    /**
     * The function type abstraction used by inference
     */
    abstract val function: InferenceFunction?

    /**
     * The container node being referred to by the this node, if there is one. For example, if this
     * is a parameter reference then this is the function that contains the parameter (which
     * parameterIndex can be used to determine the parameter index). If it is a call to a static
     * function then the reference is to the IrFunction that is being called.
     */
    open val referenceContainer: InferenceNode? = null

    /**
     * [node] is one of the parameters of this container node then return its index. -1 indicates
     * that [node] is not a parameter of this container (or this is not a container).
     */
    open fun parameterIndex(node: InferenceNode): Int = -1

    /**
     * An overly wide function (a function with Any types) is too wide to use for to infer an
     * applier (that is it contains parameters of type Any or Any?).
     */
    open fun isOverlyWide(): Boolean = function?.isOverlyWide() == true

    override fun hashCode() = element.hashCode() * 31
    override fun equals(other: Any?) = other is InferenceNode && other.element == element
}

val IrSimpleFunctionSymbol.isGenericFunction get(): Boolean =
    owner.typeParameters.isNotEmpty() || owner.dispatchReceiverParameter?.type?.let {
        it is IrSimpleType && it.arguments.isNotEmpty()
    } == true

/**
 * An [InferenceCallTargetNode] is a wrapper around an [IrCall] which represents the target of
 * the call, not the call itself. That its type is the type of the target of the call not the
 * result of the call.
 */
class InferenceCallTargetNode(
    private val transformer: ComposableTargetAnnotationsTransformer,
    override val element: IrCall
) : InferenceNode() {
    override fun equals(other: Any?): Boolean =
        other is InferenceCallTargetNode && super.equals(other)
    override fun hashCode(): Int = super.hashCode() * 31
    override val kind: NodeKind get() = NodeKind.Function
    override val function = with(transformer) {
        if (element.symbol.owner.hasSchemeSpecified())
            InferenceFunctionDeclaration(transformer, element.symbol.owner)
        else InferenceFunctionCallType(transformer, element)
    }

    override val referenceContainer: InferenceNode? =
        // If this is a generic function then don't redirect the scheme to the declaration
        if (element.symbol.isGenericFunction) null else
        with(transformer) {
            val function = when {
                element.isComposableSingletonGetter() ->
                    // If this was a lambda transformed into a singleton, find the singleton function
                    element.singletonFunctionExpression().function
                element.hasTransformedLambda() ->
                    // If this is a normal lambda, find the lambda's IrFunction
                    element.transformedLambda().function
                else -> element.symbol.owner
            }
            // If this is a call to a non-generic function with a body (e.g. non-abstract), return its
            // function. Generic or abstract functions (interface members, lambdas, open methods, etc.)
            // do not contain a body to infer anything from so we just use the declared scheme if
            // there is one. Returning null from this function cause the scheme to be determined from
            // the target expression (using, for example, the substituted type parameters) instead of
            // the definition.
            function.takeIf { it.body != null && it.typeParameters.isEmpty() }?.let {
                inferenceNodeOf(function, transformer)
            }
        }
}

/**
 * A node representing a variable declaration.
 */
class InferenceVariable(
    private val transformer: ComposableTargetAnnotationsTransformer,
    override val element: IrVariable,
) : InferenceNode() {
    override val kind: NodeKind get() = NodeKind.Variable
    override val function: InferenceFunction get() =
        transformer.inferenceFunctionTypeOf(element.type)
    override val referenceContainer: InferenceNode? get() = null
}

fun inferenceNodeOf(
    element: IrElement,
    transformer: ComposableTargetAnnotationsTransformer
): InferenceNode =
    when (element) {
        is IrFunction -> InferenceFunctionDeclarationNode(transformer, element)
        is IrFunctionExpression -> InferenceFunctionExpressionNode(transformer, element)
        is IrTypeOperatorCall -> inferenceNodeOf(element.argument, transformer)
        is IrCall -> InferenceCallExpression(transformer, element)
        is IrExpression -> InferenceElementExpression(transformer, element)
        else -> InferenceUnknownElement(element)
    }

/**
 * A node wrapper for function declarations.
 */
class InferenceFunctionDeclarationNode(
    transformer: ComposableTargetAnnotationsTransformer,
    override val element: IrFunction
) : InferenceNode() {
    override val kind: NodeKind get() = NodeKind.Function
    override val function: InferenceFunction = transformer.inferenceFunctionOf(element)
    override val referenceContainer: InferenceNode?
        get() = this.takeIf { element.body != null }
}

/**
 * A node wrapper for function expressions (i.e. lambdas).
 */
class InferenceFunctionExpressionNode(
    private val transformer: ComposableTargetAnnotationsTransformer,
    override val element: IrFunctionExpression
) : InferenceNode() {
    override val kind: NodeKind get() = NodeKind.Lambda
    override val function: InferenceFunction = transformer.inferenceFunctionOf(element.function)
    override val referenceContainer: InferenceNode
        get() = inferenceNodeOf(element.function, transformer)
}

/**
 * A node wrapper for a call. This represents the result of a call. Use [InferenceCallTargetNode]
 * to represent the target of a call.
 */
class InferenceCallExpression(
    private val transformer: ComposableTargetAnnotationsTransformer,
    override val element: IrCall
) : InferenceNode() {
    private val isSingletonLambda = with(transformer) { element.isComposableSingletonGetter() }
    private val isTransformedLambda = with(transformer) { element.hasTransformedLambda() }
    override val kind: NodeKind get() =
        if (isSingletonLambda || isTransformedLambda) NodeKind.Lambda else NodeKind.Expression

    override val function: InferenceFunction = with(transformer) {
        when {
            isSingletonLambda ->
                inferenceFunctionOf(element.singletonFunctionExpression().function)
            isTransformedLambda ->
                inferenceFunctionOf(element.transformedLambda().function)
            else -> transformer.inferenceFunctionTypeOf(element.type)
        }
    }

    override val referenceContainer: InferenceNode?
        get() = with(transformer) {
            when {
                isSingletonLambda ->
                    inferenceNodeOf(element.singletonFunctionExpression().function, transformer)
                isTransformedLambda ->
                    inferenceNodeOf(element.transformedLambda().function, transformer)
                else -> null
            }
        }
}

/**
 * An expression node whose scheme is determined by the type of the node.
 */
class InferenceElementExpression(
    transformer: ComposableTargetAnnotationsTransformer,
    override val element: IrExpression,
) : InferenceNode() {
    override val kind: NodeKind get() = NodeKind.Expression
    override val function: InferenceFunction = transformer.inferenceFunctionTypeOf(element.type)
}

/**
 * An [InferenceUnknownElement] is a general wrapper around function declarations and lambda.
 */
class InferenceUnknownElement(
    override val element: IrElement,
) : InferenceNode() {
    override val kind: NodeKind get() = NodeKind.Expression
    override val function: InferenceFunction? get() = null
    override val referenceContainer: InferenceNode? get() = null
}

/**
 * An [InferenceResolvedParameter] is a node that references a parameter of a container of this
 * node. For example, if the parameter is captured by a nested lambda this is resolved to the
 * captured parameter.
 */
class InferenceResolvedParameter(
    override val element: IrGetValue,
    override val function: InferenceFunction,
    val container: InferenceNode,
    val index: Int
) : InferenceNode() {
    override val kind: NodeKind get() = NodeKind.ParameterReference
    override fun parameterIndex(node: InferenceNode): Int =
        if (node.function == function) index else -1

    override val referenceContainer: InferenceNode get() = container

    override fun equals(other: Any?): Boolean =
        other is InferenceResolvedParameter && other.element == element

    override fun hashCode(): Int = element.hashCode() * 31 + 103
}

private inline fun  IrConstructorCall.firstParameterOrNull() =
    if (valueArgumentsCount >= 1) {
        (getValueArgument(0) as? IrConst<*>)?.value as? T
    } else null

private val IrConstructorCall.isComposableTarget get() =
    annotationClass?.isClassWithFqName(
        ComposeFqNames.ComposableTarget.toUnsafe()
    ) == true

private val IrConstructorCall.isComposableTargetMarked: Boolean get() =
    annotationClass?.owner?.annotations?.hasAnnotation(
        ComposeFqNames.ComposableTargetMarker
    ) == true

private val IrConstructorCall.isComposableInferredTarget get() =
    annotationClass?.isClassWithFqName(
        ComposeFqNames.ComposableInferredTarget.toUnsafe()
    ) == true

private val IrConstructorCall.isComposableOpenTarget get() =
    annotationClass?.isClassWithFqName(
        ComposeFqNames.ComposableOpenTarget.toUnsafe()
    ) == true

private fun IrType.samOwnerOrNull() =
    classOrNull?.let { cls ->
        if (cls.owner.kind == ClassKind.INTERFACE) {
            cls.functions.singleOrNull {
                it.owner.modality == Modality.ABSTRACT
            }?.owner
        } else null
    }

private val IrCall.arguments get() = Array(valueArgumentsCount) {
    getValueArgument(it)
}.toList()

private fun  Iterable.takeUpTo(n: Int): List =
    if (n <= 0) emptyList() else take(n)

/**
 * A function with overly wide parameters should be ignored for traversal as well as when
 * it is called.
 */
private fun IrFunction.hasOverlyWideParameters(): Boolean =
    valueParameters.any {
        it.type.isAny() || it.type.isNullableAny()
    }

private fun IrFunction.hasOpenTypeParameters(): Boolean =
    valueParameters.any { it.type.isTypeParameter() } ||
        dispatchReceiverParameter?.type?.isTypeParameter() == true ||
        extensionReceiverParameter?.type?.isTypeParameter() == true




© 2015 - 2024 Weber Informatics LLC | Privacy Policy