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

io.github.arainko.ducktape.internal.ProductBinder.scala Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
package io.github.arainko.ducktape.internal

import io.github.arainko.ducktape.Mode

import scala.annotation.*
import scala.quoted.*
import scala.util.chaining.*

private[ducktape] object ProductBinder {

  def nestFlatMapsAndConstruct[F[+x]: Type, Dest: Type](
    F: Expr[Mode.FailFast[F]],
    unwrappedFields: List[FieldValue.Unwrapped],
    wrappedFields: List[FieldValue.Wrapped[F]],
    construct: ProductConstructor
  )(using Quotes): Expr[F[Dest]] = {

    def recurse(
      leftoverFields: List[FieldValue.Wrapped[F]],
      collectedUnwrappedFields: List[FieldValue.Unwrapped]
    )(using Quotes): Expr[F[Dest]] =
      leftoverFields match {
        case (field @ FieldValue.Wrapped(_, _, wrappedValue)) :: Nil =>
          field.tpe match {
            case '[destField] =>
              val value = wrappedValue.asExprOf[F[destField]]
              '{
                $F.map[`destField`, Dest](
                  $value,
                  ${
                    generateLambda[[A] =>> A, destField, Dest](
                      field,
                      unwrappedValue =>
                        // TODO: Come back to this at some point, owners of the collected unwrapped defs need to be aligned to Symbol.spliceOwner
                        // so there are Exprs being constructed in a wrong way somewhere (this only occurts when falling back to the non-fallible PlanInterpreter)
                        val fields =
                          ((field.index -> unwrappedValue) :: collectedUnwrappedFields.map(f =>
                            f.index -> alignOwner(f.value)
                          )).sortBy { case (idx, _) => idx }.map { case (_, expr) => expr }
                        construct(fields).asExprOf[Dest]
                    )
                  }
                )
              }
          }

        case (field @ FieldValue.Wrapped(_, _, wrappedValue)) :: next =>
          field.tpe match {
            case '[destField] =>
              val value = wrappedValue.asExprOf[F[destField]]
              '{
                $F.flatMap[`destField`, Dest](
                  $value,
                  ${
                    generateLambda[F, destField, Dest](
                      field,
                      q ?=> unwrappedValue => recurse(next, field.unwrapped(unwrappedValue) :: collectedUnwrappedFields)(using q)
                    )
                  }
                )
              }
          }

        case Nil =>
          val fields = collectedUnwrappedFields.sorted.map(_.value)
          def constructedValue(using Quotes) = construct(fields).asExprOf[Dest]
          '{ $F.pure[Dest]($constructedValue) }
      }

    recurse(wrappedFields, unwrappedFields)
  }

  private def alignOwner(expr: Expr[Any])(using Quotes) = {
    import quotes.reflect.*
    expr.asTerm.changeOwner(Symbol.spliceOwner).asExpr
  }

  // this fixes a weird compiler crash where if I use the same name for each of the lambda args the compiler is not able to find a proxy for one of the invocations (?)
  // this probably warrants a crash report?
  @nowarn // todo: use @unchecked?
  private def generateLambda[F[+x]: Type, A: Type, B: Type](
    field: FieldValue,
    f: (Quotes) ?=> Expr[A] => Expr[F[B]]
  )(using Quotes) = {
    import quotes.reflect.*

    val mtpe = MethodType(List(s"field${field.index}"))(_ => List(TypeRepr.of[A]), _ => TypeRepr.of[F[B]])
    Lambda(
      Symbol.spliceOwner,
      mtpe,
      {
        case (methSym, (arg1: Term) :: Nil) =>
          given Quotes = methSym.asQuotes
          f(arg1.asExprOf[A]).asTerm
      }
    ).asExprOf[A => F[B]]
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy