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

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

/*
 * Copyright (C) 2009-2015 Typesafe Inc. 
 */
package play.api.libs.json

import scala.language.higherKinds
import scala.reflect.macros.Context
import language.experimental.macros

object JsMacroImpl {

  def formatImpl[A: c.WeakTypeTag](c: Context): c.Expr[Format[A]] =
    macroImpl[A, Format](c, "format", "inmap", reads = true, writes = true)

  def readsImpl[A: c.WeakTypeTag](c: Context): c.Expr[Reads[A]] =
    macroImpl[A, Reads](c, "read", "map", reads = true, writes = false)

  def writesImpl[A: c.WeakTypeTag](c: Context): c.Expr[Writes[A]] =
    macroImpl[A, Writes](c, "write", "contramap", reads = false, writes = true)

  def macroImpl[A, M[_]](c: Context, methodName: String, mapLikeMethod: String, reads: Boolean, writes: Boolean)(implicit atag: c.WeakTypeTag[A], matag: c.WeakTypeTag[M[A]]): c.Expr[M[A]] = {

    val nullableMethodName = s"${methodName}Nullable"
    val lazyMethodName = s"lazy${methodName.capitalize}"

    def conditionalList[T](ifReads: T, ifWrites: T): List[T] =
      (if (reads) List(ifReads) else Nil) :::
        (if (writes) List(ifWrites) else Nil)

    import c.universe._
    import c.universe.Flag._

    val companioned = weakTypeOf[A].typeSymbol
    val companionSymbol = companioned.companionSymbol
    val companionType = companionSymbol.typeSignature

    def selectTerm(qual: Tree, names: String*) =
      names.foldLeft(qual)((z, b) => Select(z, newTermName(b)))

    def selectFromRoot(names: String*) =
      selectTerm(Ident(rootMirror.RootPackage), names: _*)

    val libsPkg = selectFromRoot("play", "api", "libs")
    val jsonPkg = selectTerm(libsPkg, "json")
    val functionalSyntaxPkg = selectTerm(libsPkg, "functional", "syntax")
    val utilPkg = selectTerm(jsonPkg, "util")

    val jsPathSelect = selectTerm(jsonPkg, "JsPath")
    val readsSelect = selectTerm(jsonPkg, "Reads")
    val writesSelect = selectTerm(jsonPkg, "Writes")
    val unliftIdent = selectTerm(functionalSyntaxPkg, "unlift")
    val lazyHelperSelect = Select(utilPkg, newTypeName("LazyHelper"))

    val unapply = companionType.declaration(stringToTermName("unapply"))
    val unapplySeq = companionType.declaration(stringToTermName("unapplySeq"))
    val hasVarArgs = unapplySeq != NoSymbol

    val effectiveUnapply = Seq(unapply, unapplySeq).filter(_ != NoSymbol).headOption match {
      case None => c.abort(c.enclosingPosition, "No unapply or unapplySeq function found")
      case Some(s) => s.asMethod
    }

    val unapplyReturnTypes: Option[List[Type]] = effectiveUnapply.returnType match {
      case TypeRef(_, _, Nil) => {
        c.abort(c.enclosingPosition, s"Unapply of ${companionSymbol} has no parameters. Are you using an empty case class?")
        None
      }
      case TypeRef(_, _, args) =>
        args.head match {
          case t @ TypeRef(_, _, Nil) => Some(List(t))
          case t @ TypeRef(_, _, args) => {
            import c.universe.definitions.TupleClass
            if (!TupleClass.seq.exists(tupleSym => t.baseType(tupleSym) ne NoType)) Some(List(t))
            else if (t <:< typeOf[Product]) Some(args)
            else None
          }
          case _ => None
        }
      case _ => None
    }

    //println("Unapply return type:" + unapplyReturnTypes)

    val applies =
      companionType.declaration(stringToTermName("apply")) match {
        case NoSymbol => c.abort(c.enclosingPosition, "No apply function found")
        case s => s.asMethod.alternatives
      }

    // searches apply method corresponding to unapply
    val apply = applies.collectFirst {
      case (apply: MethodSymbol) if hasVarArgs && {
        val someApplyTypes = apply.paramss.headOption.map(_.map(_.asTerm.typeSignature))
        val someInitApply = someApplyTypes.map(_.init)
        val someApplyLast = someApplyTypes.map(_.last)
        val someInitUnapply = unapplyReturnTypes.map(_.init)
        val someUnapplyLast = unapplyReturnTypes.map(_.last)
        val initsMatch = someInitApply == someInitUnapply
        val lastMatch = (for {
          lastApply <- someApplyLast
          lastUnapply <- someUnapplyLast
        } yield lastApply <:< lastUnapply).getOrElse(false)
        initsMatch && lastMatch
      } => apply
      case (apply: MethodSymbol) if (apply.paramss.headOption.map(_.map(_.asTerm.typeSignature)) == unapplyReturnTypes) => apply
    }

    val params = apply match {
      case Some(apply) => apply.paramss.head //verify there is a single parameter group
      case None => c.abort(c.enclosingPosition, "No apply function found matching unapply parameters")
    }

    //println("apply found:" + apply)

    final case class Implicit(paramName: Name, paramType: Type, neededImplicit: Tree, isRecursive: Boolean, tpe: Type)

    val createImplicit = { (name: Name, implType: c.universe.type#Type) =>
      val (isRecursive, tpe) = implType match {
        case TypeRef(_, t, args) =>
          val isRec = args.exists(_.typeSymbol == companioned)
          // Option[_] needs special treatment because we need to use XXXOpt
          val tp = if (implType.typeConstructor <:< typeOf[Option[_]].typeConstructor) args.head else implType
          (isRec, tp)
        case TypeRef(_, t, _) =>
          (false, implType)
      }

      // builds M implicit from expected type
      val neededImplicitType = appliedType(matag.tpe.typeConstructor, tpe :: Nil)
      // infers implicit
      val neededImplicit = c.inferImplicitValue(neededImplicitType)
      Implicit(name, implType, neededImplicit, isRecursive, tpe)
    }

    val applyParamImplicits = params.map { param => createImplicit(param.name, param.typeSignature) }
    val effectiveInferredImplicits = if (hasVarArgs) {
      val varArgsImplicit = createImplicit(applyParamImplicits.last.paramName, unapplyReturnTypes.get.last)
      applyParamImplicits.init :+ varArgsImplicit
    } else applyParamImplicits

    // if any implicit is missing, abort
    val missingImplicits = effectiveInferredImplicits.collect { case Implicit(_, t, impl, rec, _) if (impl == EmptyTree && !rec) => t }
    if (missingImplicits.nonEmpty)
      c.abort(c.enclosingPosition, s"No implicit format for ${missingImplicits.mkString(", ")} available.")

    val helperMember = Select(This(tpnme.EMPTY), newTermName("lazyStuff"))
    def callHelper(target: Tree, methodName: String): Tree =
      Apply(Select(target, newTermName(methodName)), List(helperMember))
    def readsWritesHelper(methodName: String): List[Tree] =
      conditionalList(readsSelect, writesSelect).map(s => callHelper(s, methodName))

    var hasRec = false

    // combines all reads into CanBuildX
    val canBuild = effectiveInferredImplicits.map {
      case Implicit(name, t, impl, rec, tpe) =>
        // inception of (__ \ name).read(impl)
        val jspathTree = Apply(
          Select(jsPathSelect, newTermName(scala.reflect.NameTransformer.encode("\\"))),
          List(Literal(Constant(name.decoded)))
        )

        if (!rec) {
          val callMethod = if (t.typeConstructor <:< typeOf[Option[_]].typeConstructor) nullableMethodName else methodName
          Apply(
            Select(jspathTree, newTermName(callMethod)),
            List(impl)
          )
        } else {
          hasRec = true
          if (t.typeConstructor <:< typeOf[Option[_]].typeConstructor)
            Apply(
              Select(jspathTree, newTermName(nullableMethodName)),
              callHelper(Apply(jsPathSelect, Nil), lazyMethodName) :: Nil
            )
          else {
            Apply(
              Select(jspathTree, newTermName(lazyMethodName)),
              if (tpe.typeConstructor <:< typeOf[List[_]].typeConstructor)
                readsWritesHelper("list")
              else if (tpe.typeConstructor <:< typeOf[Set[_]].typeConstructor)
                readsWritesHelper("set")
              else if (tpe.typeConstructor <:< typeOf[Seq[_]].typeConstructor)
                readsWritesHelper("seq")
              else if (tpe.typeConstructor <:< typeOf[Map[_, _]].typeConstructor)
                readsWritesHelper("map")
              else List(helperMember)
            )
          }
        }
    }.reduceLeft { (acc, r) =>
      Apply(
        Select(acc, newTermName("and")),
        List(r)
      )
    }

    // builds the final M[A] using apply method
    //val applyMethod = Ident( companionSymbol )

    val applyBody = {
      val body = params.foldLeft(List[Tree]())((l, e) =>
        l :+ Ident(newTermName(e.name.encoded))
      )
      if (hasVarArgs)
        body.init :+ Typed(body.last, Ident(tpnme.WILDCARD_STAR))
      else body
    }
    val applyMethod =
      Function(
        params.foldLeft(List[ValDef]())((l, e) =>
          l :+ ValDef(Modifiers(PARAM), newTermName(e.name.encoded), TypeTree(), EmptyTree)
        ),
        Apply(
          Select(Ident(companionSymbol), newTermName("apply")),
          applyBody
        )
      )

    val unapplyMethod = Apply(
      unliftIdent,
      List(
        Select(Ident(companionSymbol), effectiveUnapply.name)
      )
    )

    // if case class has one single field, needs to use inmap instead of canbuild.apply
    val method = if (params.length > 1) "apply" else mapLikeMethod
    val finalTree = Apply(
      Select(canBuild, newTermName(method)),
      conditionalList(applyMethod, unapplyMethod)
    )
    //println("finalTree: " + finalTree)

    val importFunctionalSyntax = Import(functionalSyntaxPkg, List(ImportSelector(nme.WILDCARD, -1, null, -1)))
    if (!hasRec) {
      val block = Block(
        List(importFunctionalSyntax),
        finalTree
      )
      //println("block:"+block)
      c.Expr[M[A]](block)
    } else {
      val block = Select(
        Block(
          List(
            importFunctionalSyntax,
            ClassDef(
              Modifiers(Flag.FINAL),
              newTypeName("$anon"),
              List(),
              Template(
                List(
                  AppliedTypeTree(
                    lazyHelperSelect,
                    List(
                      Ident(matag.tpe.typeSymbol),
                      Ident(atag.tpe.typeSymbol)
                    )
                  )
                ),
                emptyValDef,
                List(
                  DefDef(
                    Modifiers(),
                    nme.CONSTRUCTOR,
                    List(),
                    List(List()),
                    TypeTree(),
                    Block(
                      List(
                        Apply(
                          Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR),
                          List()
                        )
                      ),
                      Literal(Constant(()))
                    )
                  ),
                  ValDef(
                    Modifiers(Flag.OVERRIDE | Flag.LAZY),
                    newTermName("lazyStuff"),
                    AppliedTypeTree(Ident(matag.tpe.typeSymbol), List(TypeTree(atag.tpe))),
                    finalTree
                  )
                )
              )
            )
          ),
          Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List())
        ),
        newTermName("lazyStuff")
      )

      // println("block:" + block)

      c.Expr[M[A]](block)
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy