|
Play Framework/Scala example source code file (JsMacroImpl.scala)
The JsMacroImpl.scala Play Framework example source code
/*
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
*/
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
val libsPkg = Select(Select(Ident(newTermName("play")), newTermName("api")), newTermName("libs"))
val jsonPkg = Select(libsPkg, newTermName("json"))
val functionalSyntaxPkg = Select(Select(libsPkg, newTermName("functional")), newTermName("syntax"))
val utilPkg = Select(jsonPkg, newTermName("util"))
val jsPathSelect = Select(jsonPkg, newTermName("JsPath"))
val readsSelect = Select(jsonPkg, newTermName("Reads"))
val writesSelect = Select(jsonPkg, newTermName("Writes"))
val unliftIdent = Select(functionalSyntaxPkg, newTermName("unlift"))
val lazyHelperSelect = Select(utilPkg, newTypeName("LazyHelper"))
val unapply =
companionType.declaration(stringToTermName("unapply")) match {
case NoSymbol => c.abort(c.enclosingPosition, "No unapply function found")
case s => s.asMethod
}
val unapplyReturnTypes = unapply.returnType match {
case TypeRef(_, _, Nil) =>
c.abort(c.enclosingPosition, s"Unapply of ${companionSymbol} has no parameters. Are you using an empty case class?")
case TypeRef(_, _, args) =>
args.head match {
case t @ TypeRef(_, _, Nil) => Some(List(t))
case t @ TypeRef(_, _, args) =>
if (t <:< typeOf[Option[_]]) Some(List(t))
else if (t <:< typeOf[Seq[_]]) Some(List(t))
else if (t <:< typeOf[Set[_]]) Some(List(t))
else if (t <:< typeOf[Map[_, _]]) Some(List(t))
else if (t <:< typeOf[Product]) Some(args)
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 (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 inferredImplicits = params.map { param =>
val implType = param.typeSignature
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(param.name, implType, neededImplicit, isRecursive, tpe)
}
//println("Found implicits:"+inferredImplicits)
// if any implicit is missing, abort
val missingImplicits = inferredImplicits.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 = inferredImplicits.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 applyMethod =
Function(
params.foldLeft(List[ValDef]())((l, e) =>
l :+ ValDef(Modifiers(PARAM), newTermName(e.name.encoded), TypeTree(), EmptyTree)
),
Apply(
Select(Ident(companionSymbol), newTermName("apply")),
params.foldLeft(List[Tree]())((l, e) =>
l :+ Ident(newTermName(e.name.encoded))
)
)
)
val unapplyMethod = Apply(
unliftIdent,
List(
Select(Ident(companionSymbol), unapply.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(
importFunctionalSyntax,
finalTree
)
//println("block:"+block)
c.Expr[M[A]](block)
} else {
val helper = newTermName("helper")
val helperVal = ValDef(
Modifiers(),
helper,
Ident(weakTypeOf[play.api.libs.json.util.LazyHelper[M, A]].typeSymbol),
Apply(Ident(newTermName("LazyHelper")), List(finalTree))
)
val block = Select(
Block(
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(
Apply(
Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR),
List()
)
)
),
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)
}
}
}
Other Play Framework source code examplesHere is a short list of links related to this Play Framework JsMacroImpl.scala source code file: |
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.