wartremover icon indicating copy to clipboard operation
wartremover copied to clipboard

Any and Nothing fail with play-json in Scala 2.13

Open nehaev opened this issue 5 years ago • 6 comments

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)));

nehaev avatar Jul 30 '19 10:07 nehaev

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.

nehaev avatar Aug 01 '19 13:08 nehaev

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.

nehaev avatar Aug 22 '19 14:08 nehaev

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

mikail-khan avatar Jun 08 '20 15:06 mikail-khan

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]

mikail-khan avatar Jun 08 '20 15:06 mikail-khan

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 :(

solarmosaic-kflorence avatar Jun 11 '21 02:06 solarmosaic-kflorence

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"))

solarmosaic-kflorence avatar Jul 01 '21 02:07 solarmosaic-kflorence