I'm facing this error:
exception during macro expansion:
scala.ScalaReflectionException: type T is not a class
at scala.reflect.api.Symbols$SymbolApi.asClass(Symbols.scala:284)
at scala.reflect.api.Symbols$SymbolApi.asClass$(Symbols.scala:284)
at scala.reflect.internal.Symbols$SymbolContextApiImpl.asClass(Symbols.scala:99)
at play.api.libs.json.JsMacroImpl.directKnownSubclasses$1(JsMacroImpl.scala:527)
at play.api.libs.json.JsMacroImpl.macroImpl(JsMacroImpl.scala:850)
at play.api.libs.json.JsMacroImpl.implicitConfigWritesImpl(JsMacroImpl.scala:44)
When trying to declare a method to create the writer for a given generic type:
def makeWriter[T]: Writes[T] = Json.writes[T]
Now the implementation of the Json.writes
is just a macro, and it doesn't require any sort of context bounds or something:
def writes[A]: OWrites[A] = macro JsMacroImpl.withOptionsWritesImpl[A]
Does anyone know what's going on here? I'm not really good at macros, so I'd appreciate if anyone could explain this, any solution is appreciated.
Yes, it's possible.
The stack trace
scala.ScalaReflectionException: type T is not a class
at scala.reflect.api.Symbols$SymbolApi.asClass(Symbols.scala:284)
at scala.reflect.api.Symbols$SymbolApi.asClass$(Symbols.scala:284)
at scala.reflect.internal.Symbols$SymbolContextApiImpl.asClass(Symbols.scala:99)
at play.api.libs.json.JsMacroImpl.directKnownSubclasses$1(JsMacroImpl.scala:555)
at play.api.libs.json.JsMacroImpl.macroImpl(JsMacroImpl.scala:869)
at play.api.libs.json.JsMacroImpl.implicitConfigWritesImpl(JsMacroImpl.scala:44)
gives a hint what the problems is:
def directKnownSubclasses: Option[List[Type]] = {
// Workaround for SI-7046: https://issues.scala-lang.org/browse/SI-7046
val tpeSym = atag.tpe.typeSymbol.asClass // <------
...
.asClass
can be called only on class symbols, otherwise it throws (and runtime exceptions of macros are compile errors of main code)
def asClass: ClassSymbol = throw new ScalaReflectionException(s"$this is not a class")
https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/api/Symbols.scala#L284
The problem of the definition
def makeWriter[T]: Writes[T] = Json.writes[T]
is that the macro Json.writes[T]
is expanded here where T
is not a class yet but just a type parameter. You can postpone macro expansion if you make your makeWriter
a macro as well
import play.api.libs.json.Writes
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def makeWriter[T]: Writes[T] = macro makeWriterImpl[T]
def makeWriterImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val typeT = weakTypeOf[T]
q"_root_.play.api.libs.json.Json.writes[$typeT]"
}
// in a different subproject
case class A(i: Int)
makeWriter[A] // compiles
Implicit Json Formatter for value classes in Scala
Why the Scala compiler can provide implicit outside of object, but cannot inside? (answer)
Type parameter for implicit valued method in Scala - Circe
Parametric polymorphism issue with spark implicits, value toDF is not a member of Seq[T]
Another solution to postpone macro expansion is to postpone implicit resolution (this postpones macro expansion because macros are now used to define implicits, i.e. instances of the type class Writes
)
def makeWriter[T: Writes]: Writes[T] = implicitly[Writes[T]]
// aka
//def makeWriter[T](implicit writes: Writes[T]): Writes[T] = writes
See about the difference between implicitly[X]
and (implicit x: X)
When doing implicit resolution with type parameters, why does val placement matter?
Setting abstract type based on typeclass
and it doesn't require any sort of context bounds or something:
You have correct suspection that context bounds (implicit parameters) can be hidden via macros (although now the reason of error was different):
def foo()(implicit a: A) = ()
can become
def foo(): Unit = macro fooImpl
def fooImpl(c: blackbox.Context)(): c.Tree = {
import c.universe._
c.inferImplicitValue(typeOf[A], silent = false)
q"()"
}
(so def foo(): Unit
now pretends that it doesn't require implicit A
although actually it does).