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

io.github.zeal18.zio.mongodb.bson.codecs.Preparations.scala Maven / Gradle / Ivy

package io.github.zeal18.zio.mongodb.bson.codecs

import scala.quoted.*
import io.github.zeal18.zio.mongodb.bson.codecs.Macro.summonLater
import io.github.zeal18.zio.mongodb.bson.codecs.Macro.printResult
import io.github.zeal18.zio.mongodb.bson.codecs.Macro.FakeCodec

private class Preparations(logCode: Boolean)(using q: Quotes) {
  import q.reflect.*

  private val init1  = Init("a" + _)
  private val init2  = Init("b" + _, Flags.Lazy)
  private val lazies = List.newBuilder[Statement]
  private var preps  = Map.empty[TypeRepr, Prepared[?]]
  private val stable = collection.mutable.HashMap.empty[TypeRepr, Expr[Codec[Any]]]

  private def normalise[A](using t: Type[A]): TypeRepr =
    TypeRepr.of[A].dealias

  def addNow[A: Type](expr: Expr[Codec[A]]): Prepared[A] = {
    val vd = init1.valDef(expr, extraFlags = Flags.Implicit)
    val p  = Prepared[A](Type.of[A], vd.ref, None)
    preps = preps.updated(normalise[A], p)
    p
  }

  def addDeferred[A: Type](complete: => Expr[Codec[A]]): Prepared[A] =
    import Flags.*
    val name   = init1.newName()
    val theVar = init1.valDef('{ new FakeCodec[A](): Codec[A] }, name = name, extraFlags = Mutable)
    val theVal =
      init1.valDef(theVar.ref, name = s"_$name", extraFlags = Implicit | Lazy, onInit = false)

    lazies += theVal.valDef
    lazy val assignComplete = theVar.assign(complete)
    val p                   = Prepared[A](Type.of[A], theVar.ref, Some(() => assignComplete))
    preps = preps.updated(normalise[A], p)
    p

  /** @param typ Just `A`, not `Reusable[A]` */
  def get[A](using t: Type[A]): Option[Prepared[A]] =
    val t = normalise[A]
    preps.get(t).map(_.subst[A])

  /** @param typ Just `A`, not `Reusable[A]` */
  def need[A](using t: Type[A]): Prepared[A] =
    get[A].getOrElse(
      throw new IllegalStateException(s"Prepared type for ${normalise[A].show} not found!"),
    )

  def getOrSummonLater[A](using t: Type[A]): Expr[Codec[A]] =
    get[A] match {
      case Some(p) => p.varRef
      case None    => Expr.summonLater[Codec[A]]
    }

  def getStablisedImplicitInstance[A: Type]: Expr[Codec[A]] =
    def target: Expr[Codec[A]] =
      get[A] match
        case Some(p) => p.varRef
        case None    => init2.valDef(Expr.summonLater[Codec[A]]).ref
    stable
      .getOrElseUpdate(TypeRepr.of[A], target.asInstanceOf[Expr[Codec[Any]]])
      .asInstanceOf[Expr[Codec[A]]]

  def stabliseInstance[A: Type](e: Expr[Codec[A]]): Expr[Codec[A]] =
    init2.valDef(e).ref

  def result[A: Type](finalResult: Prepared[A]): Expr[Codec[A]] = {
    init1 ++= lazies.result()
    val result: Expr[Codec[A]] =
      init1.wrapExpr {
        init2.wrapExpr {
          val allPreps = preps.valuesIterator.flatMap(_.complete.map(_())).toList
          Expr.block(allPreps, finalResult.varRef)
        }
      }

    if logCode then printResult(result)

    result
  }
}

private object Preparations:
  def apply[A: Type](logCode: Boolean)(f: Preparations => Prepared[A])(using
    Quotes,
  ): Expr[Codec[A]] =
    val preparations = new Preparations(logCode)
    val prepared     = f(preparations)
    preparations.result(prepared)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy