Home > Net >  Distinguish Scala-3 Enum and Sealed Traits
Distinguish Scala-3 Enum and Sealed Traits

Time:11-19

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 of scala.reflect.Enum (which is true only for Scala 3 enums)
  • NotGiven for translating the absence of <:<-evidence into a false

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]
  • Related