For a not sealed trait, the instances of Generic/LabelledGeneric defined in Shapeless can't work.
All such macros are using .knownDirectSubclasses. It works only for a sealed trait.
Scala reflection: knownDirectSubclasses only works for sealed traits?
For a not sealed trait I can always add inheritors of Base in a different file (case class Case4() extends Base) or even at runtime (toolbox.define(q"case class Case4() extends Base")).
If you're interested only in inheritors defined in the current file then maybe you can avoid usage of .knownDirectSubclasses and write a macro traversing the AST of current file and looking for the inheritors.
So far I found no way to derive L and l from C.
It's not hard
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait ToName[A] {
type Out <: String with Singleton
}
object ToName {
type Aux[A, Out0 <: String with Singleton] = ToName[A] { type Out = Out0 }
implicit def mkToName[A, Out <: String with Singleton]: Aux[A, Out] = macro mkToNameImpl[A]
def mkToNameImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
q"""
new ToName[$A] {
type Out = ${A.typeSymbol.name.toString}
}
"""
}
}
implicitly[ToName.Aux[Case1, "Case1"]] // compiles
import shapeless.ops.coproduct.ToHList
import shapeless.tag.@@
import shapeless.{:+:, ::, CNil, HList, HNil, Poly0, Poly1, Witness, tag, the}
object toNamePoly extends Poly1 {
implicit def cse[A <: Base, S <: String with Singleton](implicit
toName: ToName.Aux[A, S],
witness: Witness.Aux[S],
// valueOf: ValueOf[S],
): Case.Aux[A, Symbol @@ S] = at(_ => tag[S](Symbol(witness/*valueOf*/.value)))
}
object nullPoly extends Poly0 {
implicit def default[A]: Case0[A] = at(null.asInstanceOf[A])
}
val res = HList.fillWith[the.`ToHList[C]`.Out](nullPoly).map(toNamePoly)
res: L // compiles
res == l // true
So you can derive DefaultSymbolicLabelling as follows
import shapeless.ops.coproduct.ToHList
import shapeless.ops.hlist.{FillWith, Mapper}
implicit def mySymbolicLabelling[L <: HList](implicit
toHList: ToHList.Aux[C, L],
fillWith: FillWith[nullPoly.type, L],
mapper: Mapper[toNamePoly.type, L],
): DefaultSymbolicLabelling.Aux[Base, mapper.Out] =
DefaultSymbolicLabelling.instance[Base, mapper.Out](mapper(fillWith()))
Here is the code with traversing. I'm introducing type class KnownSubclasses
import shapeless.Coproduct
import scala.collection.mutable
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait KnownSubclasses[A] {
type Out <: Coproduct
}
object KnownSubclasses {
type Aux[A, Out0 <: Coproduct] = KnownSubclasses[A] { type Out = Out0 }
implicit def mkKnownSubclasses[A, Out <: Coproduct]: Aux[A, Out] = macro mkKnownSubclassesImpl[A]
def mkKnownSubclassesImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
var children = mutable.Seq[Type]()
// subclasses of A
val traverser = new Traverser {
override def traverse(tree: Tree): Unit = {
tree match {
case _: ClassDef =>
val tpe = tree.symbol.asClass.toType
if (tpe <:< A && !(tpe =:= A)) children :+= tpe
case _ =>
}
super.traverse(tree)
}
}
// def getType(t: Tree): Type = {
// val withoutArgs = t match {
// case q"${t1@tq"$_[..$_]"}(...$_)" => t1
// case _ => t
// }
// c.typecheck(tq"$withoutArgs", mode = c.TYPEmode).tpe
// }
//
// // direct subclasses of A
// val traverser = new Traverser {
// override def traverse(tree: Tree): Unit = {
// tree match {
// case q"$_ class $_[..$_] $_(...$_) extends { ..$_ } with ..$parents { $_ => ..$_ }"
// if parents.exists(getType(_) =:= A) =>
// children :+= tree.symbol.asClass.toType
// case _ =>
// }
//
// super.traverse(tree)
// }
// }
c.enclosingRun.units.foreach(unit => traverser.traverse(unit.body))
val coprod = children.foldRight[Tree](tq"_root_.shapeless.CNil")((child, copr) => tq"_root_.shapeless.:+:[$child, $copr]")
q"""
new KnownSubclasses[$A] {
type Out = $coprod
}
"""
}
}
implicitly[KnownSubclasses.Aux[Base, Case1 :+: Case2 :+: Case3 :+: CNil]] // compiles
(This implementation doesn't work for generic trait and classes.)
So you can derive Generic and DefaultSymbolicLabelling (and therefore LabelledGeneric) as follows
import shapeless.ops.coproduct.{RuntimeInject, ToHList}
import shapeless.ops.hlist.{FillWith, Mapper}
implicit def myGeneric[C <: Coproduct](implicit
knownSubclasses: KnownSubclasses.Aux[Base, C],
runtimeInject: RuntimeInject[C]
): Generic.Aux[Base, C] = Generic.instance[Base, C](
v => Coproduct.runtimeInject[C](v).get,
v => Coproduct.unsafeGet(v).asInstanceOf[Base]
)
implicit def mySymbolicLabelling[C <: Coproduct, L <: HList](implicit
knownSubclasses: KnownSubclasses.Aux[Base, C],
toHList: ToHList.Aux[C, L],
fillWith: FillWith[nullPoly.type, L],
mapper: Mapper[toNamePoly.type, L],
): DefaultSymbolicLabelling.Aux[Base, mapper.Out] =
DefaultSymbolicLabelling.instance[Base, mapper.Out](mapper(fillWith()))