main.de.jensklingenberg.ktorfit.CreateFuncTransformer.kt Maven / Gradle / Ivy
The newest version!
package de.jensklingenberg.ktorfit
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
import org.jetbrains.kotlin.ir.types.classFqName
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.dumpKotlinLike
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.js.descriptorUtils.getKotlinTypeFqName
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
/**
* Transform exampleKtorfit.create() to exampleKtorfit.create(_TestApiProvider())
*/
internal class CreateFuncTransformer(
private val pluginContext: IrPluginContext,
private val debugLogger: DebugLogger,
) : IrElementTransformerVoidWithContext() {
companion object {
fun errorTypeArgumentNotInterface(implName: String) =
"create<$implName> argument is not supported. Type argument needs to be an interface"
fun errorImplNotFound(
implName: String,
className: String,
) = "$implName not found, did you apply the Ksp Ktorfit plugin? Use .create$className() instead"
fun errorClassNotFound(implName: String) = "class $implName not found, did you apply the Ksp Ktorfit plugin?"
private const val KTORFIT_PACKAGE = "de.jensklingenberg.ktorfit.Ktorfit"
private const val KTORFIT_CREATE = "create"
}
@OptIn(UnsafeDuringIrConstructionAPI::class)
override fun visitExpression(expression: IrExpression): IrExpression {
// Find exampleKtorfit.create()
(expression as? IrCall)?.let { irCall ->
if (irCall.typeArgumentsCount > 0) {
if (!expression.symbol.owner.symbol
.toString()
.contains(KTORFIT_PACKAGE)
) {
return expression
}
if (expression.symbol.owner.name
.asString() != KTORFIT_CREATE
) {
return expression
}
if (expression.getValueArgument(0) != null) {
return expression
}
// Get T from create()
val argumentType = irCall.getTypeArgument(0) ?: return expression
val classFqName = argumentType.classFqName
if (!argumentType.isInterface()) {
throw IllegalStateException(
errorTypeArgumentNotInterface(argumentType.dumpKotlinLike()),
)
}
if (classFqName == null) {
throw IllegalStateException(
errorClassNotFound(argumentType.toIrBasedKotlinType().getKotlinTypeFqName(false))
)
}
val packageName = classFqName.packageName
val className = classFqName.shortName().toString()
val providerClassName = "_$className" + "Provider"
// Find the class _TestApiProvider
val implClassSymbol =
pluginContext.referenceClass(
ClassId(
FqName(packageName),
Name.identifier(providerClassName),
),
) ?: throw IllegalStateException(errorImplNotFound(providerClassName, className))
val newConstructor = implClassSymbol.constructors.first()
// Create the constructor call for _ExampleApiProvider()
val newCall =
IrConstructorCallImpl(
0,
0,
type = implClassSymbol.defaultType,
symbol = newConstructor,
0,
0,
0,
null,
)
// Set _ExampleApiProvider() as argument for create()
irCall.putValueArgument(0, newCall)
debugLogger.log(
"Transformed " + argumentType.toIrBasedKotlinType().getKotlinTypeFqName(false).substringAfterLast(".") +
" to _$className" +
"Provider",
)
return super.visitExpression(irCall)
}
}
return super.visitExpression(expression)
}
}
private val FqName?.packageName: String
get() {
return this.toString().substringBeforeLast(".")
}