Is it possible to distinguish Scala-3 enums and sealed traits using Mirrors or even Macros?
transparent inline def isScalaEnum[A]: Boolean = ${ isScalaEnumImpl[A] }
private def isScalaEnumImpl[A: Type](using q: Quotes): Expr[Boolean] = ???
For example, how do you implement the above macro?
sealed trait T
case class A(x: Int) extends T
case class B(x: String) extends T
enum Color(val rgb: Int):
case Red extends Color(1)
case Green extends Color(2)
isScalaEnum[T] should be false
isScalaEnum[Color] should be true
CodePudding user response:
Thanks to @Gaël J's comment, here is what I came up with. Unfortunately, it uses Macro, but a simple one.
In one file:
import scala.quoted.*
transparent inline def isScalaEnum[A]: Boolean =
${ isScalaEnumImpl[A] }
private def isScalaEnumImpl[A: Type](using q: Quotes): Expr[Boolean] =
import q.reflect.*
TypeRepr.of[A].asType match
case '[reflect.Enum] => Expr(true)
case _ => Expr(false)
Here are my test:
enum Foo:
case Bar
enum FooS(x:String):
case Bar extends FooS("str")
sealed trait NotFoo
@main
def demo(): Unit =
inline val a: true = isScalaEnum[Foo]
inline val b: true = isScalaEnum[Foo.Bar.type]
inline val c: true = isScalaEnum[FooS]
inline val d: true = isScalaEnum[FooS.Bar.type]
inline val e: false = isScalaEnum[NotFoo]
inline val f: false = isScalaEnum[Int]
inline val g: false = isScalaEnum[Null]
Please let me know if you think there is an easier/better solution, or there is something wrong with what I provided here.
CodePudding user response:
You could do it with <:<
and NotGiven
:
<:< reflect.Enum
for checking whether your type is subclass ofscala.reflect.Enum
(which is true only for Scala 3enum
s)NotGiven
for translating the absence of<:<
-evidence into afalse
Here is how it can be implemented:
import scala.util.NotGiven
case class IsEnum[X](value: Boolean)
given isEnum[X](using X <:< reflect.Enum): IsEnum[X] = IsEnum(true)
given isNotEnum[X](using NotGiven[X <:< reflect.Enum]): IsEnum[X] = IsEnum(false)
inline def isScalaEnum[X](using inline ev: IsEnum[X]): Boolean = ev.value
Here is how it can be used:
enum Foo:
case Bar
sealed trait NotFoo
@main def demo(): Unit =
println(isScalaEnum[Foo]) // true
println(isScalaEnum[NotFoo]) // false
Update
If you want to have more precise type information at compile time, just transparently inline all the things:
import scala.util.NotGiven
case class IsEnum[X](value: Boolean)
transparent inline given isEnum[X](using X <:< reflect.Enum): IsEnum[X] =
IsEnum(true)
transparent inline given isNotEnum[X](using NotGiven[X <:< reflect.Enum]): IsEnum[X] =
IsEnum(false)
transparent inline def isScalaEnum[X](using inline ev: IsEnum[X]): ev.value.type =
ev.value
It then behaves similarly to the test cases that you've mentioned in your own answer, with Null
and Nothing
being notable exceptions:
enum Foo:
case Bar
enum FooS(x:String):
case Bar extends FooS("str")
sealed trait NotFoo
inline val a: true = isScalaEnum[Foo]
inline val b: true = isScalaEnum[Foo.Bar.type]
inline val c: true = isScalaEnum[FooS]
inline val d: true = isScalaEnum[FooS.Bar.type]
inline val e: false = isScalaEnum[NotFoo]
inline val f: false = isScalaEnum[Int]
inline val g: false = isScalaEnum[String]
inline val x: true = isScalaEnum[Null]
inline val y: true = isScalaEnum[Nothing]