ComposableFunInterfaceLowering.kt

/*
 * 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 org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.util.isLambda
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.platform.jvm.isJvm

@Suppress("PRE_RELEASE_CLASS")
class ComposableFunInterfaceLowering(private val context: IrPluginContext) :
    IrElementTransformerVoidWithContext(),
    ModuleLoweringPass {

    override fun lower(module: IrModuleFragment) {
        if (context.platform.isJvm()) {
            module.transformChildrenVoid(this)
        }
    }

    private fun isFunInterfaceConversion(expression: IrTypeOperatorCall): Boolean {
        val argument = expression.argument
        val operator = expression.operator
        val type = expression.typeOperand
        val functionClass = type.classOrNull
        return operator == IrTypeOperator.SAM_CONVERSION &&
            argument is IrFunctionExpression &&
            argument.origin.isLambda &&
            functionClass != null &&
            functionClass.owner.isFun
        // IMPORTANT(b/178663739):
        // We are transforming not just SAM conversions for composable fun interfaces, but ALL
        // fun interfaces temporarily until KT-44622 gets fixed in the version of kotlin we
        // are using, which should be in 1.4.30.
        // Once it does, we should either add the below additional condition to this predicate,
        // or, if possible, remove this lowering all together if kotlin's lowering works for
        // composable fun interfaces as well.
        //
        // functionClass.functions.single {
        //    it.owner.modality == Modality.ABSTRACT
        // }.owner.annotations.hasAnnotation(ComposeFqNames.Composable)
    }

    override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
        if (isFunInterfaceConversion(expression)) {
            val argument = expression.argument.transform(this, null) as IrFunctionExpression
            val superType = expression.typeOperand
            val superClass = superType.classOrNull ?: error("Expected non-null class")
            return FunctionReferenceBuilder(
                argument,
                superClass,
                superType,
                currentDeclarationParent!!,
                context,
                currentScope!!.scope.scopeOwnerSymbol,
                IrTypeSystemContextImpl(context.irBuiltIns)
            ).build()
        }
        return super.visitTypeOperator(expression)
    }
}