Reproduction: Define a macro annotation. It adds shapeless.Generic[A] to the companion object of A being a class or trait (this should compile if the class is a case class or the trait is a sealed trait with case-class children). If the companion object doesn't exist the annotation creates it. The annotation can annotate not only a class/trait but a companion object itself (anyway Generic is materialized inside the object).
@compileTimeOnly("enable macro annotations")
class generateGeneric extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro GenerateGenericMacroImpl.macroTransformImpl
}
object GenerateGenericMacroImpl {
def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
def modifyObject(obj: Tree): Tree = obj match {
case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" =>
q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
..$body
_root_.shapeless.Generic[${tname.toTypeName}]
}"""
case _ => sys.error("impossible")
}
def modify(cls: Tree, obj: Tree): Tree = q"..${Seq(cls, modifyObject(obj))}"
annottees match {
case (cls: ClassDef) :: (obj: ModuleDef) :: Nil => modify(cls, obj)
case (cls: ClassDef) :: Nil => modify(cls, q"object ${cls.name.toTermName}")
// this works for the companion object of a sealed trait or top-level case class but not nested case class
case (obj: ModuleDef) :: Nil => modifyObject(obj)
case _ => c.abort(c.enclosingPosition, "@generateGeneric can annotate only traits, classes, and objects")
}
}
}
Everything compiles if the annotation annotates:
a sealed trait (top-level or nested) or its companion object (existing or not),
a top-level case class or its companion object (existing or not),
a nested case class.
This doesn't compile if the annotation annotates:
the companion object of a nested case class.
object App {
case class A(i: Int, s: String)
@generateGeneric
object A
//implicit error;
//!I gen: shapeless.Generic[App.A]
// @generateGeneric
//scalac: object A extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// _root_.shapeless.Generic[A]
//}
//scalac: Generic.instance[App.A, Int :: String :: shapeless.HNil](<empty> match {
// case (x$macro$3 @ _) => ::(x$macro$3.i, ::(x$macro$3.s, HNil)).asInstanceOf[Int :: String :: shapeless.HNil]
//}, <empty> match {
// case ::((i$macro$1 @ _), ::((s$macro$2 @ _), HNil)) => App.this.A(i$macro$1, s$macro$2)
//})
}
If we replace _root_.shapeless.Generic[${tname.toTypeName}] with explicitly resolved _root_.shapeless.Generic[${tname.toTypeName}](_root_.shapeless.Generic.materialize) then the error is
//App.A.type does not take parameters
// @generateGeneric
It seems that Generic materialized in the macro-generated companion object of a nested case class (when the object is annotated) is typechecked when apply method doesn't exist yet.
Reproduction: Define a macro annotation. It adds
shapeless.Generic[A]
to the companion object ofA
being a class or trait (this should compile if the class is a case class or the trait is a sealed trait with case-class children). If the companion object doesn't exist the annotation creates it. The annotation can annotate not only a class/trait but a companion object itself (anywayGeneric
is materialized inside the object).Everything compiles if the annotation annotates:
This doesn't compile if the annotation annotates:
If we replace
_root_.shapeless.Generic[${tname.toTypeName}]
with explicitly resolved_root_.shapeless.Generic[${tname.toTypeName}](_root_.shapeless.Generic.materialize)
then the error ishttps://scastie.scala-lang.org/DmytroMitin/im8OhAEySOqOiS0d7tOxvQ/15 (toolbox is not a part of reproduction, it's just for Scastie because it doesn't support multiple files/subprojects)
It seems that
Generic
materialized in the macro-generated companion object of a nested case class (when the object is annotated) is typechecked whenapply
method doesn't exist yet.A possible fix is https://github.com/milessabin/shapeless/pull/1286
This impacts how Circe materializes semi-automatic codecs (when implicits are macro-annotation generated).
Discovered in https://stackoverflow.com/questions/74364545/type-parameter-for-implicit-valued-method-in-scala-circe