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

io.github.arainko.ducktape.internal.Structure.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.internal.*
import io.github.arainko.ducktape.internal.Structure.*

import scala.annotation.{ tailrec, unused }
import scala.collection.immutable.VectorMap
import scala.deriving.Mirror
import scala.quoted.*
import scala.reflect.TypeTest

private[ducktape] sealed trait Structure extends scala.Product derives Debug {
  def tpe: Type[?]

  def path: Path

  def _1: Type[?] = tpe

  final def force: Structure =
    this match {
      case lzy: Lazy => lzy.struct.force
      case other     => other
    }

  final def narrow[A <: Structure](using tt: TypeTest[Structure, A]): Option[A] = tt.unapply(this.force)
}

private[ducktape] object Structure {
  def unapply(struct: Structure): Structure = struct

  def toplevelAny(using Quotes) = Structure.Ordinary(Type.of[Any], Path.empty(Type.of[Any]))

  def toplevelNothing(using Quotes) = Structure.Ordinary(Type.of[Nothing], Path.empty(Type.of[Nothing]))

  case class Product(tpe: Type[?], path: Path, fields: VectorMap[String, Structure]) extends Structure {
    private var cachedDefaults: Map[String, Expr[Any]] = null

    def defaults(using Quotes): Map[String, Expr[Any]] =
      if cachedDefaults != null then cachedDefaults
      else {
        cachedDefaults = Defaults.of(this)
        cachedDefaults
      }
  }

  case class Tuple(tpe: Type[?], path: Path, elements: Vector[Structure], isPlain: Boolean) extends Structure

  case class Coproduct(tpe: Type[?], path: Path, children: Map[String, Structure]) extends Structure

  case class Function(
    tpe: Type[?],
    path: Path,
    args: VectorMap[String, Structure],
    function: io.github.arainko.ducktape.internal.Function
  ) extends Structure

  case class Optional(tpe: Type[? <: Option[?]], path: Path, paramStruct: Structure) extends Structure

  object Optional {
    def fromWrapped(wrapped: Wrapped[Option]): Optional =
      Optional(wrapped.tpe, wrapped.path, wrapped.underlying)
  }

  case class Collection(tpe: Type[? <: Iterable[?]], path: Path, paramStruct: Structure) extends Structure

  case class Singleton(tpe: Type[?], path: Path, name: String, value: Expr[Any]) extends Structure

  case class Ordinary(tpe: Type[?], path: Path) extends Structure

  case class ValueClass(tpe: Type[? <: AnyVal], path: Path, paramTpe: Type[?], paramFieldName: String) extends Structure

  case class Wrapped[F[+x]](tpe: Type[? <: F[Any]], wrapper: WrapperType[F], path: Path, underlying: Structure) extends Structure

  case class Lazy private (tpe: Type[?], path: Path, private val deferredStruct: () => Structure) extends Structure {
    lazy val struct: Structure = deferredStruct()
  }

  object Lazy {
    def of[A: Type](path: Path)(using Context, Quotes): Lazy = Lazy(Type.of[A], path, () => Structure.of[A](path))
  }

  def fromFunction(function: io.github.arainko.ducktape.internal.Function)(using Context, Quotes): Structure.Function = {
    import quotes.reflect.*
    val path = Path.empty(function.returnTpe)

    val args =
      function.args.transform((name, tpe) =>
        tpe match { case '[argTpe] => Lazy.of[argTpe](path.appended(Path.Segment.Field(tpe, name))) }
      )

    Structure.Function(function.returnTpe, path, args, function)
  }

  def fromTypeRepr(using Context, Quotes)(repr: quotes.reflect.TypeRepr, path: Path): Structure =
    repr.widen.asType match {
      case '[tpe] => Structure.of[tpe](path)
    }

  def of[A: Type](path: Path)(using Context, Quotes): Structure = {
    import quotes.reflect.*

    Logger.loggedInfo("Structure"):
      Type.of[A] match {
        case tpe @ '[Nothing] =>
          Structure.Ordinary(tpe, path)

        case WrapperType(wrapper: WrapperType[f], '[underlying]) =>
          @unused given Type[f] = wrapper.wrapper
          Structure.Wrapped(
            Type.of[f[underlying]],
            wrapper,
            path,
            Structure.of[underlying](path.appended(Path.Segment.Element(Type.of[underlying])))
          )

        case tpe @ '[Option[param]] =>
          Structure.Optional(tpe, path, Structure.of[param](path.appended(Path.Segment.Element(Type.of[param]))))

        case tpe @ '[Iterable[param]] =>
          Structure.Collection(tpe, path, Structure.of[param](path.appended(Path.Segment.Element(Type.of[param]))))

        case tpe @ '[AnyVal] if tpe.repr.typeSymbol.flags.is(Flags.Case) =>
          val repr = tpe.repr
          val param = repr.typeSymbol.caseFields.head
          val paramTpe = repr.memberType(param)
          Structure.ValueClass(tpe, path, paramTpe.asType, param.name)

        case tpe @ '[Any *: scala.Tuple] if !tpe.repr.isTupleN => // let plain tuples be caught later on
          val elements =
            tupleTypeElements(tpe).zipWithIndex.map { (tpe, idx) =>
              tpe.asType match {
                case '[tpe] => Lazy.of[tpe](path.appended(Path.Segment.TupleElement(Type.of[tpe], idx)))
              }
            }.toVector
          Structure.Tuple(Type.of[A], path, elements, isPlain = false)

        case tpe =>
          Expr.summon[Mirror.Of[A]] match {
            case None =>
              Structure.Ordinary(Type.of[A], path)

            case Some(value) =>
              value match {
                case '{
                      type label <: String
                      $m: Mirror.Singleton {
                        type MirroredLabel = `label`
                      }
                    } =>
                  val value = materializeSingleton[A]
                  Structure.Singleton(Type.of[A], path, constantString[label], value.asExpr)
                case '{
                      type label <: String
                      $m: Mirror.SingletonProxy {
                        type MirroredLabel = `label`
                      }
                    } =>
                  val value = materializeSingleton[A]
                  Structure.Singleton(Type.of[A], path, constantString[label], value.asExpr)

                case '{
                      $m: Mirror.Product {
                        type MirroredElemLabels = labels
                        type MirroredElemTypes = types
                      }
                    } if tpe.repr.isTupleN =>
                  val structures =
                    tupleTypeElements(Type.of[types]).zipWithIndex
                      .map((tpe, idx) =>
                        tpe.asType match {
                          case '[tpe] => Lazy.of[tpe](path.appended(Path.Segment.TupleElement(Type.of[tpe], idx)))
                        }
                      )
                      .toVector

                  Structure.Tuple(Type.of[A], path, structures, isPlain = true)

                case '{
                      $m: Mirror.Product {
                        type MirroredElemLabels = labels
                        type MirroredElemTypes = types
                      }
                    } =>
                  val structures =
                    tupleTypeElements(Type.of[types])
                      .zip(constStringTuple(TypeRepr.of[labels]))
                      .map((tpe, name) =>
                        name -> (tpe.asType match {
                          case '[tpe] => Lazy.of[tpe](path.appended(Path.Segment.Field(Type.of[tpe], name)))
                        })
                      )
                      .to(VectorMap)

                  Structure.Product(Type.of[A], path, structures)
                case '{
                      $m: Mirror.Sum {
                        type MirroredElemLabels = labels
                        type MirroredElemTypes = types
                      }
                    } =>
                  val structures =
                    tupleTypeElements(Type.of[types])
                      .zip(constStringTuple(TypeRepr.of[labels]))
                      .map((tpe, name) =>
                        name -> (tpe.asType match { case '[tpe] => Lazy.of[tpe](path.appended(Path.Segment.Case(Type.of[tpe]))) })
                      )
                      .toMap

                  Structure.Coproduct(Type.of[A], path, structures)
              }
          }
      }
  }

  private def materializeSingleton[A: Type](using Quotes) = {
    import quotes.reflect.*
    TypeRepr.of[A] match { case ref: TermRef => Ident(ref) }
  }

  private def constantString[Const <: String: Type](using Quotes) = Type.valueOfConstant[Const].get

  private def tupleTypeElements(tpe: Type[?])(using Quotes): List[quotes.reflect.TypeRepr] = {
    @tailrec def loop(using Quotes)(curr: Type[?], acc: List[quotes.reflect.TypeRepr]): List[quotes.reflect.TypeRepr] = {
      import quotes.reflect.*

      curr match {
        case '[head *: tail] =>
          loop(Type.of[tail], TypeRepr.of[head] :: acc)
        case '[EmptyTuple] =>
          acc
        case other =>
          report.errorAndAbort(
            s"Unexpected type (${other.repr.show}) encountered when extracting tuple type elems. This is a bug in ducktape."
          )
      }
    }

    loop(tpe, Nil).reverse
  }

  private def constStringTuple(using Quotes)(tp: quotes.reflect.TypeRepr): List[String] = {
    import quotes.reflect.*
    tupleTypeElements(tp.asType).map { case ConstantType(StringConstant(l)) => l }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy