Skip to content

Commit

Permalink
ValueEnumMacros
Browse files Browse the repository at this point in the history
  • Loading branch information
cchantep committed Sep 5, 2022
1 parent 9636290 commit 5a16eee
Show file tree
Hide file tree
Showing 17 changed files with 384 additions and 272 deletions.
9 changes: 8 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject}
lazy val scala_2_11Version = "2.11.12"
lazy val scala_2_12Version = "2.12.16"
lazy val scala_2_13Version = "2.13.8"
lazy val scala_3Version = "3.2.1-RC1-bin-20220705-9bb3108-NIGHTLY" // Fix not yet available in RC or stable version: https://github.com/lampepfl/dotty/issues/12498
lazy val scala_3Version = "3.2.1-RC1"
lazy val scalaVersionsAll = Seq(scala_2_11Version, scala_2_12Version, scala_2_13Version, scala_3Version)

lazy val theScalaVersion = scala_2_12Version
Expand Down Expand Up @@ -629,6 +629,13 @@ lazy val compilerSettings = Seq(
case Some((2, _)) => base ++ Seq("-Xlint")
case _ => base
}
},
Test / scalacOptions ++= {
if (scalaBinaryVersion.value == "3") {
Seq("-Yretain-trees")
} else {
Seq.empty
}
}
)

Expand Down
10 changes: 9 additions & 1 deletion enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ private[enumeratum] trait EnumCompat[A <: EnumEntry] { _: Enum[A] =>
/** Returns a Seq of [[A]] objects that the macro was able to find.
*
* You will want to use this in some way to implement your [[values]] method. In fact, if you
* aren't using this method...why are you even bothering with this lib?
* aren't using this method... why are you even bothering with this lib?
*/
protected def findValues: IndexedSeq[A] =
macro EnumMacros.findValuesImpl[A]

/** The sequence of values for your [[Enum]]. You will typically want to implement this in your
* extending class as a `val` so that `withName` and friends are as efficient as possible.
*
* Feel free to implement this however you'd like (including messing around with ordering, etc)
* if that fits your needs better.
*/
def values: IndexedSeq[A]
}

