Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,14 @@ lazy val macros = crossProject(JSPlatform, JVMPlatform, NativePlatform)
name := "enumeratum-macros",
version := Versions.Macros.head,
crossScalaVersions := scalaVersionsAll, // eventually move this to aggregateProject once more 2.13 libs are out
libraryDependencies += {
libraryDependencies ++= {
if (scalaBinaryVersion.value == "3") {
"org.scala-lang" %% "scala3-compiler" % scalaVersion.value % Provided
Seq(
"org.scala-lang" %% "scala3-compiler" % scalaVersion.value % Provided,
"org.scala-lang" % "scala-reflect" % scala_2_13Version,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if there's a way to make this additional dependency

  • Contingent on actually using the mix mode macro
  • Only a compile time constraint?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I think only the consumer of the library knows if the mixed mode macro is being used, from here, sbt only knows that it's a scala3 build.

I believe we could mark this as Provided too, but then all consumers would need to explicitly provide it. Marking it provided here means I also need to add it as Provided in core. It would only need to be added as a normal/runtime dep if the project was using the mixed mode because I think this is only needed for the scala 2 portion of the build.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lloydmeta let me know if you have a preference here.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still having a think about this - has there been any better solution than the ones presented here? Adding another runtime dependency is not great.

Personally I think it's fine if the experience is subpar for "mixed mode" but not for Scala 2 or 3 users.

)
} else {
"org.scala-lang" % "scala-reflect" % scalaVersion.value
Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value)
}
},
libraryDependencies += scalaXmlTest
Expand Down Expand Up @@ -141,7 +144,7 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
if (useLocalVersion) {
Seq.empty
} else {
Seq("com.beachape" %%% "enumeratum-macros" % Versions.Macros.stable)
Seq("com.beachape" %%% "enumeratum-macros" % Versions.Macros.head)
}
},
libraryDependencies += scalaXmlTest
Expand Down Expand Up @@ -685,7 +688,7 @@ lazy val compilerSettings = Seq(

val base = {
if (scalaBinaryVersion.value == "3") {
minimal :+ "-deprecation"
minimal :+ "-Wconf:cat=deprecation:s"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why this was needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[error] -- Error: /home/gregg/lucid/enumeratum/macros/src/main/scala/enumeratum/compat/EnumMacros.scala:109:32 
[error] 109 |        val enclosingModule = c.enclosingClass match {
[error]     |                              ^^^^^^^^^^^^^^^^
[error]     |method enclosingClass in trait Enclosures is deprecated since 2.11.0: c.enclosingTree-style APIs are now deprecated; consult the scaladoc for more information

and a few more identical errors

} else {
minimal ++ Seq(
// "-Ywarn-adapted-args",
Expand Down
2 changes: 2 additions & 0 deletions enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import scala.collection.immutable.IndexedSeq

import scala.language.experimental.macros

import _root_.enumeratum.compat.EnumMacros

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

/** Returns a Seq of [[A]] objects that the macro was able to find.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import scala.language.experimental.macros

import scala.collection.immutable.IndexedSeq

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

private[enumeratum] trait IntEnumCompanion {

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

import _root_.enumeratum.compat
import scala.language.experimental.macros

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

// format: off
Expand All @@ -9,6 +12,7 @@ private[enumeratum] trait EnumCompat[A <: EnumEntry] { _enum: Enum[A] =>
* aren't using this method... why are you even bothering with this lib?
*/
inline def findValues: IndexedSeq[A] = ${ EnumMacros.findValuesImpl[A] }
def findValues: IndexedSeq[A] = macro compat.EnumMacros.findValuesImpl[A]
// format: on

/** The sequence of values for your [[Enum]]. You will typically want to implement this in your
Expand All @@ -26,6 +30,8 @@ private[enumeratum] trait EnumCompanion {
/** Finds the `Enum` companion object for a particular `EnumEntry`. */
implicit inline def materializeEnum[A <: EnumEntry]: Enum[A] =
${ EnumMacros.materializeEnumImpl[A, Enum[A]] }
implicit def materializeEnum[A <: EnumEntry]: Enum[A] = macro
compat.EnumMacros.materializeEnumImpl[A]
// format: on

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package enumeratum.values
import scala.language.experimental.macros

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

private[enumeratum] trait IntEnumCompanion {

/** Materializes an `IntEnum` for a given `IntEnumEntry`. */
implicit inline def materialiseIntValueEnum[EntryType <: IntEnumEntry]: IntEnum[EntryType] = ${
EnumMacros.materializeEnumImpl[EntryType, IntEnum[EntryType]]
}
implicit def materialiseIntValueEnum[EntryType <: IntEnumEntry]: IntEnum[EntryType] = macro
compat.EnumMacros.materializeEnumImpl[EntryType]
}

private[enumeratum] trait IntEnumCompat[A <: IntEnumEntry] { _enum: IntEnum[A] =>
Expand All @@ -22,6 +25,7 @@ private[enumeratum] trait IntEnumCompat[A <: IntEnumEntry] { _enum: IntEnum[A] =
*/
protected inline def findValues: IndexedSeq[A] =
${ ValueEnumMacros.findIntValueEntriesImpl[A] }
protected def findValues: IndexedSeq[A] = macro compat.ValueEnumMacros.findIntValueEntriesImpl[A]
// format: on
}

Expand All @@ -32,6 +36,8 @@ private[enumeratum] trait LongEnumCompanion {
implicit inline def materialiseLongValueEnum[EntryType <: LongEnumEntry]: LongEnum[EntryType] = ${
EnumMacros.materializeEnumImpl[EntryType, LongEnum[EntryType]]
}
implicit def materialiseLongValueEnum[EntryType <: LongEnumEntry]: LongEnum[EntryType] = macro
compat.EnumMacros.materializeEnumImpl[EntryType]
}

private[enumeratum] trait LongEnumCompat[A <: LongEnumEntry] { _enum: LongEnum[A] =>
Expand All @@ -43,6 +49,7 @@ private[enumeratum] trait LongEnumCompat[A <: LongEnumEntry] { _enum: LongEnum[A
* aren't using this method...why are you even bothering with this lib?
*/
protected inline def findValues: IndexedSeq[A] = ${ ValueEnumMacros.findLongValueEntriesImpl[A] }
protected def findValues: IndexedSeq[A] = macro compat.ValueEnumMacros.findLongValueEntriesImpl[A]
// format: on
}

Expand All @@ -54,6 +61,8 @@ private[enumeratum] trait ShortEnumCompanion {
${
EnumMacros.materializeEnumImpl[EntryType, ShortEnum[EntryType]]
}
implicit def materialiseShortValueEnum[EntryType <: ShortEnumEntry]: ShortEnum[EntryType] = macro
compat.EnumMacros.materializeEnumImpl[EntryType]
}

private[enumeratum] trait ShortEnumCompat[A <: ShortEnumEntry] { _enum: ShortEnum[A] =>
Expand All @@ -66,6 +75,8 @@ private[enumeratum] trait ShortEnumCompat[A <: ShortEnumEntry] { _enum: ShortEnu
protected inline def findValues: IndexedSeq[A] = ${
ValueEnumMacros.findShortValueEntriesImpl[A]
}
protected def findValues: IndexedSeq[A] = macro
compat.ValueEnumMacros.findShortValueEntriesImpl[A]
}

private[enumeratum] trait StringEnumCompanion {
Expand All @@ -76,6 +87,9 @@ private[enumeratum] trait StringEnumCompanion {
: StringEnum[EntryType] = ${
EnumMacros.materializeEnumImpl[EntryType, StringEnum[EntryType]]
}
implicit def materialiseStringValueEnum[EntryType <: StringEnumEntry]: StringEnum[
EntryType
] = macro compat.EnumMacros.materializeEnumImpl[EntryType]
}

private[enumeratum] trait StringEnumCompat[A <: StringEnumEntry] { _enum: StringEnum[A] =>
Expand All @@ -89,6 +103,8 @@ private[enumeratum] trait StringEnumCompat[A <: StringEnumEntry] { _enum: String
protected inline def findValues: IndexedSeq[A] = ${
ValueEnumMacros.findStringValueEntriesImpl[A]
}
protected def findValues: IndexedSeq[A] = macro
compat.ValueEnumMacros.findStringValueEntriesImpl[A]
// format: on
}

Expand All @@ -99,6 +115,8 @@ private[enumeratum] trait ByteEnumCompanion {
implicit inline def materialiseByteValueEnum[EntryType <: ByteEnumEntry]: ByteEnum[EntryType] = ${
EnumMacros.materializeEnumImpl[EntryType, ByteEnum[EntryType]]
}
implicit def materialiseByteValueEnum[EntryType <: ByteEnumEntry]: ByteEnum[EntryType] = macro
compat.EnumMacros.materializeEnumImpl[EntryType]
}

private[enumeratum] trait ByteEnumCompat[A <: ByteEnumEntry] { _enum: ByteEnum[A] =>
Expand All @@ -112,6 +130,7 @@ private[enumeratum] trait ByteEnumCompat[A <: ByteEnumEntry] { _enum: ByteEnum[A
protected inline def findValues: IndexedSeq[A] = ${
ValueEnumMacros.findByteValueEntriesImpl[A]
}
protected def findValues: IndexedSeq[A] = macro compat.ValueEnumMacros.findByteValueEntriesImpl[A]
// format: on
}

Expand All @@ -122,6 +141,8 @@ private[enumeratum] trait CharEnumCompanion {
implicit inline def materialiseCharValueEnum[EntryType <: CharEnumEntry]: CharEnum[EntryType] = ${
EnumMacros.materializeEnumImpl[EntryType, CharEnum[EntryType]]
}
implicit def materialiseCharValueEnum[EntryType <: CharEnumEntry]: CharEnum[EntryType] = macro
compat.EnumMacros.materializeEnumImpl[EntryType]
}

private[enumeratum] trait CharEnumCompat[A <: CharEnumEntry] { _enum: CharEnum[A] =>
Expand All @@ -135,5 +156,6 @@ private[enumeratum] trait CharEnumCompat[A <: CharEnumEntry] { _enum: CharEnum[A
protected inline def findValues: IndexedSeq[A] = ${
ValueEnumMacros.findCharValueEntriesImpl[A]
}
protected def findValues: IndexedSeq[A] = macro compat.ValueEnumMacros.findCharValueEntriesImpl[A]
// format: on
}
1 change: 1 addition & 0 deletions macros/compat/src/main/scala-3
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package enumeratum
package compat

import ContextUtils.Context

Expand All @@ -10,7 +11,7 @@ object EnumMacros {

/** Finds any [A] in the current scope and returns an expression for a list of them
*/
def findValuesImpl[A: c.WeakTypeTag](c: Context): c.Expr[IndexedSeq[A]] = {
def findValuesImpl[A: c.WeakTypeTag](c: Context): c.Tree = {
import c.universe._
val typeSymbol = weakTypeOf[A].typeSymbol
validateType(c)(typeSymbol)
Expand Down Expand Up @@ -160,20 +161,22 @@ object EnumMacros {
@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
private[enumeratum] def buildSeqExpr[A: c.WeakTypeTag](c: Context)(
subclassSymbols: Seq[c.universe.Symbol]
) = {
): c.Tree = {
import c.universe._
val resultType = weakTypeOf[A]
val indexedSeq = Ident(c.mirror.staticModule(classOf[IndexedSeq[A]].getName))
Copy link

Copilot AI Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using classOf[IndexedSeq[A]].getName with c.mirror.staticModule is more fragile than the original reify(IndexedSeq).tree approach. The classOf[IndexedSeq[A]] will fail at runtime because A is a type parameter. Consider using "scala.collection.immutable.IndexedSeq" as a string literal instead.

Suggested change
val indexedSeq = Ident(c.mirror.staticModule(classOf[IndexedSeq[A]].getName))
val indexedSeq = Ident(c.mirror.staticModule("scala.collection.immutable.IndexedSeq"))

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this change was necessary because we no longer have a WeakTypeTag[IndexedSeq[A\] available.

if (subclassSymbols.isEmpty) {
c.Expr[IndexedSeq[A]](reify(IndexedSeq.empty[A]).tree)
TypeApply(
Select(indexedSeq, ContextUtils.termName(c)("empty")),
List(TypeTree(resultType))
)
} else {
c.Expr[IndexedSeq[A]](
Apply(
TypeApply(
Select(reify(IndexedSeq).tree, ContextUtils.termName(c)("apply")),
List(TypeTree(resultType))
),
subclassSymbols.map(Ident(_)).toList
)
Apply(
TypeApply(
Select(indexedSeq, ContextUtils.termName(c)("apply")),
List(TypeTree(resultType))
),
subclassSymbols.map(Ident(_)).toList
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package enumeratum
package compat

import ContextUtils.Context

Expand All @@ -15,7 +16,7 @@ object ValueEnumMacros {
*/
def findIntValueEntriesImpl[ValueEntryType: c.WeakTypeTag](
c: Context
): c.Expr[IndexedSeq[ValueEntryType]] = {
): c.Tree = {
findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Int](c)(identity)
}

Expand All @@ -25,7 +26,7 @@ object ValueEnumMacros {
*/
def findLongValueEntriesImpl[ValueEntryType: c.WeakTypeTag](
c: Context
): c.Expr[IndexedSeq[ValueEntryType]] = {
): c.Tree = {
findValueEntriesImpl[ValueEntryType, ContextUtils.CTLong, Long](c)(identity)
}

Expand All @@ -38,7 +39,7 @@ object ValueEnumMacros {
*/
def findShortValueEntriesImpl[ValueEntryType: c.WeakTypeTag](
c: Context
): c.Expr[IndexedSeq[ValueEntryType]] = {
): c.Tree = {
findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Short](c)(
_.toShort
) // do a transform because there is no such thing as Short literals
Expand All @@ -52,7 +53,7 @@ object ValueEnumMacros {
*/
def findStringValueEntriesImpl[ValueEntryType: c.WeakTypeTag](
c: Context
): c.Expr[IndexedSeq[ValueEntryType]] = {
): c.Tree = {
findValueEntriesImpl[ValueEntryType, String, String](c)(identity)
}

Expand All @@ -64,7 +65,7 @@ object ValueEnumMacros {
*/
def findByteValueEntriesImpl[ValueEntryType: c.WeakTypeTag](
c: Context
): c.Expr[IndexedSeq[ValueEntryType]] = {
): c.Tree = {
findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Byte](c)(_.toByte)
}

Expand All @@ -76,7 +77,7 @@ object ValueEnumMacros {
*/
def findCharValueEntriesImpl[ValueEntryType: c.WeakTypeTag](
c: Context
): c.Expr[IndexedSeq[ValueEntryType]] = {
): c.Tree = {
findValueEntriesImpl[ValueEntryType, ContextUtils.CTChar, Char](c)(identity)
}

Expand All @@ -88,7 +89,7 @@ object ValueEnumMacros {
ProcessedValue
](c: Context)(
processFoundValues: ValueType => ProcessedValue
): c.Expr[IndexedSeq[ValueEntryType]] = {
): c.Tree = {
import c.universe._
val typeSymbol = weakTypeOf[ValueEntryType].typeSymbol
EnumMacros.validateType(c)(typeSymbol)
Expand All @@ -104,7 +105,11 @@ object ValueEnumMacros {
processFoundValues
)

if (weakTypeOf[ValueEntryType] <:< c.typeOf[AllowAlias]) {
if (
weakTypeOf[ValueEntryType].baseClasses.contains(
c.mirror.staticClass(classOf[AllowAlias].getName)
)
Comment on lines +109 to +111
Copy link

Copilot AI Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The refactored type check using baseClasses.contains and c.mirror.staticClass(classOf[AllowAlias].getName) is more complex than the original <:< subtype check. Consider if this change is necessary for Scala 2/3 compatibility or if the original approach could be preserved.

Suggested change
weakTypeOf[ValueEntryType].baseClasses.contains(
c.mirror.staticClass(classOf[AllowAlias].getName)
)
weakTypeOf[ValueEntryType] <:< weakTypeOf[AllowAlias]

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a similar problem here: the Scala 3 compiler is unable to summon WeakTypeTags

[error] -- [E172] Type Error: /home/jpeterson/lucid/enumeratum/macros/src/main/scala/enumeratum/compat/ValueEnumMacros.scala:108:61 
[error] 108 |    if (weakTypeOf[ValueEntryType] <:< weakTypeOf[AllowAlias]) {
[error]     |                                                             ^
[error]     |             No WeakTypeTag available for enumeratum.values.AllowAlias

) {
// Skip the uniqueness check
} else {
// Make sure the processed found value implementations are unique
Expand Down
1 change: 1 addition & 0 deletions macros/src/test/scala-2/enumeratum/FindValEnums.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package enumeratum

import enumeratum.compat.ValueEnumMacros
import scala.language.experimental.macros

/** Created by Lloyd on 1/4/17.
Expand Down