From 1655d2e42fc2609f7299656060587bf5dfc26e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Sowa?= Date: Fri, 12 Aug 2022 15:23:16 +0200 Subject: [PATCH 1/2] Basic implementation of ValueEnum for Scala 3. --- .../pl/iterators/kebs/enums/ValueEnum.scala | 5 ++ .../kebs/macros/enums/EnumEntryMacros.scala | 46 +++++++++++++++---- .../test/scala-3/DerivingSpecification.scala | 14 +++++- 3 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 macro-utils/src/main/scala-3/pl/iterators/kebs/enums/ValueEnum.scala diff --git a/macro-utils/src/main/scala-3/pl/iterators/kebs/enums/ValueEnum.scala b/macro-utils/src/main/scala-3/pl/iterators/kebs/enums/ValueEnum.scala new file mode 100644 index 00000000..9eb84be5 --- /dev/null +++ b/macro-utils/src/main/scala-3/pl/iterators/kebs/enums/ValueEnum.scala @@ -0,0 +1,5 @@ +package pl.iterators.kebs.enums + +trait ValueEnum[ValueType] { + def value: ValueType +} \ No newline at end of file diff --git a/macro-utils/src/main/scala-3/pl/iterators/kebs/macros/enums/EnumEntryMacros.scala b/macro-utils/src/main/scala-3/pl/iterators/kebs/macros/enums/EnumEntryMacros.scala index 632500cf..536cc8df 100644 --- a/macro-utils/src/main/scala-3/pl/iterators/kebs/macros/enums/EnumEntryMacros.scala +++ b/macro-utils/src/main/scala-3/pl/iterators/kebs/macros/enums/EnumEntryMacros.scala @@ -1,5 +1,6 @@ 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 @@ -7,27 +8,25 @@ 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) => @@ -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 } } \ No newline at end of file diff --git a/macro-utils/src/test/scala-3/DerivingSpecification.scala b/macro-utils/src/test/scala-3/DerivingSpecification.scala index e8a0f69a..fdfa3b7b 100644 --- a/macro-utils/src/test/scala-3/DerivingSpecification.scala +++ b/macro-utils/src/test/scala-3/DerivingSpecification.scala @@ -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) @@ -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 + } } From 09fd021501cedd5265448d650f93b2eb09479a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kiersznowski?= Date: Tue, 11 Apr 2023 16:01:02 +0200 Subject: [PATCH 2/2] move ValueEnum to core module --- .../main/scala-3/pl/iterators/kebs/macros}/enums/ValueEnum.scala | 1 + 1 file changed, 1 insertion(+) rename {macro-utils/src/main/scala-3/pl/iterators/kebs => core/src/main/scala-3/pl/iterators/kebs/macros}/enums/ValueEnum.scala (98%) diff --git a/macro-utils/src/main/scala-3/pl/iterators/kebs/enums/ValueEnum.scala b/core/src/main/scala-3/pl/iterators/kebs/macros/enums/ValueEnum.scala similarity index 98% rename from macro-utils/src/main/scala-3/pl/iterators/kebs/enums/ValueEnum.scala rename to core/src/main/scala-3/pl/iterators/kebs/macros/enums/ValueEnum.scala index 9eb84be5..802cd69d 100644 --- a/macro-utils/src/main/scala-3/pl/iterators/kebs/enums/ValueEnum.scala +++ b/core/src/main/scala-3/pl/iterators/kebs/macros/enums/ValueEnum.scala @@ -1,3 +1,4 @@ + package pl.iterators.kebs.enums trait ValueEnum[ValueType] {