I need to generate random signleton type on every macro invocation in scala 2.13
I tried something like this, but I can't change macro def return type
def randomSingletonInt: Int = macro randomImpl
def randomImpl(c: blackbox.Context): c.Tree = {
import c.universe.*
val number = c.freshName().hashCode
q"$number"
}
I need something like this
val a = randomSingletonInt // a: 42 = 42
val b = randomSingletonInt // b: -112 = -112
How can I achieve that?
My use case is that I want to use it with implicit resolution and type inference
def randomTag[K <: Int & Singleton](implicit tag: K): CustomType[K]
implicit val a = randomSingletonInt
val tagged = randomTag // tagged: CustomType[42]
You can generate 42
of singleton type 42
instead of type Int
with a whitebox macro
def randomSingletonInt: Int = macro randomImpl
def randomImpl(c: whitebox.Context): c.Tree = {
import c.universe._
q"42" // or q"42: 42"
}
randomSingletonInt // 42{Int(42)} // scalacOptions ++= Seq("-Xprint:typer", "-Xprint-types")
randomSingletonInt: 42 // checking, compiles
In this sense Scala 2 whitebox macros are similar to Scala 3 transparent inline methods
https://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html#blackbox-and-whitebox-macros
The problem is that if you assign a value of a singleton type to a variable then this doesn't mean that the type of the variable is singleton
val a = randomSingletonInt // val a: Int = 42{Int(42)}
a: 42 // doesn't compile
Solution is to add extra { }
(i.e. empty type refinement, this makes impact on type inference)
def randomImpl(c: whitebox.Context): c.Tree = {
import c.universe._
q"42: 42 {}"
}
val a = randomSingletonInt // val a: 42 = (42{Int(42)}: 42){42}
a: 42 // compiles
This is what Shapeless .narrow
does
import shapeless.syntax.singleton._
val a = 42.narrow
// val a: Int(42) = SingletonOps.instance{[T0](w: shapeless.Witness.Aux[T0]): shapeless.syntax.SingletonOps{type T = T0; val witness: w.type}}[Int(42)]{(w: shapeless.Witness.Aux[Int(42)]): shapeless.syntax.SingletonOps{type T = Int(42); val witness: w.type}}(Witness.mkWitness{[T0](value0: T0): shapeless.Witness.Aux[T0]}[Int(42)]{(value0: Int(42)): shapeless.Witness.Aux[Int(42)]}(42{Int(42)}.asInstanceOf{[T0]T0}[Int(42)]{42}){shapeless.Witness.Aux[Int(42)]}){shapeless.syntax.SingletonOps{type T = Int(42); val witness: shapeless.Witness.Aux[Int(42)]}}.narrow{Int(42)}
def narrow: T {} = witness.value
// ^^^^
Next problem is that this not always will work further on (with implicits for example)
val x: 42 = 42
implicitly[x.type =:= 42] // compiles
val a = randomSingletonInt
implicitly[a.type =:= 42] // doesn't compile
trait TC[I <: Int with Singleton]
object TC {
implicit val tc: TC[42] = null
}
implicitly[TC[42]] // compiles
implicitly[TC[x.type]] // compiles
implicitly[TC[a.type]] // doesn't compile
So you should better add code you want to compile with a
, b
and we'll see whether this is possible.
For example in implicits a type can be cleaned from refinements before comparing with =:=
trait TC[A <: Int with Singleton]
object TC {
implicit def tc[A <: Int with Singleton]: TC[A] = macro tcImpl[A]
def tcImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typeA = weakTypeOf[A]
def unrefy(tpe: Type): Type = tpe.typeSymbol.typeSignature match {
case RefinedType(List(tp, _*), _) => tp
case _ => tpe
}
val type42 = c.internal.constantType(Constant(42))
if (typeA =:= type42 || unrefy(typeA) =:= type42)
q"new TC[$typeA] {}"
else c.abort(c.enclosingPosition, "not 42")
}
}
implicitly[TC[42]] // compiles
implicitly[TC[x.type]] // compiles
implicitly[TC[a.type]] // compiles
Also solution can be to generate a singleton type rather than a value of singleton type.