Skip to content

Commit bc570f2

Browse files
cchantepcchantep
authored andcommitted
WIP
1 parent d3e15ce commit bc570f2

File tree

4 files changed

+56
-85
lines changed

4 files changed

+56
-85
lines changed

play-json/shared/src/main/scala-2/play/api/libs/json/JsMacroImpl.scala

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,9 @@ class JsMacroImpl(val c: blackbox.Context) {
170170
selfRef: Boolean
171171
)
172172

173-
val optTpeCtor = typeOf[Option[_]].typeConstructor
173+
val optTpeCtor = typeOf[Option[_]].typeConstructor
174174
val forwardPrefix = "play_jsmacro"
175-
val forwardName = TermName(c.freshName(forwardPrefix))
175+
val forwardName = TermName(c.freshName(forwardPrefix))
176176

177177
// MacroOptions
178178
val options = config.actualType.member(TypeName("Opts")).asType.toTypeIn(config.actualType)
@@ -691,10 +691,11 @@ class JsMacroImpl(val c: blackbox.Context) {
691691
val (applyFunction, tparams, params, defaultValues) = utility.applyFunction match {
692692
case Some(info) => info
693693

694-
case _ => c.abort(
695-
c.enclosingPosition,
696-
"No apply function found matching unapply parameters"
697-
)
694+
case _ =>
695+
c.abort(
696+
c.enclosingPosition,
697+
"No apply function found matching unapply parameters"
698+
)
698699
}
699700

700701
// ---
@@ -731,36 +732,38 @@ class JsMacroImpl(val c: blackbox.Context) {
731732
val defaultValue = // not applicable for 'write' only
732733
defaultValueMap.get(name).filter(_ => methodName != "write")
733734

734-
val resolvedImpl = {
735-
val implTpeName = Option(impl.tpe).fold("null")(_.toString)
735+
val resolvedImpl = {
736+
val implTpeName = Option(impl.tpe).fold("null")(_.toString)
736737

737-
if (implTpeName.startsWith(forwardPrefix) ||
738-
(implTpeName.startsWith("play.api.libs.json") &&
739-
!implTpeName.contains("MacroSpec"))) {
740-
impl // Avoid extra check for builtin formats
741-
} else {
742-
q"""_root_.java.util.Objects.requireNonNull($impl, "Invalid implicit resolution (forward reference?) for '" + $cn + "': " + ${implTpeName})"""
743-
}
744-
}
738+
if (implTpeName.startsWith(forwardPrefix) ||
739+
(implTpeName.startsWith("play.api.libs.json") &&
740+
!(implTpeName.startsWith("play.api.libs.json.Functional") ||
741+
implTpeName.contains("MacroSpec")))) {
742+
impl // Avoid extra check for builtin formats
743+
} else {
744+
q"""_root_.java.util.Objects.requireNonNull($impl, "Implicit value for '" + $cn + "' was null (forward reference?): " + ${implTpeName})"""
745+
}
746+
}
745747

746748
// - If we're an default value, invoke the withDefault version
747749
// - If we're an option with default value,
748750
// invoke the WithDefault version
749751
(isOption, defaultValue) match {
750752
case (true, Some(v)) =>
751753
val c = TermName(s"${methodName}HandlerWithDefault")
752-
q"$config.optionHandlers.$c($jspathTree, $v)($resolveImpl)"
754+
q"$config.optionHandlers.$c($jspathTree, $v)($resolvedImpl)"
753755

754-
case (true, _) =>
756+
case (true, _) => {
755757
val c = TermName(s"${methodName}Handler")
756-
q"$config.optionHandlers.$c($jspathTree)($resolveImpl)"
758+
q"$config.optionHandlers.$c($jspathTree)($impl)"
759+
}
757760

758761
case (false, Some(v)) =>
759762
val c = TermName(s"${methodName}WithDefault")
760-
q"$jspathTree.$c($v)($resolveImpl)"
763+
q"$jspathTree.$c($v)($resolvedImpl)"
761764

762765
case _ =>
763-
q"$jspathTree.${TermName(methodName)}($resolveImpl)"
766+
q"$jspathTree.${TermName(methodName)}($resolvedImpl)"
764767
}
765768
}
766769
.reduceLeft[Tree] { (acc, r) =>

play-json/shared/src/main/scala/play/api/libs/json/Writes.scala

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,7 @@ object OWrites extends PathWrites with ConstraintWrites {
144144
/**
145145
* Returns an instance which uses `f` as [[OWrites.writes]] function.
146146
*/
147-
def apply[A](f: A => JsObject): OWrites[A] = new OWrites[A] {
148-
def writes(a: A): JsObject = f(a)
149-
}
147+
def apply[A](f: A => JsObject): OWrites[A] = new FunctionalOWrites[A](f)
150148

151149
/**
152150
* Transforms the resulting [[JsObject]] using the given function,
@@ -159,6 +157,12 @@ object OWrites extends PathWrites with ConstraintWrites {
159157
OWrites[A] { a =>
160158
f(a, w.writes(a))
161159
}
160+
161+
// ---
162+
163+
private[json] final class FunctionalOWrites[A](w: A => JsObject) extends OWrites[A] {
164+
def writes(a: A): JsObject = w(a)
165+
}
162166
}
163167

164168
/**
@@ -177,9 +181,7 @@ object Writes extends PathWrites with ConstraintWrites with DefaultWrites with G
177181
/**
178182
* Returns an instance which uses `f` as [[Writes.writes]] function.
179183
*/
180-
def apply[A](f: A => JsValue): Writes[A] = new Writes[A] {
181-
def writes(a: A): JsValue = f(a)
182-
}
184+
def apply[A](f: A => JsValue): Writes[A] = new FunctionalWrites[A](f)
183185

184186
/**
185187
* Transforms the resulting [[JsValue]] using the given function,
@@ -194,6 +196,12 @@ object Writes extends PathWrites with ConstraintWrites with DefaultWrites with G
194196
Writes[A] { a =>
195197
f(a, w.writes(a))
196198
}
199+
200+
// ---
201+
202+
private[json] final class FunctionalWrites[A](w: A => JsValue) extends Writes[A] {
203+
def writes(a: A): JsValue = w(a)
204+
}
197205
}
198206

199207
/**

play-json/shared/src/test/scala-2/play/api/libs/json/JsonExtensionScala2Spec.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,10 @@ class JsonExtensionScala2Spec extends AnyWordSpec with Matchers {
127127
implicit val jsonConfiguration: JsonConfiguration.Aux[Json.WithDefaultValues] =
128128
JsonConfiguration[Json.WithDefaultValues](optionHandlers = OptionHandlers.WritesNull)
129129
val formatter = Json.format[OptionalWithDefault]
130-
formatter.writes(OptionalWithDefault()).mustEqual(Json.obj("props" -> JsNull))
131-
formatter.writes(OptionalWithDefault(Some("foo"))).mustEqual(Json.obj("props" -> "foo"))
130+
131+
formatter.writes(OptionalWithDefault()) mustEqual Json.obj("props" -> JsNull)
132+
133+
formatter.writes(OptionalWithDefault(Some("foo"))) mustEqual Json.obj("props" -> "foo")
132134

133135
formatter.reads(Json.obj()).mustEqual(JsSuccess(OptionalWithDefault()))
134136
formatter.reads(Json.obj("props" -> JsNull)).mustEqual(JsSuccess(OptionalWithDefault(None)))

play-json/shared/src/test/scala/play/api/libs/json/MacroSpec.scala

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -151,39 +151,29 @@ class MacroSpec extends AnyWordSpec with Matchers with org.scalatestplus.scalach
151151
val jsLorem = Json.obj("age" -> 11, "ipsum" -> Json.obj("bar" -> "foo"))
152152

153153
try {
154-
jsLorem.validate[Lorem[Simple]]
155-
} catch {
156-
case NonFatal(npe: NullPointerException) => {
157-
val expected = "Invalid implicit resolution"
158-
npe.getMessage.take(expected.size) mustEqual expected
154+
try {
155+
jsLorem.validate[Lorem[Simple]]
156+
} catch {
157+
case e: Throwable if e.getCause != null =>
158+
// scalatest ExceptionInInitializerError
159+
throw e.getCause
159160
}
160-
161-
case NonFatal(cause) => throw cause
162-
}
163-
}
164-
165-
"fails due to forward reference to Format" in {
166-
implicit def format: Format[Lorem[Simple]] =
167-
InvalidForwardResolution.simpleLoremFormat
168-
169-
val jsLorem = Json.obj("age" -> 11, "ipsum" -> Json.obj("bar" -> "foo"))
170-
171-
try {
172-
jsLorem.validate[Lorem[Simple]]
173161
} catch {
174162
case NonFatal(npe: NullPointerException) => {
175-
val expected = "Invalid implicit resolution"
176-
npe.getMessage.take(expected.size) mustEqual expected
163+
val expected = "Implicit value for 'ipsum'"
164+
npe.getMessage.take(expected.size).mustEqual(expected)
177165
}
178166

179-
case NonFatal(cause) => throw cause
167+
case cause: Throwable =>
168+
cause.printStackTrace()
169+
throw cause
180170
}
181171
}
182172
}
183173

184174
"Writes" should {
185175
"be generated for simple case class" in {
186-
Json.writes[Simple].writes(Simple("lorem")) mustEqual Json.obj("bar" -> "lorem")
176+
Json.writes[Simple].writes(Simple("lorem")).mustEqual(Json.obj("bar" -> "lorem"))
187177
}
188178

189179
"as Format for a generic case class" in {
@@ -521,38 +511,6 @@ class MacroSpec extends AnyWordSpec with Matchers with org.scalatestplus.scalach
521511
formatter.writes(Obj).mustEqual(jsObj)
522512
formatter.reads(jsObj).mustEqual(JsSuccess(Obj))
523513
}
524-
525-
"fails due to forward reference to Writes" in {
526-
implicit def writes: Writes[Lorem[Simple]] =
527-
InvalidForwardResolution.simpleLoremWrites
528-
529-
try {
530-
Json.toJson(Lorem(age = 11, ipsum = Simple(bar = "foo")))
531-
} catch {
532-
case NonFatal(npe: NullPointerException) => {
533-
val expected = "Invalid implicit resolution"
534-
npe.getMessage.take(expected.size) mustEqual expected
535-
}
536-
537-
case NonFatal(cause) => throw cause
538-
}
539-
}
540-
541-
"fails due to forward reference to Format" in {
542-
implicit def format: Format[Lorem[Simple]] =
543-
InvalidForwardResolution.simpleLoremFormat
544-
545-
try {
546-
Json.toJson(Lorem(age = 11, ipsum = Simple(bar = "foo")))
547-
} catch {
548-
case NonFatal(npe: NullPointerException) => {
549-
val expected = "Invalid implicit resolution"
550-
npe.getMessage.take(expected.size) mustEqual expected
551-
}
552-
553-
case NonFatal(cause) => throw cause
554-
}
555-
}
556514
}
557515
}
558516

@@ -584,11 +542,11 @@ object MacroSpec {
584542

585543
object InvalidForwardResolution {
586544
// Invalids as forward references to `simpleX`
587-
val simpleLoremReads = Json.reads[Lorem[Simple]]
545+
val simpleLoremReads = Json.reads[Lorem[Simple]]
588546
val simpleLoremWrites = Json.writes[Lorem[Simple]]
589547
val simpleLoremFormat = Json.format[Lorem[Simple]]
590548

591-
implicit val simpleReads: Reads[Simple] = Json.reads
549+
implicit val simpleReads: Reads[Simple] = Json.reads
592550
implicit val simpleWrites: OWrites[Simple] = Json.writes[Simple]
593551
}
594552

0 commit comments

Comments
 (0)