Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (C) 2009-2018 Lightbend Inc.
*/
package play.api.libs.json
import scala.language.experimental.macros
import scala.language.higherKinds
import scala.reflect.macros.blackbox
/**
* Implementation for the JSON macro.
*/
@macrocompat.bundle class JsMacroImpl(val c: blackbox.Context) {
import c.universe._
/** Only for internal purposes */
final class Placeholder {}
/** Only for internal purposes */
object Placeholder {
implicit object Format extends OFormat[Placeholder] {
val success = JsSuccess(new Placeholder())
def reads(json: JsValue): JsResult[Placeholder] = success
def writes(pl: Placeholder) = Json.obj()
}
}
def withOptionsReadsImpl[A: c.WeakTypeTag]: c.Expr[Reads[A]] = macroImpl[A, Reads, Reads](
withOptionsConfig, "read", "map", reads = true, writes = false)
def withOptionsWritesImpl[A: c.WeakTypeTag]: c.Expr[OWrites[A]] = macroImpl[A, OWrites, Writes](
withOptionsConfig, "write", "contramap", reads = false, writes = true)
def withOptionsFormatImpl[A: c.WeakTypeTag]: c.Expr[OFormat[A]] = macroImpl[A, OFormat, Format](
withOptionsConfig, "format", "inmap", reads = true, writes = true)
def implicitConfigReadsImpl[A: c.WeakTypeTag]: c.Expr[Reads[A]] = macroImpl[A, Reads, Reads](
implicitOptionsConfig, "read", "map", reads = true, writes = false)
def implicitConfigWritesImpl[A: c.WeakTypeTag]: c.Expr[OWrites[A]] = macroImpl[A, OWrites, Writes](
implicitOptionsConfig, "write", "contramap", reads = false, writes = true)
def implicitConfigFormatImpl[A: c.WeakTypeTag]: c.Expr[OFormat[A]] = macroImpl[A, OFormat, Format](
implicitOptionsConfig, "format", "inmap", reads = true, writes = true)
// because binary compatibility...
@deprecated("Use implicitConfigReadsImpl or withOptionsReadsImpl", "2.6.6")
protected def readsImpl[A: c.WeakTypeTag, O: c.WeakTypeTag]: c.Expr[Reads[A]] = macroImpl[A, Reads, Reads](
implicitOptionsConfig, "read", "map", reads = true, writes = false)
@deprecated("Use implicitConfigWritesImpl or withOptionsWritesImpl", "2.6.6")
protected def writesImpl[A: c.WeakTypeTag, O: c.WeakTypeTag]: c.Expr[OWrites[A]] = macroImpl[A, OWrites, Writes](
implicitOptionsConfig, "write", "contramap", reads = false, writes = true)
@deprecated("Use implicitConfigFormatImpl or withOptionsFormatImpl", "2.6.6")
protected def formatImpl[A: c.WeakTypeTag, O: c.WeakTypeTag]: c.Expr[OFormat[A]] = macroImpl[A, OFormat, Format](
implicitOptionsConfig, "format", "inmap", reads = true, writes = true)
private def withOptionsConfig: c.Expr[JsonConfiguration] = {
c.Expr[JsonConfiguration](c.typecheck(q"${c.prefix}.config"))
}
private def implicitOptionsConfig: c.Expr[JsonConfiguration] = {
c.Expr[JsonConfiguration](c.inferImplicitValue(c.typeOf[JsonConfiguration]))
}
/**
* Generic implementation of the macro.
*
* The reads/writes flags are used to say whether a reads/writes is being generated (in the case format,
* these are both true). This is also used to determine what arguments should be passed to various
* functions, for example, if the apply, unapply, or both should be passed to the functional builder apply
* method.
*
* @param config The configuration tree
* @param methodName The name of the method on JsPath that gets called, ie, read/write/format
* @param mapLikeMethod The method that's used to map the type of thing being built, used in case there is
* only one field in the case class.
* @param reads Whether we should generate a reads.
* @param writes Whether we should generate a writes
* @param atag The class of the type we're generating a reads/writes/format for.
* @param matag The class of the reads/writes/format.
* @param natag The class of the reads/writes/format.
*/
private def macroImpl[A, M[_], N[_]](config: c.Expr[JsonConfiguration], methodName: String, mapLikeMethod: String,
reads: Boolean, writes: Boolean)(implicit atag: c.WeakTypeTag[A], matag: c.WeakTypeTag[M[A]], natag: c.WeakTypeTag[N[A]]): c.Expr[M[A]] = {
def debug(msg: => String): Unit = {
if (debugEnabled) {
c.info(c.enclosingPosition, msg, force = false)
}
}
// All these can be sort of thought as imports
// that can then be used later in quasi quote interpolation
val libs = q"_root_.play.api.libs"
val json = q"$libs.json"
val syntax = q"$libs.functional.syntax"
val JsPath = q"$json.JsPath"
val unlift = q"$syntax.unlift"
val atpe = atag.tpe.dealias
// ---
// Now we find all the implicits that we need
final case class Implicit(
paramType: Type,
neededImplicit: Tree,
tpe: Type,
selfRef: Boolean
)
val optTpeCtor = typeOf[Option[_]].typeConstructor
val forwardName = TermName(c.freshName("forward"))
// MacroOptions
val options = config.actualType.member(TypeName("Opts")).asType.toTypeIn(config.actualType)
def hasOption[Flag: c.TypeTag]: Boolean = options <:< typeOf[Flag]
/* Utility for implicit resolution - can hardly be moved outside
* (due to dependent types).
*
* @param resolvedType Per each symbol of the type parameters,
* which type is bound to
*/
class ImplicitResolver(resolvedType: Type => Type) {
// The placeholder type
private val PlaceholderType: Type = typeOf[Placeholder]
/* Refactor the input types, by replacing any type matching the `filter`,
* by the given `replacement`.
*/
@annotation.tailrec
private def refactor(in: List[Type], base: TypeSymbol, out: List[Type], tail: List[(List[Type], TypeSymbol, List[Type])], filter: Type => Boolean, replacement: Type, altered: Boolean): (Type, Boolean) = in match {
case tpe :: ts => resolvedType(tpe) match {
case t if (filter(t)) =>
refactor(ts, base, (replacement :: out), tail,
filter, replacement, true)
case TypeRef(_, sym, as) if as.nonEmpty =>
refactor(as, sym.asType, List.empty, (ts, base, out) :: tail,
filter, replacement, altered)
case t => refactor(ts, base, (t :: out), tail,
filter, replacement, altered)
}
case _ => {
val tpe = appliedType(base.toTypeConstructor, out.reverse)
tail match {
case (x, y, more) :: ts =>
refactor(x, y, (tpe :: more), ts, filter, replacement, altered)
case _ => tpe -> altered
}
}
}
/**
* Replaces any reference to the type itself by the Placeholder type.
* @return the normalized type + whether any self reference has been found
*/
private def normalized(subject: Type, tpe: Type): (Type, Boolean) =
resolvedType(tpe) match {
case t if (t =:= subject) => PlaceholderType -> true
case TypeRef(_, sym, args) if args.nonEmpty =>
refactor(args, sym.asType, List.empty, List.empty,
_ =:= subject, PlaceholderType, false)
case t => t.dealias -> false
}
private class ImplicitTransformer[T](
subject: Type
) extends Transformer {
/* Restores reference to the type itself when Placeholder is found. */
private def denormalized(ptype: Type): Type = ptype match {
case PlaceholderType => subject
case TypeRef(_, sym, args) if args.nonEmpty =>
refactor(args, sym.asType, List.empty, List.empty,
_ == PlaceholderType, subject, false)._1
case _ => ptype
}
override def transform(tree: Tree): Tree = tree match {
case tt: TypeTree =>
super.transform(TypeTree(denormalized(tt.tpe).dealias))
case Select(Select(This(TypeName("JsMacroImpl")), t), sym) if (
t.toString == "Placeholder" && sym.toString == "Format"
) => super.transform(q"$forwardName")
case _ => super.transform(tree)
}
}
def createImplicit(subject: Type, ctag: Type)(ptype: Type): Implicit = {
val (isOpt, tpe) = ptype.dealias match {
case t @ TypeRef(_, _, targ :: _) if t.typeConstructor <:< optTpeCtor =>
// Option[_] needs special treatment because we need to use XXXOpt
true -> targ.dealias
case t =>
false -> t
}
if (isOpt) { // Option special case was applied
val it = c.inferImplicitValue(
appliedType(ctag.typeConstructor, ptype), silent = true)
if (it != EmptyTree) {
debug("Ignoring instance of ${ctag.typeSymbol.fullName} for ${ptype} (${it.pos.source}:${it.pos.line}:${it.pos.column}); Alias for Option[$tpe] will be handled by the nullable operations.")
}
}
val (ntpe, selfRef) = normalized(subject, tpe)
val ptpe = resolvedType(ntpe)
// infers implicit
val neededImplicitType = appliedType(ctag.typeConstructor, ptpe)
val tx = new ImplicitTransformer(subject)
val neededImplicit = if (!selfRef) {
c.inferImplicitValue(neededImplicitType)
} else c.untypecheck(
// Reset the type attributes on the refactored tree for the implicit
tx.transform(
c.inferImplicitValue(neededImplicitType)
)
)
Implicit(ptype.dealias, neededImplicit, tpe.dealias, selfRef)
}
}
// ---
// Utility about apply/unapply
class CaseClass[T](tpeArgs: List[Type])(implicit tag: WeakTypeTag[T]) {
// Common definitions
private val companioned = weakTypeOf[T].typeSymbol
private val companionObject = companioned.companion
private val companionType = companionObject.typeSignature
private val unapply = companionType.decl(TermName("unapply"))
private val unapplySeq = companionType.decl(TermName("unapplySeq"))
private val hasVarArgs = unapplySeq != NoSymbol
// Returns the unapply symbol
private lazy val effectiveUnapply: MethodSymbol =
Seq(unapply, unapplySeq).find(_ != NoSymbol) match {
case None => c.abort(c.enclosingPosition, s"No unapply or unapplySeq function found for $companioned: $unapply / $unapplySeq")
case Some(s) => s.asMethod
}
private lazy val unapplyReturnTypes: Option[List[Type]] =
effectiveUnapply.returnType match {
case TypeRef(_, _, Nil) => {
c.abort(c.enclosingPosition, s"Unapply of ${companioned.fullName} 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(t.baseType(_) ne NoType)) Some(List(t))
else if (t <:< typeOf[Product]) Some(args)
else None
case _ => None
}
case _ => None
}
/* Deep check for type compatibility */
@annotation.tailrec
private def conforms(types: Seq[(Type, Type)]): Boolean =
types.headOption match {
case Some((TypeRef(NoPrefix, a, _),
TypeRef(NoPrefix, b, _))) => { // for generic parameter
if (a.fullName != b.fullName) {
debug(s"Type symbols are not compatible: $a != $b")
false
} else conforms(types.tail)
}
case Some((a, b)) if (a.typeArgs.size != b.typeArgs.size) => {
debug(s"Type parameters are not matching: $a != $b")
false
}
case Some((a, b)) if a.typeArgs.isEmpty =>
if (a =:= b) conforms(types.tail) else {
debug(s"Types are not compatible: $a != $b")
false
}
case Some((a, b)) if (a.baseClasses != b.baseClasses) => {
debug(s"Generic types are not compatible: $a != $b")
false
}
case Some((a, b)) =>
conforms(new scala.runtime.Tuple2Zipped(a.typeArgs, b.typeArgs) ++: types.tail)
case _ => true
}
// The apply methods for the object
private lazy val applies: List[MethodSymbol] =
companionType.decl(TermName("apply")) match {
case NoSymbol => c.abort(
c.enclosingPosition,
s"No apply function found for ${companioned.fullName}"
)
case s => s.asTerm.alternatives.flatMap { apply =>
val meth = apply.asMethod
meth.paramLists match {
case ps :: pss if (
ps.nonEmpty && pss.forall {
case p :: _ => p.isImplicit
case _ => false
}
) => List(meth)
case _ => List.empty
}
}
}
/**
* Returns whether the case class is valid:
* has at least only "apply" with a non empty list of parameters.
*/
@inline def validCaseClass: (Symbol, Boolean) =
companioned -> !applies.isEmpty
// Find an apply method that matches the unapply
private val maybeApply: Option[MethodSymbol] = applies.collectFirst {
case (apply: MethodSymbol) if hasVarArgs && {
// Option[List[c.universe.Type]]
val someApplyTypes = apply.paramLists.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 {
val applyParams = apply.paramLists.headOption.
toList.flatMap(identity).map(_.typeSignature)
val unapplyParams = unapplyReturnTypes.toList.flatMap(identity)
(applyParams.size == unapplyParams.size &&
conforms(new scala.runtime.Tuple2Zipped(applyParams, unapplyParams).toSeq))
} => apply
}
// Parameters symbols -> Function tree
lazy val applyFunction: Option[(Tree, List[TypeSymbol], List[Symbol], List[Option[Tree]])] =
maybeApply.flatMap { app =>
app.paramLists.headOption.map { params =>
val defaultValues = params.map(_.asTerm).zipWithIndex map {
case (p, i) =>
if (!p.isParamWithDefault) None else {
val getter = TermName("apply$default$" + (i + 1))
Some(q"$companionObject.$getter")
}
}
val tree = if (hasVarArgs) {
val applyParams = params.foldLeft(List.empty[Tree]) { (l, e) =>
l :+ Ident(TermName(e.name.encodedName.toString))
}
val vals = params.foldLeft(List.empty[Tree])((l, e) =>
// Let type inference infer the type by using the empty type
l :+ q"val ${TermName(e.name.encodedName.toString)}: ${TypeTree()}")
q"(..$vals) => $companionObject.apply(..${applyParams.init}, ${applyParams.last}: _*)"
} else if (tpeArgs.isEmpty) {
q"$companionObject.apply _"
} else q"$companionObject.apply[..$tpeArgs] _"
(tree, app.typeParams.map(_.asType), params, defaultValues)
}
}
lazy val unapplyFunction: Tree = if (tpeArgs.isEmpty) {
q"$unlift($companionObject.$effectiveUnapply)"
} else q"$unlift($companionObject.$effectiveUnapply[..$tpeArgs])"
@inline private def params: List[(Name, Type)] = applyFunction match {
case Some((_, _, ps, _)) => {
val base = if (hasVarArgs) ps.init else ps
val defs = base.map { p => p.name -> p.typeSignature }.toList
val end = if (!hasVarArgs) List.empty[(Name, Type)] else {
List(ps.last.name -> unapplyReturnTypes.get.last)
}
defs ++ end
}
case _ => List.empty
}
def implicits(resolver: ImplicitResolver): List[(Name, Implicit)] = {
val createImplicit = resolver.createImplicit(
atpe, natag.tpe) _
val effectiveImplicits = params.map {
case (n, t) => n -> createImplicit(t)
}
// if any implicit is missing, abort
val missingImplicits = effectiveImplicits.collect {
case (_, Implicit(t, EmptyTree /* ~= not found */ , _, _)) => t
}
if (missingImplicits.nonEmpty) {
c.abort(
c.enclosingPosition,
s"No instance of ${natag.tpe.typeSymbol.fullName} is available for ${missingImplicits.map(prettyType(_)).mkString(", ")} in the implicit scope (Hint: if declared in the same file, make sure it's declared before)"
)
}
effectiveImplicits
}
lazy val boundTypes: Map[String, Type] =
applyFunction.fold(Map.empty[String, Type]) {
case (_, tparams, _, _) => tparams.zip(tpeArgs).map {
case (sym, ty) => sym.fullName -> ty
}(scala.collection.breakOut)
}
// To print the implicit types in the compiler messages
private def prettyType(t: Type): String =
boundTypes.getOrElse(t.typeSymbol.fullName, t).dealias match {
case TypeRef(_, base, args) if args.nonEmpty => s"""${base.asType.fullName}[${args.map(prettyType(_)).mkString(", ")}]"""
case t => t.typeSymbol.fullName
}
}
// ---
def directKnownSubclasses: Option[List[Type]] = {
// Workaround for SI-7046: https://issues.scala-lang.org/browse/SI-7046
val tpeSym = atag.tpe.typeSymbol.asClass
@annotation.tailrec
def allSubclasses(path: Traversable[Symbol], subclasses: Set[Type]): Set[Type] = path.headOption match {
case Some(cls: ClassSymbol) if (
tpeSym != cls && cls.selfType.baseClasses.contains(tpeSym)
) => {
val newSub: Set[Type] = if (!cls.isCaseClass) {
c.warning(c.enclosingPosition, s"cannot handle class ${cls.fullName}: no case accessor")
Set.empty
} else if (!cls.typeParams.isEmpty) {
c.warning(c.enclosingPosition, s"cannot handle class ${cls.fullName}: type parameter not supported")
Set.empty
} else Set(cls.selfType)
allSubclasses(path.tail, subclasses ++ newSub)
}
case Some(o: ModuleSymbol) if (
o.companion == NoSymbol && // not a companion object
tpeSym != c && o.typeSignature.baseClasses.contains(tpeSym)
) => {
val newSub: Set[Type] = if (!o.moduleClass.asClass.isCaseClass) {
c.warning(c.enclosingPosition, s"cannot handle object ${o.fullName}: no case accessor")
Set.empty
} else Set(o.typeSignature)
allSubclasses(path.tail, subclasses ++ newSub)
}
case Some(o: ModuleSymbol) if (
o.companion == NoSymbol // not a companion object
) => allSubclasses(path.tail, subclasses)
case Some(_) => allSubclasses(path.tail, subclasses)
case _ => subclasses
}
if (tpeSym.isSealed && tpeSym.isAbstract) {
Some(allSubclasses(tpeSym.owner.typeSignature.decls, Set.empty).toList)
} else None
}
// --- Sub implementations
val readsType = c.typeOf[Reads[_]]
def macroSealedFamilyImpl(subTypes: List[Type]): c.Expr[M[A]] = {
if (subTypes.isEmpty) {
c.abort(c.enclosingPosition, s"Sealed trait ${atpe} is not supported: no known subclasses")
}
val typeNaming = (_: Type).typeSymbol.fullName
def readLambda: Tree = {
val resolver = new ImplicitResolver({ orig: Type => orig })
val cases = Match(q"dis", (
cq"""_ => $json.JsError("error.invalid")""" :: (
subTypes.foldLeft(List.empty[CaseDef]) { (out, t) =>
val rtpe = appliedType(readsType, List(t))
val reader = resolver.createImplicit(
atpe, rtpe
)(t).neededImplicit
if (reader.isEmpty) {
c.abort(
c.enclosingPosition,
s"No instance of Reads is available for ${t.typeSymbol.fullName} in the implicit scope (Hint: if declared in the same file, make sure it's declared before)"
)
}
cq"${typeNaming(t)} => $reader.reads(vjs)" :: out
}
)
).reverse)
q"""(_: $json.JsValue) match {
case obj @ $json.JsObject(_) => obj.value.get("_type") match {
case Some(tjs) => {
val vjs = obj.value.get("_value").getOrElse(obj)
tjs.validate[String].flatMap { dis => $cases }
}
case _ => $json.JsError($JsPath \ "_type", "error.missing.path")
}
case _ => $json.JsError("error.expected.jsobject")
}"""
}
def writeLambda: Tree = {
val owner = c.internal.enclosingOwner
if (!owner.isTerm) {
c.abort(
c.enclosingPosition,
"the macro must be used to generate the body of a term"
)
}
// ---
val term = owner.asTerm
val cases = Match(q"v", subTypes.map { t =>
// Use `implicitly` rather than `ImplicitResolver.createImplicit`,
// due to the implicit/contravariance workaround
cq"""x: $t => {
val xjs = implicitly[$json.Writes[$t]].writes(x)
@inline def jso = xjs match {
case xo @ $json.JsObject(_) => xo
case jsv => $json.JsObject(Seq("_value" -> jsv))
}
jso + ("_type" -> $json.JsString(${typeNaming(t)}))
}"""
})
// Use shadowing to eliminate the generated term itself
// from the implicit scope, due to the contravariant/implicit issue:
// https://groups.google.com/forum/#!topic/scala-language/ZE83TvSWpT4
val shadowName = TermName(term.name.decodedName.toString.trim)
// DO NOT directly use `term.name` as for some reason,
// the TermName is provided within Scala.JS is appended with a final ' '
q"""{ v: ${atpe} =>
def ${shadowName}: $json.OWrites[${atpe}] =
sys.error("Invalid implicit resolution")
$cases
}"""
}
def tree = methodName match {
case "read" => q"$json.Reads[${atpe}]($readLambda)"
case "write" => q"$json.OWrites[${atpe}]($writeLambda)"
case _ => q"$json.OFormat($readLambda, $writeLambda)"
}
c.Expr[M[A]](tree)
}
def macroCaseImpl(tpeArgs: List[Type]): c.Expr[M[A]] = {
val utility = new CaseClass[A](tpeArgs)
val (companioned, isCase) = utility.validCaseClass
if (!isCase) {
c.abort(
c.enclosingPosition,
s"Type ${companioned.fullName} is not valid: must be a case class with at least one non empty list of parameter"
)
}
val (applyFunction, tparams, params, defaultValues) = utility.applyFunction match {
case Some(info) => info
case _ => c.abort(
c.enclosingPosition,
s"No apply function found matching unapply parameters"
)
}
// ---
// combines all reads into CanBuildX
val resolver = new ImplicitResolver({
import utility.boundTypes
{ orig: Type =>
boundTypes.getOrElse(orig.typeSymbol.fullName, orig)
}
})
val defaultValueMap: Map[Name, Tree] =
if (!hasOption[Json.DefaultValues]) Map.empty else {
(params, defaultValues).zipped.collect {
case (p, Some(dv)) => p.name.encodedName -> dv
}(scala.collection.breakOut)
}
val resolvedImplicits = utility.implicits(resolver)
val canBuild = resolvedImplicits.map {
case (name, Implicit(pt, impl, _, _)) =>
// Equivalent to __ \ "name", but uses a naming scheme
// of (String) => (String) to find the correct "name"
val cn = c.Expr[String](
q"$config.naming(${name.decodedName.toString})"
)
val jspathTree = q"$JsPath \ $cn"
val isOption = pt.typeConstructor <:< optTpeCtor
val defaultValue = // not applicable for 'write' only
defaultValueMap.get(name).filter(_ => methodName != "write")
// - If we're an default value, invoke the withDefault version
// - If we're an option with default value,
// invoke the nullableWithDefault version
(isOption, defaultValue) match {
case (true, Some(v)) =>
val c = TermName(s"${methodName}NullableWithDefault")
q"$jspathTree.$c($v)($impl)"
case (true, _) =>
val c = TermName(s"${methodName}Handler")
q"$config.optionHandlers.$c($jspathTree)($impl)"
case (false, Some(v)) =>
val c = TermName(s"${methodName}WithDefault")
q"$jspathTree.$c($v)($impl)"
case _ =>
q"$jspathTree.${TermName(methodName)}($impl)"
}
}.reduceLeft[Tree] { (acc, r) =>
q"$acc.and($r)"
}
val multiParam = params.length > 1
// if case class has one single field, needs to use map/contramap/inmap on the Reads/Writes/Format instead of
// canbuild.apply
val applyOrMap = TermName(if (multiParam) "apply" else mapLikeMethod)
// Helper function to create parameter lists for function invocations
// based on whether this is a reads, writes or both.
def conditionalList[T](ifReads: T, ifWrites: T): List[T] =
(if (reads) List(ifReads) else Nil) :::
(if (writes) List(ifWrites) else Nil)
val syntaxImport = if (!multiParam && !writes) q"" else q"import $syntax._"
@inline def buildCall = q"$canBuild.$applyOrMap(..${conditionalList(applyFunction, utility.unapplyFunction)})"
def readResult =
if (multiParam) q"underlying.reads(obj)"
else q"underlying.flatMap[${atpe}]($json.Reads.pure(_)).reads(obj)"
val canBuildCall = methodName match {
case "read" => {
q"""{
val underlying = $buildCall
$json.Reads[${atpe}] {
case obj @ $json.JsObject(_) => $readResult
case _ => $json.JsError("error.expected.jsobject")
}
}"""
}
case "format" => {
q"""{
val underlying = $buildCall
val rfn: $json.JsValue => $json.JsResult[${atpe}] = {
case obj @ $json.JsObject(_) => $readResult
case _ => $json.JsError("error.expected.jsobject")
}
$json.OFormat[${atpe}](rfn, underlying.writes _)
}"""
}
case _ => buildCall
}
val finalTree =
if (!resolvedImplicits.exists(_._2.selfRef)) {
// there is no self reference
q"""
$syntaxImport
$canBuildCall
"""
} else {
// Has nested reference to the same type
val forward: Tree = methodName match {
case "read" =>
q"$json.Reads[${atpe}](instance.reads(_))"
case "write" =>
q"$json.OWrites[${atpe}](instance.writes(_))"
case _ =>
q"$json.OFormat[${atpe}](instance.reads(_), instance.writes(_))"
}
val forwardCall =
q"private val $forwardName = $forward"
val generated = TypeName(c.freshName("Generated"))
q"""
final class $generated() {
// wrap there for self reference
$syntaxImport
$forwardCall
def instance: ${matag.tpe.typeSymbol}[${atpe}] = $canBuildCall
}
new $generated().instance
"""
}
debug(showCode(finalTree))
c.Expr[M[A]](finalTree)
}
def caseObjectImpl: c.Expr[M[A]] = {
def reader =
q"""
$json.Reads[${atpe}] {
case obj @ $json.JsObject(_) => $json.JsSuccess(${atpe.termSymbol})
case _ => $json.JsError("error.expected.jsobject")
}
"""
def writer = q"$json.OWrites[$atpe]{_ => $json.JsObject.empty }"
val tree = methodName match {
case "read" => reader
case "write" => writer
case _ => q"""$json.OFormat[$atpe]($reader, $writer)"""
}
debug(showCode(tree))
c.Expr[M[A]](tree)
}
// ---
directKnownSubclasses match {
case Some(subTypes) => macroSealedFamilyImpl(subTypes)
case _ =>
atpe match {
case _: SingletonType => caseObjectImpl
case TypeRef(_, _, args) => macroCaseImpl(args)
case _ =>
c.abort(
c.enclosingPosition,
s"Type ${atpe.typeSymbol.fullName} is not a valid case class or an object"
)
}
}
}
private lazy val debugEnabled =
Option(System.getProperty("play.json.macro.debug")).
filterNot(_.isEmpty).map(_.toLowerCase).exists { v =>
"true".equals(v) || v.substring(0, 1) == "y"
}
}