wartremover
wartremover copied to clipboard
Any and Nothing fail with play-json in Scala 2.13
Any
and Nothing
fail on Json.reads
macro for case classes with more than one param. On Scala 2.12 same code compiles fine.
Build settings:
scalaVersion := "2.13.0"
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4"
wartremoverErrors in (Compile, compile) ++= Warts.all
Example code:
import play.api.libs.json._
final case class Greeting(id: Long, greeting: String)
object Greeting {
implicit val reads: Reads[Greeting] = Json.reads[Greeting]
}
Error:
[error] [wartremover:Any] Inferred type containing Any
[error] implicit val reads: Reads[Greeting] = Json.reads[Greeting]
[error] ^
Output of -Xprint:typer
for the part which is presumably responsible for this:
scala 2.13: val underlying: play.api.libs.json.Reads[example.Greeting] = play.api.libs.functional.syntax.`package`.toFunctionalBuilderOps[play.api.libs.json.Reads, Long](play.api.libs.json.JsPath.\(json.this.JsonConfiguration.default[play.api.libs.json.Json.MacroOptions](MacroOptions.this.Default.macroOptionsDefault).naming.apply("id")).read[Long](json.this.Reads.LongReads))(functional.this.FunctionalCanBuild.functionalCanBuildApplicative[[A]play.api.libs.json.Reads[A]](json.this.Reads.applicative(json.this.JsResult.applicativeJsResult))).and[String](play.api.libs.json.JsPath.\(json.this.JsonConfiguration.default[play.api.libs.json.Json.MacroOptions](MacroOptions.this.Default.macroOptionsDefault).naming.apply("greeting")).read[String](json.this.Reads.StringReads)).apply[example.Greeting](((id: Long, greeting: String) => Greeting.apply(id, greeting)))(json.this.Reads.functorReads(json.this.Reads.applicative(json.this.JsResult.applicativeJsResult)));
scala 2.12: val underlying: play.api.libs.json.Reads[example.Greeting] = play.api.libs.functional.syntax.`package`.toFunctionalBuilderOps[play.api.libs.json.Reads, Long](play.api.libs.json.JsPath.\(json.this.JsonConfiguration.default[play.api.libs.json.Json.MacroOptions](MacroOptions.this.Default.macroOptionsDefault).naming.apply("id")).read[Long](json.this.Reads.LongReads))(functional.this.FunctionalCanBuild.functionalCanBuildApplicative[play.api.libs.json.Reads](json.this.Reads.applicative(json.this.JsResult.applicativeJsResult))).and[String](play.api.libs.json.JsPath.\(json.this.JsonConfiguration.default[play.api.libs.json.Json.MacroOptions](MacroOptions.this.Default.macroOptionsDefault).naming.apply("greeting")).read[String](json.this.Reads.StringReads)).apply[example.Greeting]({((id: Long, greeting: String) => Greeting.apply(id, greeting))})(json.this.Reads.functorReads(json.this.Reads.applicative(json.this.JsResult.applicativeJsResult)));
Minimized example.
The code below compiles fine even on Scala 2.13:
import play.api.libs.json.{ JsPath, Reads }
import play.api.libs.functional.FunctionalCanBuild
import play.api.libs.functional.syntax
val x = syntax.toFunctionalBuilderOps((JsPath \ "id").read[Long])(FunctionalCanBuild.functionalCanBuildApplicative[Reads])
From WartTraverser
perspective it looks like:
...
class scala.reflect.internal.Trees$Select >>> play.api.libs.functional.FunctionalCanBuild.functionalCanBuildApplicative
class scala.reflect.internal.Trees$Select >>> play.api.libs.functional.FunctionalCanBuild
class scala.reflect.internal.Trees$Select >>> play.api.libs.functional
class scala.reflect.internal.Trees$Select >>> play.api.libs
class scala.reflect.internal.Trees$Select >>> play.api
class scala.reflect.internal.Trees$Ident >>> play
class scala.reflect.internal.Trees$TypeTree >>> play.api.libs.json.Reads
class scala.reflect.internal.Trees$ApplyToImplicitArgs >>> json.this.Reads.applicative(json.this.JsResult.applicativeJsResult)
...
The following code is generated by macros and causes Inferred type containing Any
on Scala 2.13:
import play.api.libs.json.{ JsPath, Reads }
import play.api.libs.functional.FunctionalCanBuild
import play.api.libs.functional.syntax
val x = syntax.toFunctionalBuilderOps((JsPath \ "id").read[Long])
From WartTraverser
perspective it looks like:
...
class scala.reflect.internal.Trees$Select >>> functional.this.FunctionalCanBuild.functionalCanBuildApplicative
class scala.reflect.internal.Trees$Select >>> functional.this.FunctionalCanBuild
class scala.reflect.internal.Trees$This >>> functional.this
class scala.reflect.internal.Trees$TypeTree >>> [A]play.api.libs.json.Reads[A]
class scala.reflect.internal.Types$PolyType xxx [A]play.api.libs.json.Reads[A]
class scala.reflect.internal.Trees$ApplyToImplicitArgs >>> json.this.Reads.applicative(json.this.JsResult.applicativeJsResult)
...
So this PolyType(typeParams = List(type A), resultType = play.api.libs.json.Reads[A])
is what causes the problem, because the base class for A
is Any
.
Does anyone have an idea how to fix this?
What comes to my mind is to ignore PolyType
in https://github.com/wartremover/wartremover/blob/master/core/src/main/scala/wartremover/warts/ForbidInference.scala#L36 just like it's done for ExistentialType
. But given that not only Any
and Nothing
use ForbidInference
, I'm not sure that this change won't break anything else.
I have the same issue. Bumped a project to Scala 2.13.2 and using play-json-derived-codecs
(v 7.0.0) and wartremover complains:
Given this model:
sealed trait A extends Product with Serializable
object A{
case object B extends A
case object C extends A
implicit val format: OFormat[A] = derived.oformat()
}
Wartremover complains :
[wartremover: Any] Inferred type containing Any: julienrf.json.derived.DerivedReads[A]
Even trying:
implicit val format: OFormat[A] = derived.oformat[A]()
doesn't work
Same occurs even for this:
final case class AuthenticatedRequest[A](request: MessagesRequest[A]) extends WrappedRequest[A](request)
[wartremover:Any] Inferred type containing Any: [A] AuthenticatedRequest[A]
Same problem for a pretty simple play-json
use case:
case class Response(name: String, data: Map[String, String])
object Response {
implicit val format: Format[Response] = Json.format
}
Results in:
[wartremover:Any] Inferred type containing Any: [A]play.api.libs.json.Reads[A]
Strangely, it does not complain about:
case class Request(data: Map[String, String])
object Request {
implicit val format: Format[Request] = Json.format
}
Time to slap @SuppressWarnings(Array("org.wartremover.warts.Any"))
all over the place :(
I have also seen:
[wartremover:Product] Inferred type containing Product: ProducerMessage with Product with Serializable
[error] implicit val format: Format[ProducerMessage] = Json.format
Which has to be suppressed with:
@SuppressWarnings(Array("org.wartremover.warts.Product", "org.wartremover.warts.Serializable"))