Try macro-implemented type class HasCompanion
by @Jasper-M https://gist.github.com/Jasper-M/1b6540fecfa3af9094dab1614038a1b5
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait HasCompanion[A] {
type Type
def companion: Type
}
object HasCompanion {
type Aux[A,C] = HasCompanion[A] { type Type = C }
def apply[A](implicit hc: HasCompanion[A]): hc.type = hc
implicit def mkHasCompanion[A,C]: Aux[A,C] = macro MacroHasCompanion.hasCompanionMacro[A]
}
object MacroHasCompanion {
def hasCompanionMacro[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val aTpe = weakTypeOf[A]
val companionTpe = aTpe.companion
val companion = aTpe.typeSymbol.companion
val HasCompanion = weakTypeOf[HasCompanion[A]]
if (companionTpe =:= NoType)
c.abort(c.enclosingPosition, s"No companion found for type $aTpe.")
else if (!companion.isModule)
c.abort(c.enclosingPosition, s"$aTpe is the companion.")
else
q"new $HasCompanion { type Type = $companionTpe; def companion = $companion }"
}
}
def callSthStatic[T, C <: Worker](implicit hc: HasCompanion.Aux[T, C]) = {
hc.companion.doSth
}
or
def callSthStatic[T](implicit hc: HasCompanion.Aux[T, _ <: Worker]) = {
hc.companion.doSth
}
or
def callSthStatic[T, C](implicit hc: HasCompanion.Aux[T, C], ev: C <:< Worker) = {
hc.companion.doSth
}
Similarly, a type class ToCompanion
can be defined that is bi-directional on type level (from a case class to its companion object or from an object to its companion class)
Invoke construcotr based on passed parameter
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
// type class
trait ToCompanion[A] {
type Out
}
object ToCompanion {
type Aux[A, Out0] = ToCompanion[A] {type Out = Out0}
def apply[A](implicit tcc: ToCompanion[A]): ToCompanion.Aux[A, tcc.Out] = tcc
implicit def mkToCompanion[A, B]: ToCompanion.Aux[A, B] = macro mkToCompanionImpl[A]
def mkToCompanionImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val companion = A.companion
val ToCompanion = weakTypeOf[ToCompanion[A]]
q"new $ToCompanion { type Out = $companion }"
}
}
implicitly[ToCompanion.Aux[Base, Base.type]] // compiles
implicitly[ToCompanion.Aux[Base.type, Base]] // compiles
val tc = ToCompanion[Base.type]
implicitly[tc.Out =:= Base] // compiles
val tc1 = ToCompanion[Base]
implicitly[tc1.Out =:= Base.type] // compiles
// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.{Generic, HNil}
def callSthStatic[T] = new PartiallyApplied[T]
class PartiallyApplied[T] {
def apply[C <: Worker]()(implicit
toCompanion: ToCompanion.Aux[T, C],
generic: Generic.Aux[C, HNil]
) = {
generic.from(HNil).doSth
}
}
callSthStatic[Base]()