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

io.exoquery.terpal.plugin.transform.WrapInterpolatedTerm.kt Maven / Gradle / Ivy

package io.exoquery.terpal.plugin.transform

import io.exoquery.terpal.StrictType
import io.exoquery.terpal.WrapFailureMessage
import io.exoquery.terpal.plugin.classOrFail
import io.exoquery.terpal.plugin.isValidWrapFunction
import io.exoquery.terpal.plugin.location
import io.exoquery.terpal.plugin.source
import io.exoquery.terpal.plugin.trees.isSubclassOf
import org.jetbrains.kotlin.ir.backend.js.utils.valueArguments
import org.jetbrains.kotlin.ir.builders.irBoolean
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstKind
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*

fun wrapWithExceptionHandler(ctx: BuilderContext, expr: IrExpression, parent: IrDeclarationParent, spliceTermNumber: Int, totalTerms: Int): IrExpression =
  with (ctx) {
    // then we take that and put it into a zero-arg lambda in order to be able to make it lazy
    // () -> wrapString(person.name)
    val invokeCallLambda = createLambda0(expr, parent)

    val (code, isApproximate) =
      with(ctx) { expr.source }?.let { codeStr -> codeStr to false } ?: expr.dumpKotlinLike() to true

    val codeExpr = ctx.builder.irString(code)
    val codeIsApproximateExpr = ctx.builder.irBoolean(isApproximate)
    val termNumber = ctx.builder.irInt(spliceTermNumber + 1)
    val loc = expr.location(ctx.currentFile.fileEntry)
    val locationPath = ctx.builder.irString("file://${loc.path}:${loc.line}:${loc.column}")
    val totalTermsExpr = ctx.builder.irInt(totalTerms)
    return callGlobalMethod("io.exoquery.terpal", "wrapSplice")(locationPath, codeExpr, codeIsApproximateExpr, termNumber, totalTermsExpr, invokeCallLambda)
  }


fun wrapInterpolatedTerm(ctx: BuilderContext, caller: IrExpression, expr: IrExpression, interpolateType: IrType): IrExpression =
  with (ctx) {
    val classAnnotations = caller.type.classOrFail.owner.annotations.find { it.isSubclassOf() }
    val annotationMessage =
      if (classAnnotations != null) {
        val arg = classAnnotations.valueArguments[0]
        when {
          arg is IrConst<*> && arg.kind == IrConstKind.String -> arg.value as String
          else -> {
            ctx.logger.warn("Annotation WrapFailureMessage must have a Static-constant string argument but found: ${arg?.dumpKotlinLike()}. Will not be able to use this user-defined message in wrapping errors.")
            ""
          }
        }
      }
      else ""

    fun IrSimpleFunctionSymbol.isWrapForExprType(): Boolean {
      val func = this
      val isStrict = func.owner.annotations.any { it.isSubclassOf() }
      val firstParamType = func.owner.valueParameters.first().type
      return if (isStrict) {
        // If strict, just compare the types independently of nullability
        firstParamType.makeNullable() == expr.type.makeNullable()
      } else {
        // Say we've we're wrapping: Sql("... ${expr:FirstName} ...")
        // where `data class FirstName(...): Name { ... }`
        // we want a wrap function that is either Sql.wrap(value:FirstName) or Sql.wrap(value:Name)
        // in the 2nd case, we want the `expr` to be sub-type of the Sql.wrap value (i.e. contravariance).
        expr.type.isSubtypeOfClass(firstParamType.classOrFail)
      }
    }

    val invokeFunction =
      caller.type.classOrFail.functions.find { it.isValidWrapFunction(interpolateType) && it.isWrapForExprType() }
        ?: Messages.errorFailedToFindWrapper(ctx, caller, expr, interpolateType, annotationMessage)

    val invokeCall = caller.callMethodTyped(invokeFunction)().invoke(expr)

    if (ctx.options.traceWrappers) ctx.logger.warn("==== Calling wrapper function `${invokeFunction.printInvokeFunctionSignature()}` on the expression `${invokeCall.dumpKotlinLike()}` typed as: `${expr.type.dumpKotlinLike()}`")
    return invokeCall
  }

fun IrSimpleFunctionSymbol.printInvokeFunctionSignature() =
  owner.dumpKotlinLike(KotlinLikeDumpOptions(bodyPrintingStrategy = BodyPrintingStrategy.NO_BODIES))
    .replace("/* fake */", "")
    .trim()





© 2015 - 2025 Weber Informatics LLC | Privacy Policy