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

play.api.libs.json.QuotesHelper.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 
 */

package play.api.libs.json

import scala.util.{ Try => TryResult }
import scala.util.{ Success => TrySuccess }
import scala.util.{ Failure => TryFailure }

import scala.deriving.Mirror.ProductOf

import scala.quoted.Expr
import scala.quoted.Quotes
import scala.quoted.Type

private[json] trait QuotesHelper {
  protected type Q <: Quotes

  protected val quotes: Q

  import quotes.reflect.*

  // format: off
  private given q: Q = quotes
  // format: on

  protected final lazy val anyValTpe: TypeRepr = TypeRepr.of[AnyVal]

  /**
   * Recursively find the sub-classes of `tpr`.
   *
   * Sub-abstract types are not listed, but their own sub-types are examined;
   * e.g. for trait `Foo`
   *
   * {{{
   * sealed trait Foo
   * case class Bar(name: String) extends Foo
   * sealed trait SubFoo extends Foo
   * case class Lorem() extends SubFoo
   * }}}
   *
   * Class `Lorem` is listed through `SubFoo`,
   * but `SubFoo` itself is not returned.
   */
  final def knownSubclasses(tpr: TypeRepr): Option[List[TypeRepr]] =
    tpr.classSymbol.flatMap { cls =>
      @annotation.tailrec
      def subclasses(
          children: List[Tree],
          out: List[TypeRepr]
      ): List[TypeRepr] = {
        val childTpr = children.headOption.collect {
          case tpd: Typed =>
            tpd.tpt.tpe

          case vd: ValDef =>
            vd.tpt.tpe

          case cd: ClassDef =>
            cd.constructor.returnTpt.tpe

        }

        childTpr match {
          case Some(child) => {
            val tpeSym = child.typeSymbol

            if (
              (tpeSym.flags.is(Flags.Abstract) &&
                tpeSym.flags.is(Flags.Sealed) &&
                !(child <:< anyValTpe)) ||
              (tpeSym.flags.is(Flags.Sealed) &&
                tpeSym.flags.is(Flags.Trait))
            ) {
              // Ignore sub-trait itself, but check the sub-sub-classes
              subclasses(tpeSym.children.map(_.tree) ::: children.tail, out)
            } else {
              subclasses(children.tail, child :: out)
            }
          }

          case _ =>
            out.reverse
        }
      }

      val types = subclasses(cls.children.map(_.tree), Nil)

      if (types.isEmpty) None else Some(types)
    }

  @annotation.tailrec
  private def withElems[U <: Product](
      tupled: Expr[U],
      fields: List[(Symbol, TypeRepr, Term => Term)],
      prepared: List[Tuple2[String, (Ref => Term) => Term]]
  ): Map[String, (Ref => Term) => Term] = fields match {
    case (sym, t, f) :: tail => {
      val elem = ValDef.let(
        Symbol.spliceOwner,
        s"tuple${sym.name}",
        Typed(f(tupled.asTerm), Inferred(t))
      )

      withElems(tupled, tail, (sym.name -> elem) :: prepared)
    }

    case _ => prepared.reverse.toMap
  }

  /**
   * @param tupled the tupled term
   * @param tupleTpe the tuple type
   * @param decls the field declarations
   */
  def withFields[U <: Product](
      tupled: Expr[U],
      tupleTpe: TypeRepr,
      decls: List[(Symbol, TypeRepr)],
      debug: String => Unit
  ): Map[String, (Term => Term) => Term] = {
    val tupleTpeSym = tupleTpe.typeSymbol

    val fields = decls.zipWithIndex.map { case ((sym, t), i) =>
      debug(
        s"// Field: ${sym.owner.owner.fullName}.${sym.name}, type = ${t.typeSymbol.fullName}, annotations = [${sym.annotations.map(_.show).mkString(", ")}]"
      )

      val fieldNme = s"_${i + 1}"

      val resolve: Term => Term = {
        val field = tupleTpeSym.declaredField(fieldNme)

        if (field == Symbol.noSymbol) {
          tupleTpeSym.declaredMethod(fieldNme) match {
            case meth :: Nil =>
              (_: Term).select(meth)

            case _ =>
              report.errorAndAbort(s"Fails to resolve field: '${tupleTpeSym.fullName}.${fieldNme}'")
          }
        } else {
          (_: Term).select(field)
        }
      }

      Tuple3(sym, t, resolve)
    }

    withElems[U](tupled, fields, List.empty)
  }

  /**
   * @tparam T the class type
   * @tparam U the type of the product corresponding to class `T`
   * @tparam R the result type (from the field operation)
   *
   * @param tpr the type for which a `ProductOf` is provided
   * @param toProduct the function to convert the input value as product `U`
   *
   * @return The tuple type + `{ v: Term => { tuple: Ref => ... } }`
   * with `v` a term of type `tpe`, and `tuple` the product created from.
   */
  def withTuple[T, U <: Product, R: Type](
      tpr: TypeRepr,
      toProduct: Expr[T => U],
  )(using
      tt: Type[T],
      ut: Type[U]
  ): Tuple2[TypeRepr, Expr[T] => (Expr[U] => Expr[R]) => Expr[R]] =
    TypeRepr.of(using ut) -> { (in: Expr[T]) =>
      { (f: (Expr[U] => Expr[R])) =>
        '{
          val tuple: U = ${ toProduct }($in)
          ${ f('{ tuple }) }
        }
      }
    }

  /**
   * Returns the elements type for `product`.
   *
   * @param owner the type representation for `T`
   */
  def productElements[T, U <: Product](
      owner: TypeRepr,
      pof: Expr[ProductOf[T]]
  ): TryResult[List[(Symbol, TypeRepr)]] = {

    @annotation.tailrec
    def elementTypes(
        max: Int,
        tpes: List[TypeRepr],
        ts: List[TypeRepr]
    ): List[TypeRepr] = tpes.headOption match {
      case Some(AppliedType(ty, a :: b :: Nil)) if ty <:< TypeRepr.of[*:] && max > 0 =>
        elementTypes(max - 1, b :: tpes.tail, a :: ts)

      case Some(AppliedType(ty, as)) if ty <:< TypeRepr.of[Tuple] && as.size <= max =>
        elementTypes(max - as.size, as ::: tpes.tail, ts)

      case Some(t) if t =:= TypeRepr.of[EmptyTuple] =>
        elementTypes(max, tpes.tail, ts)

      case Some(TypeBounds(t, _)) =>
        elementTypes(max, t :: tpes.tail, ts)

      case Some(t) =>
        elementTypes(max, tpes.tail, t :: ts)

      case _ =>
        ts.reverse
    }

    val ownerSym = owner.typeSymbol
    val paramss  = ownerSym.primaryConstructor.paramSymss.flatten.map { s => s.name -> s }.toMap

    def prepare(
        elmLabels: TypeRepr,
        elmTypes: TypeRepr
    ): List[(Symbol, TypeRepr)] = {
      val names =
        elementTypes(Int.MaxValue, List(elmLabels), List.empty).collect { case ConstantType(StringConstant(n)) => n }

      names
        .lazyZip(elementTypes(names.size, List(elmTypes), List.empty))
        .map { case (n, t) =>
          val csym = paramss.get(n)
          def fsym =
            Option(ownerSym.declaredField(n)).filterNot(_ == Symbol.noSymbol)

          val psym: Symbol = csym
            .orElse(fsym)
            .orElse {
              ownerSym.declaredMethod(n).headOption
            }
            .getOrElse(
              Symbol.newVal(
                ownerSym,
                n,
                t,
                Flags.EmptyFlags,
                Symbol.noSymbol
              )
            )

          psym -> t
        }
        .toList
    }

    val elements: Option[List[(Symbol, TypeRepr)]] = pof.asTerm.tpe match {
      case Refinement(
            Refinement(_, _, TypeBounds(t1 @ AppliedType(tycon1, _), _)),
            _,
            TypeBounds(t2 @ AppliedType(tycon2, _), _)
          ) if tycon1 <:< TypeRepr.of[Product] && tycon2 <:< TypeRepr.of[Product] =>
        Option(prepare(t2, t1))

      case Refinement(
            ref @ Refinement(_, _, TypeBounds(t1 @ TermRef(_, _), _)),
            _,
            TypeBounds(t2 @ TermRef(_, _), _)
          ) if {
            val emptyTupTpe = TypeRepr.of[EmptyTuple]
            Ref.term(t1).tpe <:< emptyTupTpe && Ref.term(t2).tpe <:< emptyTupTpe
          } =>
        None

      case pofTpe =>
        pofTpe.dealias.typeSymbol.tree match {
          case ClassDef(_, _, _, _, members) =>
            members
              .collect {
                case TypeDef(
                      n @ ("MirroredElemTypes" | "MirroredElemLabels"),
                      tt: TypeTree
                    ) if tt.tpe <:< TypeRepr.of[Product] =>
                  n -> tt.tpe
              }
              .sortBy(_._1) match {
              case (_, elmLabels) :: (_, elmTypes) :: Nil =>
                Option(prepare(elmLabels, elmTypes))

              case _ =>
                Some(List.empty[(Symbol, TypeRepr)])
            }

          case _ =>
            Some(List.empty[(Symbol, TypeRepr)])
        }
    }

    elements match {
      case Some(ls) if elements.isEmpty =>
        TryFailure(
          new IllegalArgumentException(
            s"Ill-typed ProductOf[${owner.typeSymbol.fullName}]: Fails to resolve element types and labels (bad refinement?)"
          )
        )

      case Some(ls) =>
        TrySuccess(ls)

      case _ =>
        TrySuccess(List.empty[(Symbol, TypeRepr)])
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy