Skip to content

Commit

Permalink
Merge pull request #232 from theiterators/dotty-value-enum
Browse files Browse the repository at this point in the history
Basic implementation of ValueEnum for Scala 3
  • Loading branch information
pk044 committed Apr 11, 2023
2 parents 80b27f6 + 09fd021 commit 28f0ada
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
package pl.iterators.kebs.macros.enums

import pl.iterators.kebs.enums.ValueEnum
import scala.quoted._
import scala.compiletime.{constValue, erasedValue, error, summonInline}
import scala.deriving.Mirror
import scala.reflect.{ClassTag, Enum}

trait EnumLike[T] {
def values: Array[T]
def valueOf(name: String): T
def fromOrdinal(ordinal: Int): T
def valueOf(name: String): T = values.find(_.toString == name).getOrElse(throw new IllegalArgumentException(s"enum case not found: $name"))
def fromOrdinal(ordinal: Int): T = values.lift(ordinal).getOrElse(throw new NoSuchElementException(ordinal.toString))
}

class EnumOf[E](val `enum`: EnumLike[E])

inline private def widen[A, B] (a: A): A & B =
inline a match {
case b: B => b
}

object EnumOf {
inline given [E <: Enum](using m: Mirror.SumOf[E], ct: ClassTag[E]): EnumOf[E] = {
val enumValues = summonCases[m.MirroredElemTypes, E]
EnumOf[E](new EnumLike[E] {
override def values: Array[E] = enumValues.toArray
override def valueOf(name: String): E = enumValues.find(_.toString == name).getOrElse(throw new IllegalArgumentException(s"enum case not found: $name"))
override def fromOrdinal(ordinal: Int): E = enumValues.lift(ordinal).getOrElse(throw new NoSuchElementException(ordinal.toString))
})
}

inline private def widen[A, B](a: A): A & B =
inline a match {
case b: B => b
}

inline private def summonCases[T <: Tuple, A]: List[A] =
inline erasedValue[T] match {
case _: (h *: t) =>
Expand All @@ -37,6 +36,35 @@ object EnumOf {
case x => error("Enums cannot include parameterized cases.")
})

case _: EmptyTuple => Nil
}
}

trait ValueEnumLike[ValueType, T <: ValueEnum[ValueType]] {
def values: Array[T]
def valueOf(value: ValueType): T = values.find(_.value == value).getOrElse(throw new IllegalArgumentException(s"enum case not found: $value"))
def fromOrdinal(ordinal: Int): T = values.lift(ordinal).getOrElse(throw new NoSuchElementException(ordinal.toString))
}

class ValueEnumOf[V, E <: ValueEnum[V]](val `enum`: ValueEnumLike[V, E])

object ValueEnumOf {
inline given [V, E <: ValueEnum[V] with Enum](using m: Mirror.SumOf[E], ct: ClassTag[E]): ValueEnumOf[V, E] = {
val enumValues = summonValueCases[m.MirroredElemTypes, V, E]
ValueEnumOf[V, E](new ValueEnumLike[V, E] {
override def values: Array[E] = enumValues.toArray
})
}

inline private def summonValueCases[T <: Tuple, V, A <: ValueEnum[V]]: List[A] =
inline erasedValue[T] match {
case _: (h *: t) =>
(inline summonInline[Mirror.Of[h]] match {
case m: Mirror.Singleton =>
widen[m.MirroredMonoType, A](m.fromProduct(EmptyTuple)) :: summonValueCases[t, V, A]
case x => error("Enums cannot include parameterized cases.")
})

case _: EmptyTuple => Nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

package pl.iterators.kebs.enums

trait ValueEnum[ValueType] {
def value: ValueType
}
14 changes: 13 additions & 1 deletion core/src/test/scala-3/DerivingSpecification.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import org.scalacheck.Prop.forAll
import org.scalacheck.{Gen, Properties}
import pl.iterators.kebs.macros.CaseClass1Rep
import pl.iterators.kebs.macros.enums.EnumOf
import pl.iterators.kebs.macros.enums.{EnumOf, ValueEnumOf}
import pl.iterators.kebs.enums.ValueEnum

object DerivingSpecification extends Properties("Deriving") {
case class CC1Ex(whatever: String)
Expand All @@ -19,4 +20,15 @@ object DerivingSpecification extends Properties("Deriving") {
val tc = implicitly[EnumOf[Color]]
tc.`enum`.values.contains(color) && tc.`enum`.valueOf(color.toString) == color && tc.`enum`.fromOrdinal(color.ordinal) == color
}

enum ColorButRGB(val value: Int) extends ValueEnum[Int] {
case Red extends ColorButRGB(0xFF0000)
case Green extends ColorButRGB(0x00FF00)
case Blue extends ColorButRGB(0x0000FF)
}

property("ValueEnumOf derives properly for an enum") = forAll(Gen.oneOf(ColorButRGB.values.toList)) { (color: ColorButRGB) =>
val tc = implicitly[ValueEnumOf[Int, ColorButRGB]]
tc.`enum`.values.contains(color) && tc.`enum`.valueOf(color.value) == color && tc.`enum`.fromOrdinal(color.ordinal) == color
}
}

0 comments on commit 28f0ada

Please sign in to comment.