private[enumeratum] trait EnumCompanion {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package enumeratum.values

import scala.language.experimental.macros

import _root_.enumeratum.{Enum, EnumMacros, ValueEnumMacros}
import _root_.enumeratum.{EnumMacros, ValueEnumMacros}

private[enumeratum] trait IntEnumCompanion {

Expand Down
14 changes: 14 additions & 0 deletions enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
package enumeratum

private[enumeratum] trait EnumCompat[A <: EnumEntry] { _enum: Enum[A] =>

/** Returns a Seq of [[A]] objects that the macro was able to find.
*
* You will want to use this in some way to implement your [[values]] method. In fact, if you
* aren't using this method... why are you even bothering with this lib?
*/
inline def findValues: IndexedSeq[A] = ${ EnumMacros.findValuesImpl[A] }

/** The sequence of values for your [[Enum]]. You will typically want to implement this in your
* extending class as a `val` so that `withName` and friends are as efficient as possible.
*
* Feel free to implement this however you'd like (including messing around with ordering, etc)
* if that fits your needs better.
*/
def values: IndexedSeq[A]
}

private[enumeratum] trait EnumCompanion {
Expand Down
8 changes: 0 additions & 8 deletions enumeratum-core/src/main/scala/enumeratum/Enum.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,6 @@ trait Enum[A <: EnumEntry] extends EnumCompat[A] {
*/
lazy final val valuesToIndex: Map[A, Int] = values.zipWithIndex.toMap

/** The sequence of values for your [[Enum]]. You will typically want to implement this in your
* extending class as a `val` so that `withName` and friends are as efficient as possible.
*
* Feel free to implement this however you'd like (including messing around with ordering, etc)
* if that fits your needs better.
*/
def values: IndexedSeq[A]

/** Tries to get an [[A]] by the supplied name. The name corresponds to the .name of the case
* objects implementing [[A]]
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package enumeratum.values

import _root_.enumeratum.Enum

/** Base trait for a Value-based enums.
*
* Example:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* TODO
package enumeratum.values
/** Created by Lloyd on 1/4/17.
*
* Copyright 2017
*/
*
* Copyright 2017
*/
// From https://github.com/lloydmeta/enumeratum/issues/96
sealed abstract class A private (val value: Int) extends IntEnumEntry {
Expand Down Expand Up @@ -45,3 +46,4 @@ object B extends IntEnum[B] {
def identity(str: String) = str
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ trait CustomEnumEntry extends IntEnumEntry {
val value: Int
val name: String
}

trait CustomEnum[T <: CustomEnumEntry] extends IntEnum[T] {
def apply(name: String): T =
values.find(_.name == name).get
}

trait CustomEnumComparable[T <: CustomEnumEntry] { this: T =>
def >=(that: T): Boolean =
this.value >= that.value
}

sealed abstract class CustomEnumPrivateConstructor private (val value: Int, val name: String)
extends CustomEnumEntry
with CustomEnumComparable[CustomEnumPrivateConstructor]

object CustomEnumPrivateConstructor extends CustomEnum[CustomEnumPrivateConstructor] {
val values = findValues
case object A extends CustomEnumPrivateConstructor(10, "a")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ case object LibraryItem extends IntEnum[LibraryItem] {
case object CD extends LibraryItem(14, name = "cd")

val values = findValues

}

case object Newspaper extends LibraryItem(5, "Zeitung")
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package enumeratum.values

sealed abstract class NumericPrecision(val value: String) extends StringEnumEntry with AllowAlias

object NumericPrecision extends StringEnum[NumericPrecision] {
case object Integer extends NumericPrecision("integer")
case object Int extends NumericPrecision("integer")

case object Float extends NumericPrecision("float")
case object Double extends NumericPrecision("double")

val values = findValues
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,40 @@ import org.scalatest.matchers.should.Matchers
class ValueEnumSpec extends AnyFunSpec with Matchers with ValueEnumHelpers {

describe("basic sanity check") {

it("should have the proper values") {
LibraryItem.withValue(1) shouldBe LibraryItem.Book
LibraryItem.withValue(2) shouldBe LibraryItem.Movie
LibraryItem.withValue(10) shouldBe LibraryItem.Magazine
LibraryItem.withValue(14) shouldBe LibraryItem.CD
}

}

testNumericEnum("IntEnum", LibraryItem)
testNumericEnum("ShortEnum", Drinks)
testNumericEnum("LongEnum", ContentType)

testEnum("StringEnum", OperatingSystem, Seq("windows-phone"))
testEnum("CharEnum", Alphabet, Seq('Z'))
testEnum("ByteEnum", Bites, Seq(10).map(_.toByte))
testNumericEnum("when using val members in the body", MovieGenre)

testNumericEnum("When using val members in the body", MovieGenre)
testNumericEnum("LongEnum that is nesting an IntEnum", Animal)
testNumericEnum("IntEnum that is nested inside a LongEnum", Animal.Mammalian)
testNumericEnum("Custom IntEnum with private constructors", CustomEnumPrivateConstructor)

describe("AllowAlias") {
it("should be supported") {
NumericPrecision.values should contain(NumericPrecision.Integer)
NumericPrecision.values should contain(NumericPrecision.Int)
NumericPrecision.values should contain(NumericPrecision.Float)
NumericPrecision.values should contain(NumericPrecision.Double)

NumericPrecision.withValue("integer") shouldBe NumericPrecision.Int
NumericPrecision.withValue("float") shouldBe NumericPrecision.Float
NumericPrecision.withValue("double") shouldBe NumericPrecision.Double
}
}

describe("finding companion object") {

it("should work for IntEnums") {
Expand Down Expand Up @@ -83,11 +96,9 @@ class ValueEnumSpec extends AnyFunSpec with Matchers with ValueEnumHelpers {
companion shouldBe Bites
companion.values should contain(Bites.FourByte)
}

}

describe("compilation failures") {

describe("problematic values") {

it("should fail to compile when values are repeated") {
Expand Down Expand Up @@ -141,7 +152,6 @@ class ValueEnumSpec extends AnyFunSpec with Matchers with ValueEnumHelpers {
}
""" shouldNot compile
}

}

describe("trying to use with improper types") {
Expand Down Expand Up @@ -198,7 +208,5 @@ class ValueEnumSpec extends AnyFunSpec with Matchers with ValueEnumHelpers {
""" shouldNot compile
}
}

}

}
36 changes: 0 additions & 36 deletions macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala

This file was deleted.

1 change: 1 addition & 0 deletions macros/src/main/scala-2/enumeratum/ValueEnumMacros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ object ValueEnumMacros {

// Finish by building our Sequence
val subclassSymbols = treeWithVals.map(_.tree.symbol)

EnumMacros.buildSeqExpr[ValueEntryType](c)(subclassSymbols)
}

Expand Down
92 changes: 92 additions & 0 deletions macros/src/main/scala-3/enumeratum/EnumHelper.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package test

import scala.deriving.Mirror
import scala.quoted.{Expr, Quotes, Type}
import scala.reflect.Enum

object EnumHelper:

/** {{{
* enum Color:
* case Red, Green, Blue
*
* import reactivemongo.api.bson.EnumHelper
*
* val valueOf: String => Option[Color] = EnumHelper.strictValueOf[Color]
*
* assert(valueOf("Red") contains Color.Red)
* assert(valueOf("red").isEmpty)
* }}}
*/
inline def strictValueOf[T](using
Mirror.SumOf[T]
): String => Option[T] = ${ strictValueOfImpl[T] }

private def strictValueOfImpl[T](using
Quotes,
Type[T]
): Expr[String => Option[T]] = enumValueOfImpl(identity, None)

private def enumValueOfImpl[T](
labelNaming: String => String,
normalize: Option[Expr[String => String]]
)(using
q: Quotes,
tpe: Type[T]
): Expr[String => Option[T]] = {
import q.reflect.*

val tpr = TypeRepr.of(using tpe)

val compSym = tpr.typeSymbol.companionModule

if (compSym == Symbol.noSymbol) {
report.errorAndAbort(s"Unresolved type: ${tpr.typeSymbol.fullName}")
}

val compRef = Ref(compSym)

val cases = compSym.fieldMembers
.flatMap { fieldSym =>
val fieldTerm = compRef.select(fieldSym)

if (fieldTerm.tpe <:< tpr) {
Seq(fieldSym -> fieldTerm.asExprOf[T])
} else {
Seq.empty[(Symbol, Expr[T])]
}
}
.zipWithIndex
.map { case ((sym, expr), i) =>
val name = sym.name.toLowerCase
val body: Expr[Some[T]] = '{ Some(${ expr }) }

CaseDef(
Literal(StringConstant(labelNaming(sym.name))),
guard = None,
rhs = body.asTerm
)
}

val none = CaseDef(
Wildcard(),
None,
'{ Option.empty[T] }.asTerm
)

def mtch(s: Expr[String]): Expr[Option[T]] = {
Match(s.asTerm, cases :+ none).asExprOf[Option[T]]
}

normalize match {
case Some(nz) =>
'{ (s: String) =>
val in = ${ nz }(s)
${ mtch('in) }
}

case _ =>
'{ (s: String) => ${ mtch('s) } }
}
}
end EnumHelper
Loading

0 comments on commit 5a16eee

Please sign in to comment.