how to generate fresh singleton literal type in scala using macros

61 Views Asked by At

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]
1

There are 1 best solutions below

2
Dmytro Mitin On BEST ANSWER

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

https://docs.scala-lang.org/scala3/reference/metaprogramming/inline.html#transparent-inline-methods-1

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)}

https://github.com/milessabin/shapeless/blob/main/core/shared/src/main/scala/shapeless/syntax/singletons.scala#L33

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.