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

dev.mokkery.plugin.transformers.IrInterceptCall.kt Maven / Gradle / Ivy

package dev.mokkery.plugin.transformers

import dev.mokkery.plugin.core.Mokkery
import dev.mokkery.plugin.core.TransformerScope
import dev.mokkery.plugin.core.allowIndirectSuperCalls
import dev.mokkery.plugin.core.getClass
import dev.mokkery.plugin.ir.defaultTypeErased
import dev.mokkery.plugin.ir.irCall
import dev.mokkery.plugin.ir.irCallConstructor
import dev.mokkery.plugin.ir.irCallListOf
import dev.mokkery.plugin.ir.irCallMapOf
import dev.mokkery.plugin.ir.irLambda
import dev.mokkery.plugin.ir.isJvmBinarySafeSuperCall
import dev.mokkery.plugin.ir.kClassReference
import org.jetbrains.kotlin.backend.jvm.fullValueParameterList
import org.jetbrains.kotlin.backend.jvm.functionByName
import org.jetbrains.kotlin.backend.jvm.ir.eraseTypeParameters
import org.jetbrains.kotlin.config.JvmAnalysisFlags
import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.irAs
import org.jetbrains.kotlin.ir.builders.irBoolean
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.builders.parent
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.putArgument
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.getPropertyGetter
import org.jetbrains.kotlin.ir.util.getSimpleFunction
import org.jetbrains.kotlin.ir.util.isVararg
import org.jetbrains.kotlin.ir.util.makeTypeParameterSubstitutionMap
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.util.substitute

fun IrBlockBodyBuilder.irInterceptMethod(
    transformer: TransformerScope,
    function: IrSimpleFunction,
    irCallSpyLambda: IrExpression? = null,
): IrCall = irInterceptCall(
    transformer = transformer,
    mokkeryScope = irGet(function.dispatchReceiverParameter!!),
    function = function,
    irCallSpyLambda = irCallSpyLambda,
)

fun IrBlockBodyBuilder.irInterceptCall(
    transformer: TransformerScope,
    mokkeryScope: IrExpression,
    function: IrSimpleFunction,
    irCallSpyLambda: IrExpression? = null
): IrCall {
    val interceptorClass = transformer.getClass(Mokkery.Class.MokkeryInterceptor).symbol
    val interceptorScopeClass = transformer.getClass(Mokkery.Class.MokkeryInterceptorScope)
    val callContextClass = transformer.getClass(Mokkery.Class.CallContext)
    val interceptFun = if (function.isSuspend) {
        interceptorClass.functionByName("interceptSuspendCall")
    } else {
        interceptorClass.functionByName("interceptCall")
    }
    return irCall(interceptFun) {
        dispatchReceiver = interceptorScopeClass
            .getPropertyGetter("interceptor")!!
            .let(::irCall)
            .apply { dispatchReceiver = mokkeryScope }
        val contextCreationCall = irCallConstructor(callContextClass.primaryConstructor!!) {
            putValueArgument(0, mokkeryScope)
            putValueArgument(1, irString(function.name.asString()))
            putValueArgument(2, kClassReference(function.returnType.eraseTypeParameters()))
            putValueArgument(3, irCallArgsList(transformer, function.fullValueParameterList))
            putValueArgument(4, irCallSupersMap(transformer, function))
            if (irCallSpyLambda != null) putValueArgument(5, irCallSpyLambda)
        }
        putValueArgument(0, contextCreationCall)
    }
}

private fun IrBuilderWithScope.irCallArgsList(scope: TransformerScope, parameters: List): IrCall {
    val callArgClass = scope.getClass(Mokkery.Class.CallArg)
    val callArgs = parameters
        .map {
            irCallConstructor(callArgClass.primaryConstructor!!) {
                putValueArgument(0, irString(it.name.asString()))
                putValueArgument(1, kClassReference(it.type.eraseTypeParameters()))
                putValueArgument(2, irGet(it))
                putValueArgument(3, irBoolean(it.isVararg))
            }
        }
    return irCallListOf(scope, callArgClass.defaultType, callArgs)
}

private fun IrBuilderWithScope.irCallSupersMap(transformer: TransformerScope, function: IrSimpleFunction): IrCall? {
    val allowIndirectSuperCalls = transformer.allowIndirectSuperCalls
    val defaultMode = transformer.pluginContext.languageVersionSettings.getFlag(JvmAnalysisFlags.jvmDefaultMode)
    val supers = function.overriddenSymbols
        .filter { it.owner.isJvmBinarySafeSuperCall(function, defaultMode, allowIndirectSuperCalls) }
        .takeIf { it.isNotEmpty() }
        ?.map { it.owner }
        ?: return null
    val superLambdas = supers.map { superFunction ->
        val kClass = kClassReference(superFunction.parentAsClass.defaultType)
        val lambda = createSuperCallLambda(transformer, function, superFunction)
        kClass to lambda
    }
    return irCallMapOf(transformer, superLambdas)
}

private fun IrBuilderWithScope.createSuperCallLambda(
    transformer: TransformerScope,
    function: IrSimpleFunction,
    superFunction: IrSimpleFunction
): IrExpression {
    val pluginContext = transformer.pluginContext
    val typesMap = makeTypeParameterSubstitutionMap(superFunction, function)
    val returnType = superFunction.returnType.substitute(typesMap)
    val lambdaType = pluginContext
        .irBuiltIns
        .let { if (function.isSuspend) it.suspendFunctionN(1) else it.functionN(1) }
        .typeWith(pluginContext.irBuiltIns.listClass.owner.defaultTypeErased, returnType)
    return irLambda(
        returnType = returnType,
        lambdaType = lambdaType,
        parent = parent,
    ) { lambda ->
        val superCall = irCall(
            symbol = superFunction.symbol,
            superQualifierSymbol = superFunction.parentAsClass.symbol,
            type = returnType,
        ) {
            dispatchReceiver = irGet(function.dispatchReceiverParameter!!)
            contextReceiversCount = superFunction.contextReceiverParametersCount
            function.typeParameters.forEachIndexed { i, type -> putTypeArgument(i, type.defaultType) }
            superFunction.fullValueParameterList.forEachIndexed { index, irValueParameter ->
                putArgument(
                    parameter = irValueParameter,
                    argument = irAs(
                        argument = irCall(context.irBuiltIns.listClass.owner.getSimpleFunction("get")!!) {
                            dispatchReceiver = irGet(lambda.valueParameters[0])
                            putValueArgument(0, irInt(index))
                        },
                        type = irValueParameter.type.substitute(typesMap)
                    )
                )
            }
        }
        +irReturn(superCall)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy