From 9636290ad7e9a34691a41ca97cb21a05f12a1b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Mon, 8 Aug 2022 15:09:56 +0200 Subject: [PATCH 01/16] EnumMacros --- .scalafmt.conf | 9 +- build.sbt | 159 ++++++---- .../main/scala-2/enumeratum/EnumCompat.scala | 21 ++ .../enumeratum/values/ValueEnumCompat.scala | 123 ++++++++ .../main/scala-3/enumeratum/EnumCompat.scala | 13 + .../enumeratum/values/ValueEnumCompat.scala | 129 ++++++++ .../src/main/scala/enumeratum/Enum.scala | 18 +- .../scala/enumeratum/values/ValueEnum.scala | 122 +------- .../scala-2/enumeratum/EnumSpecCompat.scala | 14 + .../scala-3/enumeratum/EnumSpecCompat.scala | 3 + .../src/test/scala/enumeratum/EnumSpec.scala | 41 +-- .../src/test/scala/enumeratum/Models.scala | 4 - .../enumeratum/values/ValueEnumHelpers.scala | 38 +-- .../enumeratum/values/ValueEnumSpec.scala | 3 + .../scala-2.13/enumeratum/ContextUtils.scala | 15 +- .../scala-3/enumeratum/ContextUtils.scala | 36 +++ .../enumeratum/EnumMacros.scala | 3 +- .../enumeratum/ValueEnumMacros.scala | 0 .../main/scala-3/enumeratum/EnumMacros.scala | 207 +++++++++++++ .../scala-3/enumeratum/ValueEnumMacros.scala | 291 ++++++++++++++++++ project/build.properties | 2 +- project/plugins.sbt | 2 +- 22 files changed, 1010 insertions(+), 243 deletions(-) create mode 100644 enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala create mode 100644 enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala create mode 100644 enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala create mode 100644 enumeratum-core/src/main/scala-3/enumeratum/values/ValueEnumCompat.scala create mode 100644 enumeratum-core/src/test/scala-2/enumeratum/EnumSpecCompat.scala create mode 100644 enumeratum-core/src/test/scala-3/enumeratum/EnumSpecCompat.scala create mode 100644 macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala rename macros/src/main/{scala => scala-2}/enumeratum/EnumMacros.scala (98%) rename macros/src/main/{scala => scala-2}/enumeratum/ValueEnumMacros.scala (100%) create mode 100644 macros/src/main/scala-3/enumeratum/EnumMacros.scala create mode 100644 macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala diff --git a/.scalafmt.conf b/.scalafmt.conf index cb2eb7f6..aa82a84f 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,3 +1,10 @@ -version = 3.0.0-RC6 +version = 3.5.8 +runner.dialect = Scala213Source3 style = defaultWithAlign maxColumn = 100 + +fileOverride { + "glob:**/src/*/scala-3/**/*.scala" { + runner.dialect = scala3 + } +} diff --git a/build.sbt b/build.sbt index bdd13a56..756c9061 100644 --- a/build.sbt +++ b/build.sbt @@ -3,68 +3,74 @@ import sbtbuildinfo.BuildInfoPlugin.autoImport._ import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} lazy val scala_2_11Version = "2.11.12" -lazy val scala_2_12Version = "2.12.14" -lazy val scala_2_13Version = "2.13.6" -lazy val scalaVersionsAll = Seq(scala_2_11Version, scala_2_12Version, scala_2_13Version) +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 scalaVersionsAll = Seq(scala_2_11Version, scala_2_12Version, scala_2_13Version, scala_3Version) lazy val theScalaVersion = scala_2_12Version lazy val scalaTestVersion = "3.2.9" // Library versions -lazy val reactiveMongoVersion = "1.0.0" +lazy val reactiveMongoVersion = "1.0.0-RC6" lazy val json4sVersion = "4.0.3" lazy val quillVersion = "4.1.0" def theDoobieVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor >= 12 => "1.0.0-RC2" - case Some((2, scalaMajor)) if scalaMajor == 11 => "0.7.1" + case Some((2, scalaMajor)) if scalaMajor <= 11 => "0.7.1" + case Some(_) => "1.0.0-RC2" case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion") + throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Doobie") } def theArgonautVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor >= 12 => "6.3.0" case Some((2, scalaMajor)) if scalaMajor >= 11 => "6.2.5" + case Some(_) => "6.3.0" + case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion") + throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Argonaut") } def thePlayVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor >= 12 => "2.8.0" + case Some((3, _)) => "2.8.0" case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion") + throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Play") } def theSlickVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor >= 11 => "3.3.3" + case Some((2, scalaMajor)) if scalaMajor <= 11 => "3.3.3" + case Some(_) => "3.3.3" case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion") + throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Slick") } def theCatsVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor >= 12 => "2.6.1" - case Some((2, scalaMajor)) if scalaMajor >= 11 => "2.0.0" + case Some((2, scalaMajor)) if scalaMajor <= 11 => "2.0.0" + case Some(_) => "2.6.1" case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion") + throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Cats") } def thePlayJsonVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor >= 12 => "2.9.0" + case Some((2, scalaMajor)) if scalaMajor <= 11 => "2.7.3" // TODO drop 2.11 as play-json 2.7.x supporting Scala.js 1.x is unlikely? - case Some((2, scalaMajor)) if scalaMajor >= 11 => "2.7.3" + + case Some(_) => "2.9.0" case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion") + throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for play-json") } def theCirceVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { + case Some((3, _)) => "0.14.1" case Some((2, scalaMajor)) if scalaMajor >= 12 => "0.14.1" case Some((2, scalaMajor)) if scalaMajor >= 11 => "0.11.1" case _ => @@ -81,7 +87,7 @@ def scalaTestPlay(scalaVersion: String) = CrossVersion.partialVersion(scalaVersi case Some((2, scalaMajor)) if scalaMajor >= 12 => "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion") + throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for play-test") } lazy val baseProjectRefs = @@ -197,39 +203,63 @@ lazy val macrosAggregate = aggregateProject("macros", macrosJS, macrosJVM) lazy val macros = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("macros")) - .settings(testSettings: _*) - .jsSettings(jsTestSettings: _*) - .settings(commonWithPublishSettings: _*) - .settings(withCompatUnmanagedSources(jsJvmCrossProject = true, includeTestSrcs = false): _*) + .settings(testSettings) + .jsSettings(jsTestSettings) + .settings(commonWithPublishSettings) + .settings(withCompatUnmanagedSources( + jsJvmCrossProject = true, + includeTestSrcs = false)) .settings( name := "enumeratum-macros", version := Versions.Macros.head, crossScalaVersions := scalaVersionsAll, // eventually move this to aggregateProject once more 2.13 libs are out - libraryDependencies ++= Seq( - "org.scala-lang" % "scala-reflect" % scalaVersion.value - ) + libraryDependencies ++= { + if (scalaBinaryVersion.value startsWith "2.") { + Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value) + } else { + Seq( + "org.scala-lang" %% "scala3-compiler" % scalaVersion.value % Provided + ) + } + } ) lazy val macrosJS = macros.js lazy val macrosJVM = macros.jvm +lazy val devMacros = sys.props.get("enumeratum.devMacros").nonEmpty + // Aggregates core lazy val coreAggregate = aggregateProject("core", coreJS, coreJVM) lazy val core = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("enumeratum-core")) - .settings(testSettings: _*) - .jsSettings(jsTestSettings: _*) - .settings(commonWithPublishSettings: _*) + .settings(testSettings) + .jsSettings(jsTestSettings) + .settings(commonWithPublishSettings) .settings( - name := "enumeratum", - version := Versions.Core.head, - crossScalaVersions := scalaVersionsAll, - libraryDependencies += "com.beachape" %% "enumeratum-macros" % Versions.Macros.stable + name := "enumeratum", + version := Versions.Core.head, + crossScalaVersions := scalaVersionsAll, + libraryDependencies ++= { + if (devMacros) { + Seq.empty + } else { + Seq("com.beachape" %% "enumeratum-macros" % Versions.Macros.stable) + } + } ) -// .dependsOn(macros) // used for testing macros -lazy val coreJS = core.js -lazy val coreJVM = core.jvm + +def configureWithMacro(m: Project): Project => Project = { p => + if (devMacros) { // used for testing macros + p.dependsOn(m) + } else { + p + } +} + +lazy val coreJS = core.js.configure(configureWithMacro(macrosJS)) +lazy val coreJVM = core.jvm.configure(configureWithMacro(macrosJVM)) lazy val testsAggregate = aggregateProject("test", enumeratumTestJs, enumeratumTestJvm) // Project models used in test for some subprojects @@ -316,8 +346,8 @@ lazy val enumeratumPlayJsonJs = enumeratumPlayJson.js lazy val enumeratumPlayJsonJvm = enumeratumPlayJson.jvm lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratum-play")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) .settings( version := "1.7.1-SNAPSHOT", crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version), @@ -329,16 +359,18 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu scalaTestPlay(scalaVersion.value) ) ) - .settings(withCompatUnmanagedSources(jsJvmCrossProject = false, includeTestSrcs = true): _*) + .settings(withCompatUnmanagedSources( + jsJvmCrossProject = false, + includeTestSrcs = true)) .dependsOn(enumeratumPlayJsonJvm % "compile->compile;test->test") lazy val circeAggregate = aggregateProject("circe", enumeratumCirceJs, enumeratumCirceJvm) lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("enumeratum-circe")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) - .jsSettings(jsTestSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) + .jsSettings(jsTestSettings) .settings( name := "enumeratum-circe", version := "1.7.1-SNAPSHOT", @@ -552,8 +584,7 @@ lazy val compilerSettings = Seq( } }, scalacOptions in (Compile, compile) ++= { - val base = Seq( - "-Xlog-free-terms", + val minimal = Seq( "-encoding", "UTF-8", // yes, this is 2 args "-feature", @@ -561,13 +592,24 @@ lazy val compilerSettings = Seq( "-language:higherKinds", "-language:implicitConversions", "-unchecked", - "-Xfatal-warnings", -// "-Ywarn-adapted-args", - "-Ywarn-dead-code", // N.B. doesn't work well with the ??? hole - "-Ywarn-numeric-widen", - "-Ywarn-value-discard", - "-Xfuture" + "-Xfatal-warnings" ) + + val base = { + if (scalaBinaryVersion.value == "3") { + minimal + } else { + minimal ++ Seq( + // "-Ywarn-adapted-args", + "-Xlog-free-terms", + "-Ywarn-dead-code", // N.B. doesn't work well with the ??? hole + "-Ywarn-numeric-widen", + "-Ywarn-value-discard", + "-Xfuture" + ) + } + } + CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, m)) if m >= 13 => base.filterNot(flag => @@ -576,13 +618,16 @@ lazy val compilerSettings = Seq( Seq( /*"-deprecation:false", */ "-Xlint:-unused,_" ) // unused-import breaks Circe Either shim + case Some((2, m)) if m >= 12 => base ++ Seq( "-deprecation:false", "-Xlint:-unused,_" ) // unused-import breaks Circe Either shim + case Some((2, 11)) => base ++ Seq("-deprecation:false", "-Xlint", "-Ywarn-unused-import") - case _ => base ++ Seq("-Xlint") + case Some((2, _)) => base ++ Seq("-Xlint") + case _ => base } } ) @@ -672,13 +717,21 @@ def withCompatUnmanagedSources( ): Seq[Setting[_]] = { def compatDirs(projectbase: File, scalaVersion: String, isMain: Boolean) = { val base = if (jsJvmCrossProject) projectbase / ".." else projectbase + val cat = if (isMain) "main" else "test" + CrossVersion.partialVersion(scalaVersion) match { + case Some((3, _)) => + Seq(base / "compat" / "src" / cat / "scala-3") + .map(_.getCanonicalFile) + case Some((2, scalaMajor)) if scalaMajor >= 13 => - Seq(base / "compat" / "src" / (if (isMain) "main" else "test") / "scala-2.13") + Seq(base / "compat" / "src" / cat / "scala-2.13") .map(_.getCanonicalFile) + case Some((2, scalaMajor)) if scalaMajor >= 11 => - Seq(base / "compat" / "src" / (if (isMain) "main" else "test") / "scala-2.11") + Seq(base / "compat" / "src" / cat / "scala-2.11") .map(_.getCanonicalFile) + case _ => Nil } } diff --git a/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala b/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala new file mode 100644 index 00000000..1fd7952f --- /dev/null +++ b/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala @@ -0,0 +1,21 @@ +package enumeratum + +import scala.language.experimental.macros + +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? + */ + protected def findValues: IndexedSeq[A] = + macro EnumMacros.findValuesImpl[A] +} + +private[enumeratum] trait EnumCompanion { + + /** Finds the `Enum` companion object for a particular `EnumEntry`. */ + implicit def materializeEnum[A <: EnumEntry]: Enum[A] = + macro EnumMacros.materializeEnumImpl[A] +} diff --git a/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala b/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala new file mode 100644 index 00000000..3e4f1f0a --- /dev/null +++ b/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala @@ -0,0 +1,123 @@ +package enumeratum.values + +import scala.language.experimental.macros + +import _root_.enumeratum.{Enum, EnumMacros, ValueEnumMacros} + +private[enumeratum] trait IntEnumCompanion { + + /** Materializes an IntEnum for a given IntEnumEntry + */ + implicit def materialiseIntValueEnum[EntryType <: IntEnumEntry]: IntEnum[EntryType] = + macro EnumMacros.materializeEnumImpl[EntryType] +} + +private[enumeratum] trait IntEnumCompat[A <: IntEnumEntry] { _: IntEnum[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? + */ + protected def findValues: IndexedSeq[A] = + macro ValueEnumMacros.findIntValueEntriesImpl[A] +} + +private[enumeratum] trait LongEnumCompanion { + + /** Materializes a LongEnum for an scope LongEnumEntry + */ + implicit def materialiseLongValueEnum[EntryType <: LongEnumEntry]: LongEnum[EntryType] = + macro EnumMacros.materializeEnumImpl[EntryType] + +} + +private[enumeratum] trait LongEnumCompat[A <: LongEnumEntry] { _: LongEnum[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? + */ + protected def findValues: IndexedSeq[A] = + macro ValueEnumMacros.findLongValueEntriesImpl[A] +} + +private[enumeratum] trait ShortEnumCompanion { + + /** Materializes a ShortEnum for an in-scope ShortEnumEntry + */ + implicit def materialiseShortValueEnum[EntryType <: ShortEnumEntry]: ShortEnum[EntryType] = + macro EnumMacros.materializeEnumImpl[EntryType] +} + +private[enumeratum] trait ShortEnumCompat[A <: ShortEnumEntry] { _: ShortEnum[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? + */ + protected def findValues: IndexedSeq[A] = + macro ValueEnumMacros.findShortValueEntriesImpl[A] +} + +private[enumeratum] trait StringEnumCompanion { + + /** Materializes a StringEnum for an in-scope StringEnumEntry + */ + implicit def materialiseStringValueEnum[EntryType <: StringEnumEntry]: StringEnum[EntryType] = + macro EnumMacros.materializeEnumImpl[EntryType] + +} + +private[enumeratum] trait StringEnumCompat[A <: StringEnumEntry] { _: StringEnum[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? + */ + protected def findValues: IndexedSeq[A] = + macro ValueEnumMacros.findStringValueEntriesImpl[A] +} + +private[enumeratum] trait ByteEnumCompanion { + + /** Materializes a ByteEnum for an in-scope ByteEnumEntry + */ + implicit def materialiseByteValueEnum[EntryType <: ByteEnumEntry]: ByteEnum[EntryType] = + macro EnumMacros.materializeEnumImpl[EntryType] + +} + +private[enumeratum] trait ByteEnumCompat[A <: ByteEnumEntry] { _: ByteEnum[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? + */ + protected def findValues: IndexedSeq[A] = + macro ValueEnumMacros.findByteValueEntriesImpl[A] +} + +private[enumeratum] trait CharEnumCompanion { + + /** Materializes a CharEnum for an in-scope CharEnumEntry + */ + implicit def materialiseCharValueEnum[EntryType <: CharEnumEntry]: CharEnum[EntryType] = + macro EnumMacros.materializeEnumImpl[EntryType] + +} + +private[enumeratum] trait CharEnumCompat[A <: CharEnumEntry] { _: CharEnum[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? + */ + protected def findValues: IndexedSeq[A] = + macro ValueEnumMacros.findCharValueEntriesImpl[A] +} diff --git a/enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala b/enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala new file mode 100644 index 00000000..73aae7a1 --- /dev/null +++ b/enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala @@ -0,0 +1,13 @@ +package enumeratum + +private[enumeratum] trait EnumCompat[A <: EnumEntry] { _enum: Enum[A] => + inline def findValues: IndexedSeq[A] = ${ EnumMacros.findValuesImpl[A] } +} + +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]] } + +} diff --git a/enumeratum-core/src/main/scala-3/enumeratum/values/ValueEnumCompat.scala b/enumeratum-core/src/main/scala-3/enumeratum/values/ValueEnumCompat.scala new file mode 100644 index 00000000..f5da15a2 --- /dev/null +++ b/enumeratum-core/src/main/scala-3/enumeratum/values/ValueEnumCompat.scala @@ -0,0 +1,129 @@ +package enumeratum.values + +import scala.language.experimental.macros + +import _root_.enumeratum.{Enum, EnumMacros, ValueEnumMacros} + +private[enumeratum] trait IntEnumCompanion { + + /** Materializes an `IntEnum` for a given `IntEnumEntry`. */ + implicit inline def materialiseIntValueEnum[EntryType <: IntEnumEntry]: IntEnum[EntryType] = ${ + EnumMacros.materializeEnumImpl[EntryType, IntEnum[EntryType]] + } +} + +private[enumeratum] trait IntEnumCompat[A <: IntEnumEntry] { _enum: IntEnum[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? + */ + protected inline def findValues: IndexedSeq[A] = + ${ ValueEnumMacros.findIntValueEntriesImpl[A] } +} + +private[enumeratum] trait LongEnumCompanion { + + /** Materializes a LongEnum for an scope LongEnumEntry + */ + implicit inline def materialiseLongValueEnum[EntryType <: LongEnumEntry]: LongEnum[EntryType] = ${ + EnumMacros.materializeEnumImpl[EntryType, LongEnum[EntryType]] + } +} + +private[enumeratum] trait LongEnumCompat[A <: LongEnumEntry] { _enum: LongEnum[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? + */ + protected inline def findValues: IndexedSeq[A] = ${ ValueEnumMacros.findLongValueEntriesImpl[A] } +} + +private[enumeratum] trait ShortEnumCompanion { + + /** Materializes a ShortEnum for an in-scope ShortEnumEntry + */ + implicit inline def materialiseShortValueEnum[EntryType <: ShortEnumEntry]: ShortEnum[EntryType] = + ${ + EnumMacros.materializeEnumImpl[EntryType, ShortEnum[EntryType]] + } +} + +private[enumeratum] trait ShortEnumCompat[A <: ShortEnumEntry] { _enum: ShortEnum[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? + */ + protected inline def findValues: IndexedSeq[A] = ${ + ValueEnumMacros.findShortValueEntriesImpl[A] + } +} + +private[enumeratum] trait StringEnumCompanion { + + /** Materializes a StringEnum for an in-scope StringEnumEntry + */ + implicit inline def materialiseStringValueEnum[EntryType <: StringEnumEntry] + : StringEnum[EntryType] = ${ + EnumMacros.materializeEnumImpl[EntryType, StringEnum[EntryType]] + } +} + +private[enumeratum] trait StringEnumCompat[A <: StringEnumEntry] { _enum: StringEnum[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? + */ + protected inline def findValues: IndexedSeq[A] = ${ + ValueEnumMacros.findStringValueEntriesImpl[A] + } +} + +private[enumeratum] trait ByteEnumCompanion { + + /** Materializes a ByteEnum for an in-scope ByteEnumEntry + */ + implicit inline def materialiseByteValueEnum[EntryType <: ByteEnumEntry]: ByteEnum[EntryType] = ${ + EnumMacros.materializeEnumImpl[EntryType, ByteEnum[EntryType]] + } +} + +private[enumeratum] trait ByteEnumCompat[A <: ByteEnumEntry] { _enum: ByteEnum[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? + */ + protected inline def findValues: IndexedSeq[A] = ${ + ValueEnumMacros.findByteValueEntriesImpl[A] + } +} + +private[enumeratum] trait CharEnumCompanion { + + /** Materializes a CharEnum for an in-scope CharEnumEntry + */ + implicit inline def materialiseCharValueEnum[EntryType <: CharEnumEntry]: CharEnum[EntryType] = ${ + EnumMacros.materializeEnumImpl[EntryType, CharEnum[EntryType]] + } +} + +private[enumeratum] trait CharEnumCompat[A <: CharEnumEntry] { _enum: CharEnum[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? + */ + protected inline def findValues: IndexedSeq[A] = ${ + ValueEnumMacros.findCharValueEntriesImpl[A] + } +} diff --git a/enumeratum-core/src/main/scala/enumeratum/Enum.scala b/enumeratum-core/src/main/scala/enumeratum/Enum.scala index ac0a189f..15c99715 100644 --- a/enumeratum-core/src/main/scala/enumeratum/Enum.scala +++ b/enumeratum-core/src/main/scala/enumeratum/Enum.scala @@ -1,7 +1,6 @@ package enumeratum import scala.collection.immutable._ -import scala.language.experimental.macros /** All the cool kids have their own Enumeration implementation, most of which try to do so in the * name of implementing exhaustive pattern matching. @@ -32,7 +31,7 @@ import scala.language.experimental.macros * @tparam A * The sealed trait */ -trait Enum[A <: EnumEntry] { +trait Enum[A <: EnumEntry] extends EnumCompat[A] { /** Map of [[A]] object names to [[A]] s */ @@ -169,13 +168,6 @@ trait Enum[A <: EnumEntry] { */ def indexOf(member: A): Int = valuesToIndex.getOrElse(member, -1) - /** Method that 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? - */ - protected def findValues: IndexedSeq[A] = macro EnumMacros.findValuesImpl[A] - private def buildNotFoundMessage(notFoundName: String): String = { s"$notFoundName is not a member of Enum ($existingEntriesString)" } @@ -185,10 +177,4 @@ trait Enum[A <: EnumEntry] { } -object Enum { - - /** Finds the Enum companion object for a particular EnumEntry - */ - implicit def materializeEnum[A <: EnumEntry]: Enum[A] = macro EnumMacros.materializeEnumImpl[A] - -} +object Enum extends EnumCompanion diff --git a/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala b/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala index 1449d57e..210a5afd 100644 --- a/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala +++ b/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala @@ -1,9 +1,6 @@ package enumeratum.values -import enumeratum.{EnumMacros, ValueEnumMacros} - -import scala.collection.immutable._ -import scala.language.experimental.macros +import _root_.enumeratum.Enum /** Base trait for a Value-based enums. * @@ -80,79 +77,25 @@ sealed trait ValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { * and macro invocations cannot provide implementations for a super class's abstract method */ -object IntEnum { - - /** Materializes an IntEnum for a given IntEnumEntry - */ - implicit def materialiseIntValueEnum[EntryType <: IntEnumEntry]: IntEnum[EntryType] = - macro EnumMacros.materializeEnumImpl[EntryType] - -} +object IntEnum extends IntEnumCompanion /** Value enum with [[IntEnumEntry]] entries */ -trait IntEnum[A <: IntEnumEntry] extends ValueEnum[Int, A] { +trait IntEnum[A <: IntEnumEntry] extends ValueEnum[Int, A] with IntEnumCompat[A] - /** Method that 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? - */ - protected def findValues: IndexedSeq[A] = macro ValueEnumMacros.findIntValueEntriesImpl[A] - -} - -object LongEnum { - - /** Materializes a LongEnum for an scope LongEnumEntry - */ - implicit def materialiseLongValueEnum[EntryType <: LongEnumEntry]: LongEnum[EntryType] = - macro EnumMacros.materializeEnumImpl[EntryType] - -} +object LongEnum extends LongEnumCompanion /** Value enum with [[LongEnumEntry]] entries */ -trait LongEnum[A <: LongEnumEntry] extends ValueEnum[Long, A] { - - /** Method that 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? - */ - final protected def findValues: IndexedSeq[A] = macro ValueEnumMacros.findLongValueEntriesImpl[A] -} - -object ShortEnum { +trait LongEnum[A <: LongEnumEntry] extends ValueEnum[Long, A] with LongEnumCompat[A] - /** Materializes a ShortEnum for an in-scope ShortEnumEntry - */ - implicit def materialiseShortValueEnum[EntryType <: ShortEnumEntry]: ShortEnum[EntryType] = - macro EnumMacros.materializeEnumImpl[EntryType] - -} +object ShortEnum extends ShortEnumCompanion /** Value enum with [[ShortEnumEntry]] entries */ -trait ShortEnum[A <: ShortEnumEntry] extends ValueEnum[Short, A] { - - /** Method that 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? - */ - final protected def findValues: IndexedSeq[A] = - macro ValueEnumMacros.findShortValueEntriesImpl[A] -} - -object StringEnum { +trait ShortEnum[A <: ShortEnumEntry] extends ValueEnum[Short, A] with ShortEnumCompat[A] - /** Materializes a StringEnum for an in-scope StringEnumEntry - */ - implicit def materialiseStringValueEnum[EntryType <: StringEnumEntry]: StringEnum[EntryType] = - macro EnumMacros.materializeEnumImpl[EntryType] - -} +object StringEnum extends StringEnumCompanion /** Value enum with [[StringEnumEntry]] entries * @@ -162,25 +105,9 @@ object StringEnum { * Note that uniqueness is only guaranteed if you do not do any runtime string manipulation on * values. */ -trait StringEnum[A <: StringEnumEntry] extends ValueEnum[String, A] { +trait StringEnum[A <: StringEnumEntry] extends ValueEnum[String, A] with StringEnumCompat[A] - /** Method that 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? - */ - final protected def findValues: IndexedSeq[A] = - macro ValueEnumMacros.findStringValueEntriesImpl[A] -} - -object ByteEnum { - - /** Materializes a ByteEnum for an in-scope ByteEnumEntry - */ - implicit def materialiseByteValueEnum[EntryType <: ByteEnumEntry]: ByteEnum[EntryType] = - macro EnumMacros.materializeEnumImpl[EntryType] - -} +object ByteEnum extends ByteEnumCompanion /** Value enum with [[ByteEnumEntry]] entries * @@ -190,24 +117,9 @@ object ByteEnum { * Note that uniqueness is only guaranteed if you do not do any runtime string manipulation on * values. */ -trait ByteEnum[A <: ByteEnumEntry] extends ValueEnum[Byte, A] { +trait ByteEnum[A <: ByteEnumEntry] extends ValueEnum[Byte, A] with ByteEnumCompat[A] - /** Method that 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? - */ - final protected def findValues: IndexedSeq[A] = macro ValueEnumMacros.findByteValueEntriesImpl[A] -} - -object CharEnum { - - /** Materializes a CharEnum for an in-scope CharEnumEntry - */ - implicit def materialiseCharValueEnum[EntryType <: CharEnumEntry]: CharEnum[EntryType] = - macro EnumMacros.materializeEnumImpl[EntryType] - -} +object CharEnum extends CharEnumCompanion /** Value enum with [[CharEnumEntry]] entries * @@ -217,12 +129,4 @@ object CharEnum { * Note that uniqueness is only guaranteed if you do not do any runtime string manipulation on * values. */ -trait CharEnum[A <: CharEnumEntry] extends ValueEnum[Char, A] { - - /** Method that 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? - */ - final protected def findValues: IndexedSeq[A] = macro ValueEnumMacros.findCharValueEntriesImpl[A] -} +trait CharEnum[A <: CharEnumEntry] extends ValueEnum[Char, A] with CharEnumCompat[A] diff --git a/enumeratum-core/src/test/scala-2/enumeratum/EnumSpecCompat.scala b/enumeratum-core/src/test/scala-2/enumeratum/EnumSpecCompat.scala new file mode 100644 index 00000000..081bef59 --- /dev/null +++ b/enumeratum-core/src/test/scala-2/enumeratum/EnumSpecCompat.scala @@ -0,0 +1,14 @@ +package enumeratum + +private[enumeratum] trait EnumSpecCompat { _: EnumSpec => + describe("Scala2 in") { + it( + "should fail to compile if either enum in the parameter list is not instance of the same enum type as the checked one" + ) { + """ + val myEnum: DummyEnum = DummyEnum.Hi + myEnum.in(DummyEnum.Hello, SnakeEnum.ShoutGoodBye) + """ shouldNot compile + } + } +} diff --git a/enumeratum-core/src/test/scala-3/enumeratum/EnumSpecCompat.scala b/enumeratum-core/src/test/scala-3/enumeratum/EnumSpecCompat.scala new file mode 100644 index 00000000..2daa5d72 --- /dev/null +++ b/enumeratum-core/src/test/scala-3/enumeratum/EnumSpecCompat.scala @@ -0,0 +1,3 @@ +package enumeratum + +private[enumeratum] trait EnumSpecCompat { spec: EnumSpec => } diff --git a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala index 793a1c18..df678785 100644 --- a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala +++ b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala @@ -4,18 +4,15 @@ import org.scalatest.OptionValues._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class EnumSpec extends AnyFunSpec with Matchers { +class EnumSpec extends AnyFunSpec with Matchers with EnumSpecCompat { describe("no values") { - it("should result in findValues finding nothing") { EmptyEnum.values.size shouldBe 0 } - } describe("when not wrapped in another object") { - import DummyEnum._ describe("#values") { @@ -40,7 +37,6 @@ class EnumSpec extends AnyFunSpec with Matchers { ) ) } - } describe("#withName") { @@ -474,26 +470,26 @@ class EnumSpec extends AnyFunSpec with Matchers { } it("should return -1 for elements that do not exist") { - // Does this even make sense given that we need to have sealed traits/classes ?? + // Does this even make sense, + // given that we need to have sealed traits/classes ?? sealed trait Reactions extends EnumEntry - case object Reactions extends Enum[Reactions] { + case object Reactions extends Enum[Reactions] { case object Blah extends Reactions - - case object Yay extends Reactions - - case object Nay extends Reactions + case object Yay extends Reactions + case object Nay extends Reactions val values = findValues } + case object Woot extends Reactions + Reactions.indexOf(Woot) shouldBe -1 } } describe("trying to use with improper types") { - it("should fail to compile for unsealed traits") { """ trait NotSealed extends EnumEntry @@ -542,22 +538,13 @@ class EnumSpec extends AnyFunSpec with Matchers { import DummyEnum._ it("should return true if enum value is contained by the parameter list") { - val enum: DummyEnum = Hello - enum.in(Hello, GoodBye) should be(true) + val myEnum: DummyEnum = Hello + myEnum.in(Hello, GoodBye) should be(true) } it("should return false if enum value is not contained by the parameter list") { - val enum: DummyEnum = Hi - enum.in(Hello, GoodBye) should be(false) - } - - it( - "should fail to compile if either enum in the parameter list is not instance of the same enum type as the checked one" - ) { - """ - val enum: DummyEnum = DummyEnum.Hi - enum.in(DummyEnum.Hello, SnakeEnum.ShoutGoodBye) - """ shouldNot compile + val myEnum: DummyEnum = Hi + myEnum.in(Hello, GoodBye) should be(false) } } @@ -573,9 +560,7 @@ class EnumSpec extends AnyFunSpec with Matchers { } it("should fail to compile when passed a singleton object") { - """ - | findEnum(Hello) - """ shouldNot compile + "findEnum(Hello)" shouldNot compile } } diff --git a/enumeratum-core/src/test/scala/enumeratum/Models.scala b/enumeratum-core/src/test/scala/enumeratum/Models.scala index c2c4de6d..c575cd9c 100644 --- a/enumeratum-core/src/test/scala/enumeratum/Models.scala +++ b/enumeratum-core/src/test/scala/enumeratum/Models.scala @@ -11,19 +11,16 @@ object EmptyEnum extends Enum[EmptyEnum] { sealed trait DummyEnum extends EnumEntry object DummyEnum extends Enum[DummyEnum] { - val values = findValues case object Hello extends DummyEnum case object GoodBye extends DummyEnum case object Hi extends DummyEnum - } sealed trait SnakeEnum extends EnumEntry with Snakecase object SnakeEnum extends Enum[SnakeEnum] { - val values = findValues case object Hello extends SnakeEnum @@ -262,7 +259,6 @@ object NestedObjectEnum extends Enum[NestedObjectEnum] { object nested4 { case object NestedAgain extends NestedObjectEnum } - } sealed class MultiEnum(override val entryName: String, val alternateNames: String*) diff --git a/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumHelpers.scala b/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumHelpers.scala index acfc5a14..dbdba40e 100644 --- a/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumHelpers.scala +++ b/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumHelpers.scala @@ -16,10 +16,10 @@ trait ValueEnumHelpers { this: AnyFunSpec with Matchers => */ def testNumericEnum[EntryType <: ValueEnumEntry[ValueType], ValueType <: AnyVal: Numeric]( enumKind: String, - enum: ValueEnum[ValueType, EntryType] + myEnum: ValueEnum[ValueType, EntryType] ): Unit = { val numeric = implicitly[Numeric[ValueType]] - testEnum(enumKind, enum, Seq(numeric.fromInt(Int.MaxValue))) + testEnum(enumKind, myEnum, Seq(numeric.fromInt(Int.MaxValue))) } /* @@ -27,28 +27,28 @@ trait ValueEnumHelpers { this: AnyFunSpec with Matchers => */ def testEnum[EntryType <: ValueEnumEntry[ValueType], ValueType]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], invalidValues: Seq[ValueType] ): Unit = { describe(enumKind) { it("should have more than one value (sanity test)") { - enum.values.size should be > 0 + myEnum.values.size should be > 0 } describe("withValue") { it("should return entries that match the value") { - enum.values.foreach { entry => - enum.withValue(entry.value) shouldBe entry + myEnum.values.foreach { entry => + myEnum.withValue(entry.value) shouldBe entry } } it("should throw on values that don't map to any entries") { invalidValues.foreach { invalid => intercept[NoSuchElementException] { - enum.withValue(invalid) + myEnum.withValue(invalid) } } } @@ -58,14 +58,14 @@ trait ValueEnumHelpers { this: AnyFunSpec with Matchers => describe("withValueOpt") { it("should return Some(entry) that match the value") { - enum.values.foreach { entry => - enum.withValueOpt(entry.value) shouldBe Some(entry) + myEnum.values.foreach { entry => + myEnum.withValueOpt(entry.value) shouldBe Some(entry) } } it("should return None when given values that do not map to any entries") { invalidValues.foreach { invalid => - enum.withValueOpt(invalid) shouldBe None + myEnum.withValueOpt(invalid) shouldBe None } } @@ -74,14 +74,14 @@ trait ValueEnumHelpers { this: AnyFunSpec with Matchers => describe("withValueEither") { it("should return Right(entry) that match the value") { - enum.values.foreach { entry => - enum.withValueEither(entry.value) shouldBe Right(entry) + myEnum.values.foreach { entry => + myEnum.withValueEither(entry.value) shouldBe Right(entry) } } it("should return Left when given values that do not map to any entries") { invalidValues.foreach { invalid => - enum.withValueEither(invalid) shouldBe Left(NoSuchMember(invalid, enum.values)) + myEnum.withValueEither(invalid) shouldBe Left(NoSuchMember(invalid, myEnum.values)) } } @@ -90,26 +90,26 @@ trait ValueEnumHelpers { this: AnyFunSpec with Matchers => describe("in") { it("should return false if given an empty list") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => entry.in(Nil) shouldBe false } } it("should return false if given a list that does not hold the entry") { - enum.values.foreach { entry => - entry.in(enum.values.filterNot(_ == entry)) shouldBe false + myEnum.values.foreach { entry => + entry.in(myEnum.values.filterNot(_ == entry)) shouldBe false } } it("should return true if the list only holds itself") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => entry.in(entry) shouldBe true } } it("should return true if given a list that has the current entry") { - enum.values.foreach { entry => - entry.in(enum.values) shouldBe true + myEnum.values.foreach { entry => + entry.in(myEnum.values) shouldBe true } } } diff --git a/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala b/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala index 39c831c6..764e11c3 100644 --- a/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala +++ b/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala @@ -36,8 +36,11 @@ class ValueEnumSpec extends AnyFunSpec with Matchers with ValueEnumHelpers { it("should work for IntEnums") { def findCompanion[EntryType <: IntEnumEntry: IntEnum](entry: EntryType) = implicitly[IntEnum[EntryType]] + val companion = findCompanion(LibraryItem.Magazine: LibraryItem) + companion shouldBe LibraryItem + IntEnum.materialiseIntValueEnum[LibraryItem] shouldBe LibraryItem companion.values should contain(LibraryItem.Magazine) } diff --git a/macros/compat/src/main/scala-2.13/enumeratum/ContextUtils.scala b/macros/compat/src/main/scala-2.13/enumeratum/ContextUtils.scala index 99e110aa..1fa3703a 100644 --- a/macros/compat/src/main/scala-2.13/enumeratum/ContextUtils.scala +++ b/macros/compat/src/main/scala-2.13/enumeratum/ContextUtils.scala @@ -9,20 +9,17 @@ object ContextUtils { type CTInt = Int type CTChar = Char - /** - * Returns a TermName + /** Returns a TermName */ def termName(c: Context)(name: String): c.universe.TermName = { c.universe.TermName(name) } - /** - * Returns a companion symbol + /** Returns a companion symbol */ def companion(c: Context)(sym: c.Symbol): c.universe.Symbol = sym.companion - /** - * Returns a PartialFunction for turning symbols into names + /** Returns a PartialFunction for turning symbols into names */ def constructorsToParamNamesPF( c: Context @@ -31,15 +28,13 @@ object ContextUtils { m.asMethod.paramLists.flatten.map(_.asTerm.name) } - /** - * Returns the reserved constructor name + /** Returns the reserved constructor name */ def constructorName(c: Context): c.universe.TermName = { c.universe.termNames.CONSTRUCTOR } - /** - * Returns a named arg extractor + /** Returns a named arg extractor */ def namedArg(c: Context) = c.universe.NamedArg } diff --git a/macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala b/macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala new file mode 100644 index 00000000..f03ec571 --- /dev/null +++ b/macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala @@ -0,0 +1,36 @@ +package enumeratum + +object ContextUtils { + // Constant types + type CTLong = Long + type CTInt = Int + type CTChar = Char + + /* TODO: Remove; + + /** Returns a TermName */ + def termName(c: Context)(name: String): c.universe.TermName = { + c.universe.TermName(name) + } + + /** Returns a companion symbol. */ + def companion(c: Context)(sym: c.Symbol): c.universe.Symbol = sym.companion + + /** Returns a PartialFunction for turning symbols into names */ + def constructorsToParamNamesPF( + c: Context + ): PartialFunction[c.universe.Symbol, List[c.universe.Name]] = { + case m if m.isConstructor => + m.asMethod.paramLists.flatten.map(_.asTerm.name) + } + + /** Returns the reserved constructor name */ + def constructorName(c: Context): c.universe.TermName = { + c.universe.termNames.CONSTRUCTOR + } + + /** Returns a named arg extractor */ + def namedArg(c: Context) = c.universe.NamedArg + + */ +} diff --git a/macros/src/main/scala/enumeratum/EnumMacros.scala b/macros/src/main/scala-2/enumeratum/EnumMacros.scala similarity index 98% rename from macros/src/main/scala/enumeratum/EnumMacros.scala rename to macros/src/main/scala-2/enumeratum/EnumMacros.scala index 8908f7d8..022e5fbf 100644 --- a/macros/src/main/scala/enumeratum/EnumMacros.scala +++ b/macros/src/main/scala-2/enumeratum/EnumMacros.scala @@ -20,10 +20,11 @@ object EnumMacros { /** Given an A, provides its companion */ - def materializeEnumImpl[A: c.WeakTypeTag](c: Context) = { + def materializeEnumImpl[A: c.WeakTypeTag](c: Context): c.Expr[A] = { import c.universe._ val symbol = weakTypeOf[A].typeSymbol val companionSymbol = ContextUtils.companion(c)(symbol) + if (companionSymbol == NoSymbol) { c.abort( c.enclosingPosition, diff --git a/macros/src/main/scala/enumeratum/ValueEnumMacros.scala b/macros/src/main/scala-2/enumeratum/ValueEnumMacros.scala similarity index 100% rename from macros/src/main/scala/enumeratum/ValueEnumMacros.scala rename to macros/src/main/scala-2/enumeratum/ValueEnumMacros.scala diff --git a/macros/src/main/scala-3/enumeratum/EnumMacros.scala b/macros/src/main/scala-3/enumeratum/EnumMacros.scala new file mode 100644 index 00000000..5e677a01 --- /dev/null +++ b/macros/src/main/scala-3/enumeratum/EnumMacros.scala @@ -0,0 +1,207 @@ +package enumeratum + +import scala.quoted.{Expr, Quotes, Type} + +object EnumMacros: + /** Finds any [A] in the current scope and returns an expression for a list of them. */ + def findValuesImpl[A](using tpe: Type[A], q: Quotes): Expr[IndexedSeq[A]] = { + import q.reflect.* + + val repr = validateType[A] + val subclasses = enclosedSubClasses[A](q)(repr) + + buildSeqExpr[A](q)(subclasses) + } + + /** Given an A, provides its companion. */ + def materializeEnumImpl[A, M: Type](using tpe: Type[A], q: Quotes): Expr[M] = { + import q.reflect.* + + val repr = TypeRepr.of[A](using tpe) + + if (!repr.typeSymbol.companionModule.exists) { + report.errorAndAbort( + s""" + | + | Could not find the companion object for type ${repr.typeSymbol}. + | + | If you're sure the companion object exists, you might be able to fix this error by annotating the + | value you're trying to find the companion object for with a parent type (e.g. Light.Red: Light). + | + | This error usually happens when trying to find the companion object of a hard-coded enum member, and + | is caused by Scala inferring the type to be the member's singleton type (e.g. Light.Red.type instead of + | Light). + | + | To illustrate, given an enum: + | + | sealed abstract class Light extends EnumEntry + | case object Light extends Enum[Light] { + | val values = findValues + | case object Red extends Light + | case object Blue extends Light + | case object Green extends Light + | } + | + | and a method: + | + | def indexOf[A <: EnumEntry: Enum](entry: A): Int = implicitly[Enum[A]].indexOf(entry) + | + | Instead of calling like so: indexOf(Light.Red) + | Call like so: indexOf(Light.Red: Light) + """.stripMargin + ) + } else { + Ref(repr.typeSymbol.companionModule).asExprOf[M] + } + } + + /** Makes sure that we can work with the given type as an enum: + * + * Aborts if the type is not sealed. + */ + private[enumeratum] def validateType[T](using q: Quotes, tpe: Type[T]): q.reflect.TypeRepr = { + import q.reflect.* + + val repr = TypeRepr.of[T](using tpe) + + if (!repr.classSymbol.exists(_.flags is Flags.Sealed)) { + report.errorAndAbort( + "You can only use findValues on sealed traits or classes" + ) + } + + repr + } + + /** Returns a sequence of symbols for objects that implement the given type + * + * @tparam the + * `Enum` type + * @param tpr + * the representation of type `T` (also specified by `tpe`) + */ + private[enumeratum] def enclosedSubClasses[T](q: Quotes)( + tpr: q.reflect.TypeRepr + )(using tpe: Type[T]): List[q.reflect.TypeRepr] = { + import q.reflect.* + + given quotes: q.type = q + + @annotation.tailrec + def isObject(sym: Symbol, ok: Boolean = false): Boolean = { + if (!sym.flags.is(Flags.Module)) { + val owner = sym.maybeOwner + + if ( + owner == defn.RootClass || + owner.flags.is(Flags.Package) || + owner.isAnonymousFunction + ) { + report.errorAndAbort( + // Root must be a module to have same behaviour + "The enum (i.e. the class containing the case objects and the call to `findValues`) must be an object" + ) + } + + false + } else { + val owner = sym.maybeOwner + + if (!owner.exists || owner == defn.RootClass || owner.isTerm) { + if ( + sym.flags.is(Flags.Module) && !sym.flags.is(Flags.Package) && + !sym.fullName.startsWith(tpr.typeSymbol.companionModule.fullName) + ) { + // See EnumSpec#'should return -1 for elements that do not exist' + + report.warning( + s"The entry '${sym.fullName}' must be defined in the enum companion" + ) + + false + } else { + ok + } + } else { + isObject(sym.maybeOwner, true) + } + } + } + + type IsEntry[E <: T] = E + + @annotation.tailrec + def subclasses( + children: List[Tree], + out: List[TypeRepr] + ): List[TypeRepr] = { + val childTpr = children.headOption.collect { + case tpd: Typed => + tpd.tpt.tpe + + case vd: ValDef => + vd.tpt.tpe + + case cd: ClassDef => + cd.constructor.returnTpt.tpe + + } + + childTpr match { + case Some(child) => { + child.asType match { + case '[IsEntry[t]] => { + val tpeSym = child.typeSymbol + // TODO: Check is subtype (same in Scala2?) + + if (!isObject(tpeSym)) { + subclasses(children.tail, out) + } else { + subclasses(children.tail, child :: out) + } + } + + case _ => + subclasses(children.tail, out) + } + } + + case _ => + out.reverse + } + } + + tpr.classSymbol + .flatMap { cls => + val types = subclasses(cls.children.map(_.tree), Nil) + + if (types.isEmpty) None else Some(types) + } + .getOrElse(List.empty[TypeRepr]) + } + + /** Builds and returns an expression for an `IndexedSeq`, containing singletons for the specified + * subclasses. + */ + private[enumeratum] def buildSeqExpr[T](q: Quotes)( + subclasses: Seq[q.reflect.TypeRepr] + )(using tpe: Type[T]): Expr[IndexedSeq[T]] = { + given quotes: q.type = q + import q.reflect.* + + if (subclasses.isEmpty) { + '{ IndexedSeq.empty[T] } + } else { + val valueExprs = subclasses.map { sub => + Ref(sub.typeSymbol.companionModule).asExprOf[T] + } + + val values = Expr.ofSeq(valueExprs) + + '{ + IndexedSeq[T](${ values }: _*) + } + } + } + +end EnumMacros diff --git a/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala b/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala new file mode 100644 index 00000000..422c757e --- /dev/null +++ b/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala @@ -0,0 +1,291 @@ +package enumeratum + +import scala.reflect.ClassTag + +import scala.quoted.{Expr, Quotes, Type} + +import enumeratum.values.AllowAlias + +@SuppressWarnings(Array("org.wartremover.warts.StringPlusAny")) +object ValueEnumMacros { + + /** Finds ValueEntryType-typed objects in scope that have literal value:Int implementations + * + * Note, requires the ValueEntryType to have a 'value' member that has a literal value + */ + def findIntValueEntriesImpl[ValueEntryType: Type](using + Quotes + ): Expr[IndexedSeq[ValueEntryType]] = + findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Int](identity) + + /** Finds ValueEntryType-typed objects in scope that have literal value:Long implementations + * + * Note, requires the ValueEntryType to have a 'value' member that has a literal value + */ + def findLongValueEntriesImpl[ValueEntryType: Type](using + Quotes + ): Expr[IndexedSeq[ValueEntryType]] = + findValueEntriesImpl[ValueEntryType, ContextUtils.CTLong, Long](identity) + + /** Finds ValueEntryType-typed objects in scope that have literal value:Short implementations + * + * Note + * + * - requires the ValueEntryType to have a 'value' member that has a literal value + * - the Short value should be a literal Int (do no need to cast .toShort). + */ + def findShortValueEntriesImpl[ValueEntryType: Type](using + Quotes + ): Expr[IndexedSeq[ValueEntryType]] = + findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Short]( + _.toShort + ) // do a transform because there is no such thing as Short literals + + /** Finds ValueEntryType-typed objects in scope that have literal value:String implementations + * + * Note + * + * - requires the ValueEntryType to have a 'value' member that has a literal value + */ + def findStringValueEntriesImpl[ValueEntryType: Type](using + Quotes + ): Expr[IndexedSeq[ValueEntryType]] = + findValueEntriesImpl[ValueEntryType, String, String](identity) + + /** Finds ValueEntryType-typed objects in scope that have literal value:Byte implementations + * + * Note + * + * - requires the ValueEntryType to have a 'value' member that has a literal value + */ + def findByteValueEntriesImpl[ValueEntryType: Type](using + Quotes + ): Expr[IndexedSeq[ValueEntryType]] = + findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Byte](_.toByte) + + /** Finds ValueEntryType-typed objects in scope that have literal value:Char implementations + * + * Note + * + * - requires the ValueEntryType to have a 'value' member that has a literal value + */ + def findCharValueEntriesImpl[ValueEntryType: Type](using + Quotes + ): Expr[IndexedSeq[ValueEntryType]] = + findValueEntriesImpl[ValueEntryType, ContextUtils.CTChar, Char](identity) + + /** The method that does the heavy lifting. + */ + private[this] def findValueEntriesImpl[ + ValueEntryType, + ValueType: ClassTag, + ProcessedValue + ]( + processFoundValues: ValueType => ProcessedValue + )(using tpe: Type[ValueEntryType], q: Quotes): Expr[IndexedSeq[ValueEntryType]] = { + import q.reflect.* + + val repr = TypeRepr.of[ValueEntryType](using tpe) + val typeSymbol = repr.typeSymbol + + /* + EnumMacros.validateType(c)(typeSymbol) + // Find the trees in the enclosing object that match the given ValueEntryType + val subclassTrees = EnumMacros.enclosedSubClassTrees(c)(typeSymbol) + // Find the parameters for the constructors of ValueEntryType + val valueEntryTypeConstructorsParams = + findConstructorParamsLists[ValueEntryType](c) + // Identify the value:ValueType implementations for each of the trees we found and process them if required + val treeWithVals = findValuesForSubclassTrees[ValueType, ProcessedValue](c)( + valueEntryTypeConstructorsParams, + subclassTrees, + processFoundValues + ) + + if (weakTypeOf[ValueEntryType] <:< c.typeOf[AllowAlias]) { + // Skip the uniqueness check + } else { + // Make sure the processed found value implementations are unique + ensureUnique[ProcessedValue](c)(treeWithVals) + } + + // Finish by building our Sequence + val subclassSymbols = treeWithVals.map(_.tree.symbol) + EnumMacros.buildSeqExpr[ValueEntryType](c)(subclassSymbols) + */ + + '{ + IndexedSeq.empty[ValueEntryType] // TODO + } + } + + /* + /** Returns a list of TreeWithVal (tree with value of type ProcessedValueType) for the given trees + * and transformation + * + * Will abort compilation if not all the trees provided have a literal value member/constructor + * argument + */ + private[this] def findValuesForSubclassTrees[ValueType: ClassTag, ProcessedValueType](using Quotes)( + valueEntryCTorsParams: List[List[c.universe.Name]], + memberTrees: Seq[c.universe.ModuleDef], + processFoundValues: ValueType => ProcessedValueType + ): Seq[TreeWithVal[c.universe.ModuleDef, ProcessedValueType]] = { + val treeWithValues = toTreeWithMaybeVals[ValueType, ProcessedValueType](c)( + valueEntryCTorsParams, + memberTrees, + processFoundValues + ) + val (hasValueMember, lacksValueMember) = + treeWithValues.partition(_.maybeValue.isDefined) + if (lacksValueMember.nonEmpty) { + val classTag = implicitly[ClassTag[ValueType]] + val lacksValueMemberStr = + lacksValueMember.map(_.tree.symbol).mkString(", ") + c.abort( + c.enclosingPosition, + s""" + |It looks like not all of the members have a literal/constant 'value:${classTag.runtimeClass.getSimpleName}' declaration, namely: $lacksValueMemberStr. + | + |This can happen if: + | + |- The aforementioned members have their `value` supplied by a variable, or otherwise defined as a method + | + |If none of the above apply to your case, it's likely you have discovered an issue with Enumeratum, so please file an issue :) + """.stripMargin + ) + } + hasValueMember.collect { case TreeWithMaybeVal(tree, Some(v)) => + TreeWithVal(tree, v) + } + } + + /** Looks through the given trees and tries to find the proper value declaration/constructor + * argument. + * + * Aborts compilation if the value declaration/constructor is of the wrong type, + */ + private[this] def toTreeWithMaybeVals[ValueType: ClassTag, ProcessedValueType](c: Context)( + valueEntryCTorsParams: List[List[c.universe.Name]], + memberTrees: Seq[c.universe.ModuleDef], + processFoundValues: ValueType => ProcessedValueType + ): Seq[TreeWithMaybeVal[c.universe.ModuleDef, ProcessedValueType]] = { + import c.universe._ + val classTag = implicitly[ClassTag[ValueType]] + val valueTerm = ContextUtils.termName(c)("value") + // go through all the trees + memberTrees.map { declTree => + val directMemberTrees = + declTree.children.flatMap(_.children) // Things that are body-level, no lower + val constructorTrees = { + val immediate = directMemberTrees // for 2.11+ this is enough + val constructorName = ContextUtils.constructorName(c) + // Sadly 2.10 has parent-class constructor calls nested inside a member.. + val method = + directMemberTrees.collect { // for 2.10.x, we need to grab the body-level constructor method's trees + case t @ DefDef(_, `constructorName`, _, _, _, _) => + t.collect { case t => t } + }.flatten + immediate ++ method + }.iterator + + val valuesFromMembers = directMemberTrees.iterator.collect { + case ValDef(_, termName, _, Literal(Constant(i: ValueType))) if termName == valueTerm => + Some(i) + } + + val NamedArg = ContextUtils.namedArg(c) + + // Sadly 2.10 has parent-class constructor calls nested inside a member.. + val valuesFromConstructors = constructorTrees.collect { + // The tree has a method call + case Apply(_, args) => { + val valueArguments: List[Option[ValueType]] = + valueEntryCTorsParams.collect { + // Find non-empty constructor param lists + case paramTermNames if paramTermNames.nonEmpty => { + val paramsWithArg = paramTermNames.zip(args) + paramsWithArg.collectFirst { + // found a (paramName, argument) parameter-argument pair where paramName is "value", and argument is a constant with the right type + case (`valueTerm`, Literal(Constant(i: ValueType))) => i + // found a (paramName, argument) parameter-argument pair where paramName is "value", and argument is a constant with the wrong type + case (`valueTerm`, Literal(Constant(i))) => + c.abort( + c.enclosingPosition, + s"${declTree.symbol} has a value with the wrong type: $i:${i.getClass}, instead of ${classTag.runtimeClass}." + ) + /* + * found a (_, NamedArgument(argName, argument)) parameter-named pair where the argument is named "value" and the argument itself is of the right type + */ + case (_, NamedArg(Ident(`valueTerm`), Literal(Constant(i: ValueType)))) => + i + /* + * found a (_, NamedArgument(argName, argument)) parameter-named pair where the argument is named "value" and the argument itself is of the wrong type + */ + case (_, NamedArg(Ident(`valueTerm`), Literal(Constant(i)))) => + c.abort( + c.enclosingPosition, + s"${declTree.symbol} has a value with the wrong type: $i:${i.getClass}, instead of ${classTag.runtimeClass}" + ) + } + } + } + // We only want the first such constructor argument + valueArguments.collectFirst { case Some(v) => v } + } + } + + val values = valuesFromMembers ++ valuesFromConstructors + val processedValue = values.collectFirst { case Some(v) => + processFoundValues(v) + } + TreeWithMaybeVal(declTree, processedValue) + } + } + + /** Given a type, finds the constructor params lists for it + */ + private[this] def findConstructorParamsLists[ValueEntryType: Type](using Quotes): List[List[c.universe.Name]] = { + val valueEntryTypeTpe = implicitly[Type[ValueEntryType]].tpe + val valueEntryTypeTpeMembers = valueEntryTypeTpe.members + valueEntryTypeTpeMembers.collect(ContextUtils.constructorsToParamNamesPF(c)).toList + } + + /** Ensures that we have unique values for trees, aborting otherwise with a message indicating + * which trees have the same symbol + */ + private[this] def ensureUnique[A](c: Context)( + treeWithVals: Seq[TreeWithVal[c.universe.ModuleDef, A]] + ): Unit = { + val membersWithValues = treeWithVals.map { treeWithVal => + treeWithVal.tree.symbol -> treeWithVal.value + } + val groupedByValue = membersWithValues.groupBy(_._2).map { case (k, v) => + (k, v.map(_._1)) + } + val (valuesWithOneSymbol, valuesWithMoreThanOneSymbol) = + groupedByValue.partition(_._2.size <= 1) + if (valuesWithOneSymbol.size != membersWithValues.toMap.keys.size) { + val formattedString = valuesWithMoreThanOneSymbol.toSeq.reverse.foldLeft("") { + case (acc, (k, v)) => + acc ++ s"""$k has members [ ${v.mkString(", ")} ]\n """ + } + c.abort( + c.enclosingPosition, + s""" + | + | It does not look like you have unique values in your ValueEnum. + | Each of the following values correspond to more than one member: + | + | $formattedString + | Please check to make sure members have unique values. + | """.stripMargin + ) + } + } + + // Helper case classes + private[this] case class TreeWithMaybeVal[CTree, T](tree: CTree, maybeValue: Option[T]) + private[this] case class TreeWithVal[CTree, T](tree: CTree, value: T) + */ +} diff --git a/project/build.properties b/project/build.properties index 7a7e80d6..d738b858 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.5.5 +sbt.version = 1.7.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index c63c61e1..1a913c71 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,7 +2,7 @@ resolvers ++= Seq( Classpaths.sbtPluginReleases ) -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") From 201247da6f5ae4d98a44f516acd8c8f394976cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Sun, 4 Sep 2022 22:05:57 +0200 Subject: [PATCH 02/16] ValueEnumMacros --- build.sbt | 9 +- .../main/scala-2/enumeratum/EnumCompat.scala | 10 +- .../enumeratum/values/ValueEnumCompat.scala | 2 +- .../main/scala-3/enumeratum/EnumCompat.scala | 14 + .../src/main/scala/enumeratum/Enum.scala | 8 - .../scala/enumeratum/values/ValueEnum.scala | 2 - .../values/CustomEnumPrivateConstructor.scala | 4 + .../scala/enumeratum/values/LibraryItem.scala | 1 - .../enumeratum/values/NumericPrecision.scala | 13 + .../enumeratum/values/ValueEnumSpec.scala | 24 +- .../scala-3/enumeratum/ContextUtils.scala | 36 -- .../scala-2/enumeratum/ValueEnumMacros.scala | 1 + .../main/scala-3/enumeratum/EnumHelper.scala | 92 ++++ .../main/scala-3/enumeratum/EnumMacros.scala | 9 +- .../src/main/scala-3/enumeratum/Macros.scala | 31 ++ .../scala-3/enumeratum/ValueEnumMacros.scala | 392 ++++++++---------- 16 files changed, 379 insertions(+), 269 deletions(-) create mode 100644 enumeratum-core/src/test/scala/enumeratum/values/NumericPrecision.scala delete mode 100644 macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala create mode 100644 macros/src/main/scala-3/enumeratum/EnumHelper.scala create mode 100644 macros/src/main/scala-3/enumeratum/Macros.scala diff --git a/build.sbt b/build.sbt index 756c9061..ac6548d1 100644 --- a/build.sbt +++ b/build.sbt @@ -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 @@ -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 + } } ) diff --git a/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala b/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala index 1fd7952f..7e6aeb02 100644 --- a/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala +++ b/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala @@ -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 { diff --git a/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala b/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala index 3e4f1f0a..29a4f49d 100644 --- a/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala +++ b/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala @@ -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 { diff --git a/enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala b/enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala index 73aae7a1..ce6e7f04 100644 --- a/enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala +++ b/enumeratum-core/src/main/scala-3/enumeratum/EnumCompat.scala @@ -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 { diff --git a/enumeratum-core/src/main/scala/enumeratum/Enum.scala b/enumeratum-core/src/main/scala/enumeratum/Enum.scala index 15c99715..bdff0861 100644 --- a/enumeratum-core/src/main/scala/enumeratum/Enum.scala +++ b/enumeratum-core/src/main/scala/enumeratum/Enum.scala @@ -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]] * diff --git a/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala b/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala index 210a5afd..0bd5ea68 100644 --- a/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala +++ b/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala @@ -1,7 +1,5 @@ package enumeratum.values -import _root_.enumeratum.Enum - /** Base trait for a Value-based enums. * * Example: diff --git a/enumeratum-core/src/test/scala/enumeratum/values/CustomEnumPrivateConstructor.scala b/enumeratum-core/src/test/scala/enumeratum/values/CustomEnumPrivateConstructor.scala index e1873c0f..f5c4183d 100644 --- a/enumeratum-core/src/test/scala/enumeratum/values/CustomEnumPrivateConstructor.scala +++ b/enumeratum-core/src/test/scala/enumeratum/values/CustomEnumPrivateConstructor.scala @@ -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") diff --git a/enumeratum-core/src/test/scala/enumeratum/values/LibraryItem.scala b/enumeratum-core/src/test/scala/enumeratum/values/LibraryItem.scala index fa11a35f..20325875 100644 --- a/enumeratum-core/src/test/scala/enumeratum/values/LibraryItem.scala +++ b/enumeratum-core/src/test/scala/enumeratum/values/LibraryItem.scala @@ -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") diff --git a/enumeratum-core/src/test/scala/enumeratum/values/NumericPrecision.scala b/enumeratum-core/src/test/scala/enumeratum/values/NumericPrecision.scala new file mode 100644 index 00000000..b1e5d257 --- /dev/null +++ b/enumeratum-core/src/test/scala/enumeratum/values/NumericPrecision.scala @@ -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 +} diff --git a/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala b/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala index 764e11c3..7f7ff968 100644 --- a/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala +++ b/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala @@ -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") { @@ -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") { @@ -141,7 +152,6 @@ class ValueEnumSpec extends AnyFunSpec with Matchers with ValueEnumHelpers { } """ shouldNot compile } - } describe("trying to use with improper types") { @@ -198,7 +208,5 @@ class ValueEnumSpec extends AnyFunSpec with Matchers with ValueEnumHelpers { """ shouldNot compile } } - } - } diff --git a/macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala b/macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala deleted file mode 100644 index f03ec571..00000000 --- a/macros/compat/src/main/scala-3/enumeratum/ContextUtils.scala +++ /dev/null @@ -1,36 +0,0 @@ -package enumeratum - -object ContextUtils { - // Constant types - type CTLong = Long - type CTInt = Int - type CTChar = Char - - /* TODO: Remove; - - /** Returns a TermName */ - def termName(c: Context)(name: String): c.universe.TermName = { - c.universe.TermName(name) - } - - /** Returns a companion symbol. */ - def companion(c: Context)(sym: c.Symbol): c.universe.Symbol = sym.companion - - /** Returns a PartialFunction for turning symbols into names */ - def constructorsToParamNamesPF( - c: Context - ): PartialFunction[c.universe.Symbol, List[c.universe.Name]] = { - case m if m.isConstructor => - m.asMethod.paramLists.flatten.map(_.asTerm.name) - } - - /** Returns the reserved constructor name */ - def constructorName(c: Context): c.universe.TermName = { - c.universe.termNames.CONSTRUCTOR - } - - /** Returns a named arg extractor */ - def namedArg(c: Context) = c.universe.NamedArg - - */ -} diff --git a/macros/src/main/scala-2/enumeratum/ValueEnumMacros.scala b/macros/src/main/scala-2/enumeratum/ValueEnumMacros.scala index 346cbaa8..6eb13bef 100644 --- a/macros/src/main/scala-2/enumeratum/ValueEnumMacros.scala +++ b/macros/src/main/scala-2/enumeratum/ValueEnumMacros.scala @@ -113,6 +113,7 @@ object ValueEnumMacros { // Finish by building our Sequence val subclassSymbols = treeWithVals.map(_.tree.symbol) + EnumMacros.buildSeqExpr[ValueEntryType](c)(subclassSymbols) } diff --git a/macros/src/main/scala-3/enumeratum/EnumHelper.scala b/macros/src/main/scala-3/enumeratum/EnumHelper.scala new file mode 100644 index 00000000..ae004ea2 --- /dev/null +++ b/macros/src/main/scala-3/enumeratum/EnumHelper.scala @@ -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 diff --git a/macros/src/main/scala-3/enumeratum/EnumMacros.scala b/macros/src/main/scala-3/enumeratum/EnumMacros.scala index 5e677a01..91568b1b 100644 --- a/macros/src/main/scala-3/enumeratum/EnumMacros.scala +++ b/macros/src/main/scala-3/enumeratum/EnumMacros.scala @@ -58,6 +58,9 @@ object EnumMacros: /** Makes sure that we can work with the given type as an enum: * * Aborts if the type is not sealed. + * + * @tparam T + * the `Enum` type */ private[enumeratum] def validateType[T](using q: Quotes, tpe: Type[T]): q.reflect.TypeRepr = { import q.reflect.* @@ -75,8 +78,8 @@ object EnumMacros: /** Returns a sequence of symbols for objects that implement the given type * - * @tparam the - * `Enum` type + * @tparam T + * the `Enum` type * @param tpr * the representation of type `T` (also specified by `tpe`) */ @@ -85,6 +88,7 @@ object EnumMacros: )(using tpe: Type[T]): List[q.reflect.TypeRepr] = { import q.reflect.* + // TODO: Use SumOf? given quotes: q.type = q @annotation.tailrec @@ -173,6 +177,7 @@ object EnumMacros: tpr.classSymbol .flatMap { cls => + // TODO: cls.typeMembers val types = subclasses(cls.children.map(_.tree), Nil) if (types.isEmpty) None else Some(types) diff --git a/macros/src/main/scala-3/enumeratum/Macros.scala b/macros/src/main/scala-3/enumeratum/Macros.scala new file mode 100644 index 00000000..44654b1b --- /dev/null +++ b/macros/src/main/scala-3/enumeratum/Macros.scala @@ -0,0 +1,31 @@ +package test + +object Macros { + import scala.quoted.{Expr, Quotes, Type} + + inline def show[A]: String = ${ showImpl[A] } + + private def showImpl[A](using tpe: Type[A], q: Quotes): Expr[String] = { + import q.reflect.* + + val repr = TypeRepr.of[A](using tpe) + + val tpeSym = repr.typeSymbol + /* + > _root_.test.Macros.show[_root_.test.Bar.type] + + val res0: String = @scala.annotation.internal.SourceFile("macros/src/main/scala-3/enumeratum/Foo.scala") object Bar extends test.Foo { this: test.Bar.type => + + } + */ + + // val tpeSym = repr.typeSymbol.companionModule + /* + > _root_.test.Macros.show[_root_.test.Bar.type] + + val res0: String = lazy val Bar: test.Bar.type + */ + + Expr(tpeSym.tree.show) + } +} diff --git a/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala b/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala index 422c757e..e6bc2639 100644 --- a/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala +++ b/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala @@ -2,32 +2,36 @@ package enumeratum import scala.reflect.ClassTag -import scala.quoted.{Expr, Quotes, Type} +import scala.deriving.Mirror +import scala.quoted.{Expr, FromExpr, Quotes, Type} import enumeratum.values.AllowAlias +/** @define valueEntryTypeNote + * Note, requires the ValueEntryType to have a 'value' member that has a literal value. + */ @SuppressWarnings(Array("org.wartremover.warts.StringPlusAny")) object ValueEnumMacros { - /** Finds ValueEntryType-typed objects in scope that have literal value:Int implementations + /** Finds ValueEntryType-typed objects in scope that have literal `value: Int` implementations. * - * Note, requires the ValueEntryType to have a 'value' member that has a literal value + * $valueEntryTypeNote */ def findIntValueEntriesImpl[ValueEntryType: Type](using Quotes ): Expr[IndexedSeq[ValueEntryType]] = - findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Int](identity) + findValueEntriesImpl[ValueEntryType, Int] - /** Finds ValueEntryType-typed objects in scope that have literal value:Long implementations + /** Finds ValueEntryType-typed objects in scope that have literal `value: Long` implementations. * - * Note, requires the ValueEntryType to have a 'value' member that has a literal value + * $valueEntryTypeNote */ def findLongValueEntriesImpl[ValueEntryType: Type](using Quotes ): Expr[IndexedSeq[ValueEntryType]] = - findValueEntriesImpl[ValueEntryType, ContextUtils.CTLong, Long](identity) + findValueEntriesImpl[ValueEntryType, Long] - /** Finds ValueEntryType-typed objects in scope that have literal value:Short implementations + /** Finds ValueEntryType-typed objects in scope that have literal `value: Short` implementations. * * Note * @@ -37,11 +41,9 @@ object ValueEnumMacros { def findShortValueEntriesImpl[ValueEntryType: Type](using Quotes ): Expr[IndexedSeq[ValueEntryType]] = - findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Short]( - _.toShort - ) // do a transform because there is no such thing as Short literals + findValueEntriesImpl[ValueEntryType, Short] - /** Finds ValueEntryType-typed objects in scope that have literal value:String implementations + /** Finds ValueEntryType-typed objects in scope that have literal `value: String` implementations. * * Note * @@ -50,9 +52,9 @@ object ValueEnumMacros { def findStringValueEntriesImpl[ValueEntryType: Type](using Quotes ): Expr[IndexedSeq[ValueEntryType]] = - findValueEntriesImpl[ValueEntryType, String, String](identity) + findValueEntriesImpl[ValueEntryType, String] - /** Finds ValueEntryType-typed objects in scope that have literal value:Byte implementations + /** Finds ValueEntryType-typed objects in scope that have literal `value: Byte` implementations. * * Note * @@ -61,9 +63,9 @@ object ValueEnumMacros { def findByteValueEntriesImpl[ValueEntryType: Type](using Quotes ): Expr[IndexedSeq[ValueEntryType]] = - findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Byte](_.toByte) + findValueEntriesImpl[ValueEntryType, Byte] - /** Finds ValueEntryType-typed objects in scope that have literal value:Char implementations + /** Finds ValueEntryType-typed objects in scope that have literal `value: Char` implementations. * * Note * @@ -72,220 +74,192 @@ object ValueEnumMacros { def findCharValueEntriesImpl[ValueEntryType: Type](using Quotes ): Expr[IndexedSeq[ValueEntryType]] = - findValueEntriesImpl[ValueEntryType, ContextUtils.CTChar, Char](identity) + findValueEntriesImpl[ValueEntryType, Char] + + private given ValueOfFromExpr[T <: Singleton](using Type[T]): FromExpr[ValueOf[T]] with { + def unapply(x: Expr[ValueOf[T]])(using q: Quotes): Option[ValueOf[T]] = { + import q.reflect.* + + x match { + case '{ new ValueOf[T]($v) } => + v.asTerm match { + case id: Ident => { + val cls = Class.forName(id.symbol.fullName + '$') + val moduleField = cls.getFields.find(_.getName == f"MODULE$$") + + moduleField.map { field => + new ValueOf(field.get(null).asInstanceOf[T]) + } + } + + case _ => + None + } + + case _ => + None + } + } + } /** The method that does the heavy lifting. */ - private[this] def findValueEntriesImpl[ - ValueEntryType, - ValueType: ClassTag, - ProcessedValue - ]( - processFoundValues: ValueType => ProcessedValue - )(using tpe: Type[ValueEntryType], q: Quotes): Expr[IndexedSeq[ValueEntryType]] = { - import q.reflect.* + private def findValueEntriesImpl[A, ValueType](using + q: Quotes, + tpe: Type[A], + valueTpe: Type[ValueType] + )(using cls: ClassTag[ValueType]): Expr[IndexedSeq[A]] = { + type TakeHead[Head <: A & Singleton, Tail <: Tuple] = Head *: Tail - val repr = TypeRepr.of[ValueEntryType](using tpe) - val typeSymbol = repr.typeSymbol - - /* - EnumMacros.validateType(c)(typeSymbol) - // Find the trees in the enclosing object that match the given ValueEntryType - val subclassTrees = EnumMacros.enclosedSubClassTrees(c)(typeSymbol) - // Find the parameters for the constructors of ValueEntryType - val valueEntryTypeConstructorsParams = - findConstructorParamsLists[ValueEntryType](c) - // Identify the value:ValueType implementations for each of the trees we found and process them if required - val treeWithVals = findValuesForSubclassTrees[ValueType, ProcessedValue](c)( - valueEntryTypeConstructorsParams, - subclassTrees, - processFoundValues - ) - - if (weakTypeOf[ValueEntryType] <:< c.typeOf[AllowAlias]) { - // Skip the uniqueness check - } else { - // Make sure the processed found value implementations are unique - ensureUnique[ProcessedValue](c)(treeWithVals) + type SumOf[X <: A, T <: Tuple] = Mirror.SumOf[X] { + type MirroredElemTypes = T } - // Finish by building our Sequence - val subclassSymbols = treeWithVals.map(_.tree.symbol) - EnumMacros.buildSeqExpr[ValueEntryType](c)(subclassSymbols) - */ + import q.reflect.* - '{ - IndexedSeq.empty[ValueEntryType] // TODO - } - } + val ctx = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + + val yRetainTrees = ctx.settings.YretainTrees.valueIn(ctx.settingsState) + + if (!yRetainTrees) { + report.errorAndAbort("""Option -Yretain-trees must be set in scalacOptions. +In SBT settings: - /* - /** Returns a list of TreeWithVal (tree with value of type ProcessedValueType) for the given trees - * and transformation - * - * Will abort compilation if not all the trees provided have a literal value member/constructor - * argument - */ - private[this] def findValuesForSubclassTrees[ValueType: ClassTag, ProcessedValueType](using Quotes)( - valueEntryCTorsParams: List[List[c.universe.Name]], - memberTrees: Seq[c.universe.ModuleDef], - processFoundValues: ValueType => ProcessedValueType - ): Seq[TreeWithVal[c.universe.ModuleDef, ProcessedValueType]] = { - val treeWithValues = toTreeWithMaybeVals[ValueType, ProcessedValueType](c)( - valueEntryCTorsParams, - memberTrees, - processFoundValues - ) - val (hasValueMember, lacksValueMember) = - treeWithValues.partition(_.maybeValue.isDefined) - if (lacksValueMember.nonEmpty) { - val classTag = implicitly[ClassTag[ValueType]] - val lacksValueMemberStr = - lacksValueMember.map(_.tree.symbol).mkString(", ") - c.abort( - c.enclosingPosition, - s""" - |It looks like not all of the members have a literal/constant 'value:${classTag.runtimeClass.getSimpleName}' declaration, namely: $lacksValueMemberStr. - | - |This can happen if: - | - |- The aforementioned members have their `value` supplied by a variable, or otherwise defined as a method - | - |If none of the above apply to your case, it's likely you have discovered an issue with Enumeratum, so please file an issue :) - """.stripMargin - ) + scalacOptions += "-Yretain-trees" +""") } - hasValueMember.collect { case TreeWithMaybeVal(tree, Some(v)) => - TreeWithVal(tree, v) + + val repr = TypeRepr.of[A](using tpe) + val tpeSym = repr.typeSymbol + + val valueRepr = TypeRepr.of[ValueType] + + val ctorParams = tpeSym.primaryConstructor.paramSymss.flatten + + val enumFields = repr.typeSymbol.fieldMembers.flatMap { field => + ctorParams.zipWithIndex.find { case (p, i) => + p.name == field.name && (p.tree match { + case term: Term => + term.tpe <:< valueRepr + + case _ => + false + }) + } + }.toSeq + + val (valueField, valueParamIndex): (Symbol, Int) = { + if (enumFields.size == 1) { + enumFields.headOption + } else { + enumFields.find(_._1.name == "value") + } + }.getOrElse { + Symbol.newVal(tpeSym, "value", valueRepr, Flags.Abstract, Symbol.noSymbol) -> 0 } - } - /** Looks through the given trees and tries to find the proper value declaration/constructor - * argument. - * - * Aborts compilation if the value declaration/constructor is of the wrong type, - */ - private[this] def toTreeWithMaybeVals[ValueType: ClassTag, ProcessedValueType](c: Context)( - valueEntryCTorsParams: List[List[c.universe.Name]], - memberTrees: Seq[c.universe.ModuleDef], - processFoundValues: ValueType => ProcessedValueType - ): Seq[TreeWithMaybeVal[c.universe.ModuleDef, ProcessedValueType]] = { - import c.universe._ - val classTag = implicitly[ClassTag[ValueType]] - val valueTerm = ContextUtils.termName(c)("value") - // go through all the trees - memberTrees.map { declTree => - val directMemberTrees = - declTree.children.flatMap(_.children) // Things that are body-level, no lower - val constructorTrees = { - val immediate = directMemberTrees // for 2.11+ this is enough - val constructorName = ContextUtils.constructorName(c) - // Sadly 2.10 has parent-class constructor calls nested inside a member.. - val method = - directMemberTrees.collect { // for 2.10.x, we need to grab the body-level constructor method's trees - case t @ DefDef(_, `constructorName`, _, _, _, _) => - t.collect { case t => t } - }.flatten - immediate ++ method - }.iterator - - val valuesFromMembers = directMemberTrees.iterator.collect { - case ValDef(_, termName, _, Literal(Constant(i: ValueType))) if termName == valueTerm => - Some(i) + type IsValue[T <: ValueType] = T + + object ConstVal { + @annotation.tailrec + def unapply(tree: Tree): Option[Constant] = tree match { + case NamedArg(nme, v) if (nme == valueField.name) => + unapply(v) + + case ValDef(nme, _, Some(v)) if (nme == valueField.name) => + unapply(v) + + case lit @ Literal(const) if (lit.tpe <:< valueRepr) => + Some(const) + + case _ => + None } + } + + @annotation.tailrec + def collect[T <: Tuple]( + instances: List[Expr[A]], + values: Map[TypeRepr, ValueType] + )(using tupleTpe: Type[T]): Either[String, Expr[List[A]]] = + tupleTpe match { + case '[TakeHead[h, tail]] => { + val htpr = TypeRepr.of[h] + + (for { + vof <- Expr.summon[ValueOf[h]] + constValue <- htpr.typeSymbol.tree match { + case ClassDef(_, _, spr, _, rhs) => { + val fromCtor = spr + .collectFirst { + case Apply(Select(New(id), _), args) if id.tpe <:< repr => + args + } + .flatMap(_.lift(valueParamIndex).collect { case ConstVal(const) => + const + }) + + fromCtor + .orElse(rhs.collectFirst { case ConstVal(v) => v }) + .flatMap { const => + cls.unapply(const.value) + } - val NamedArg = ContextUtils.namedArg(c) - - // Sadly 2.10 has parent-class constructor calls nested inside a member.. - val valuesFromConstructors = constructorTrees.collect { - // The tree has a method call - case Apply(_, args) => { - val valueArguments: List[Option[ValueType]] = - valueEntryCTorsParams.collect { - // Find non-empty constructor param lists - case paramTermNames if paramTermNames.nonEmpty => { - val paramsWithArg = paramTermNames.zip(args) - paramsWithArg.collectFirst { - // found a (paramName, argument) parameter-argument pair where paramName is "value", and argument is a constant with the right type - case (`valueTerm`, Literal(Constant(i: ValueType))) => i - // found a (paramName, argument) parameter-argument pair where paramName is "value", and argument is a constant with the wrong type - case (`valueTerm`, Literal(Constant(i))) => - c.abort( - c.enclosingPosition, - s"${declTree.symbol} has a value with the wrong type: $i:${i.getClass}, instead of ${classTag.runtimeClass}." - ) - /* - * found a (_, NamedArgument(argName, argument)) parameter-named pair where the argument is named "value" and the argument itself is of the right type - */ - case (_, NamedArg(Ident(`valueTerm`), Literal(Constant(i: ValueType)))) => - i - /* - * found a (_, NamedArgument(argName, argument)) parameter-named pair where the argument is named "value" and the argument itself is of the wrong type - */ - case (_, NamedArg(Ident(`valueTerm`), Literal(Constant(i)))) => - c.abort( - c.enclosingPosition, - s"${declTree.symbol} has a value with the wrong type: $i:${i.getClass}, instead of ${classTag.runtimeClass}" - ) - } } + + case _ => + Option.empty[ValueType] } - // We only want the first such constructor argument - valueArguments.collectFirst { case Some(v) => v } + } yield Tuple3(TypeRepr.of[h], '{ ${ vof }.value: A }, constValue)) match { + case Some((tpr, instance, value)) => + collect[tail](instance :: instances, values + (tpr -> value)) + + case None => + report.errorAndAbort( + s"Fails to check value entry ${htpr.show} for enum ${repr.show}" + ) + } } - } - val values = valuesFromMembers ++ valuesFromConstructors - val processedValue = values.collectFirst { case Some(v) => - processFoundValues(v) + case '[EmptyTuple] => { + val allowAlias = repr <:< TypeRepr.of[AllowAlias] + + if (!allowAlias && values.values.toSet.size < values.size) { + val details = values + .map { case (sub, value) => + s"${sub.show} = $value" + } + .mkString(", ") + + Left(s"Values for ${valueField.name} are not discriminated subtypes: ${details}") + } else { + Right(Expr ofList instances.reverse) + } + } } - TreeWithMaybeVal(declTree, processedValue) - } - } - /** Given a type, finds the constructor params lists for it - */ - private[this] def findConstructorParamsLists[ValueEntryType: Type](using Quotes): List[List[c.universe.Name]] = { - val valueEntryTypeTpe = implicitly[Type[ValueEntryType]].tpe - val valueEntryTypeTpeMembers = valueEntryTypeTpe.members - valueEntryTypeTpeMembers.collect(ContextUtils.constructorsToParamNamesPF(c)).toList - } + val result: Either[String, Expr[List[A]]] = + Expr.summon[Mirror.SumOf[A]] match { + case Some(sum) => + sum.asTerm.tpe.asType match { + case '[SumOf[A, t]] => + collect[t](List.empty, Map.empty) - /** Ensures that we have unique values for trees, aborting otherwise with a message indicating - * which trees have the same symbol - */ - private[this] def ensureUnique[A](c: Context)( - treeWithVals: Seq[TreeWithVal[c.universe.ModuleDef, A]] - ): Unit = { - val membersWithValues = treeWithVals.map { treeWithVal => - treeWithVal.tree.symbol -> treeWithVal.value - } - val groupedByValue = membersWithValues.groupBy(_._2).map { case (k, v) => - (k, v.map(_._1)) - } - val (valuesWithOneSymbol, valuesWithMoreThanOneSymbol) = - groupedByValue.partition(_._2.size <= 1) - if (valuesWithOneSymbol.size != membersWithValues.toMap.keys.size) { - val formattedString = valuesWithMoreThanOneSymbol.toSeq.reverse.foldLeft("") { - case (acc, (k, v)) => - acc ++ s"""$k has members [ ${v.mkString(", ")} ]\n """ + case _ => + Left(s"Invalid `Mirror.SumOf[${repr.show}]`") + + } + + case None => + Left(s"Missing `Mirror.SumOf[${repr.show}]`") } - c.abort( - c.enclosingPosition, - s""" - | - | It does not look like you have unique values in your ValueEnum. - | Each of the following values correspond to more than one member: - | - | $formattedString - | Please check to make sure members have unique values. - | """.stripMargin - ) + + result match { + case Left(errorMsg) => + report.errorAndAbort(errorMsg) + + case Right(instances) => + '{ IndexedSeq.empty ++ $instances } } } - - // Helper case classes - private[this] case class TreeWithMaybeVal[CTree, T](tree: CTree, maybeValue: Option[T]) - private[this] case class TreeWithVal[CTree, T](tree: CTree, value: T) - */ } From d7d07ac621453893a4b16285e4c2d98ccee8cfb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Mon, 5 Sep 2022 17:57:00 +0200 Subject: [PATCH 03/16] Update coreJVMTests --- .github/workflows/ci.yml | 24 +- build.sbt | 320 +++++++++++------- .../enumeratum/values/ArgonautValueEnum.scala | 2 +- .../test/scala/enumeratum/EnumJVMSpec.scala | 38 +-- .../src/test/scala/enumeratum/Eval.scala | 25 -- .../enumeratum/values/ValueEnumJVMSpec.scala | 93 +---- .../main/scala-3/enumeratum/EnumHelper.scala | 92 ----- .../src/main/scala-3/enumeratum/Macros.scala | 31 -- project/CoreJVMTest.scala | 242 +++++++++++++ 9 files changed, 472 insertions(+), 395 deletions(-) delete mode 100644 enumeratum-core-jvm-tests/src/test/scala/enumeratum/Eval.scala delete mode 100644 macros/src/main/scala-3/enumeratum/EnumHelper.scala delete mode 100644 macros/src/main/scala-3/enumeratum/Macros.scala create mode 100644 project/CoreJVMTest.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e59be19..b9c8781e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,15 +13,18 @@ jobs: - java: 11 scala: 2.11.12 - java: 11 - scala: 2.12.14 + scala: 2.12.16 - java: 11 - scala: 2.13.6 + scala: 2.13.8 + - java: 11 + scala: 3.2.1-RC1 runs-on: ubuntu-latest env: SCALAJS_TEST_OPT: full # define Java options for both official sbt and sbt-extras JAVA_OPTS: -Xms6G -Xmx6G -Xss4M -XX:ReservedCodeCacheSize=256M -XX:MaxMetaspaceSize=1G -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Dfile.encoding=UTF-8 JVM_OPTS: -Xms6G -Xmx6G -Xss4M -XX:ReservedCodeCacheSize=256M -XX:MaxMetaspaceSize=1G -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Dfile.encoding=UTF-8 + SBT_OPTS: -Denumeratum.useLocalVersion steps: - name: Checkout uses: actions/checkout@v1 @@ -39,26 +42,23 @@ jobs: sbt -v ++${{ matrix.scala }} scalafmtCheck scalafmtSbtCheck scala_2_11/test:compile scala_2_11/test:doc sbt -v ++${{ matrix.scala }} scala_2_11/test ;; - 2.12.14) + 2.12.16) sbt -v ++${{ matrix.scala }} test:compile test:doc sbt -v ++${{ matrix.scala }} coverage test coverageReport sbt -v ++${{ matrix.scala }} coverageAggregate ;; - 2.13.6) + *) sbt -v ++${{ matrix.scala }} test:compile test:doc sbt -v ++${{ matrix.scala }} test ;; - *) - echo unknown Scala Version ${{ matrix.scala }} - exit 1 esac rm -rf "$HOME/.ivy2/local" || true - find $HOME/Library/Caches/Coursier/v1 -name "ivydata-*.properties" -delete || true - find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true - find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true - find $HOME/.sbt -name "*.lock" -delete || true + find $HOME/Library/Caches/Coursier/v1 -name "ivydata-*.properties" -delete || true + find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true + find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true + find $HOME/.sbt -name "*.lock" -delete || true - name: Upload coverage to Codecov - if: ${{ matrix.scala == '2.12.14' }} + if: ${{ matrix.scala == '2.12.16' }} uses: codecov/codecov-action@v2 with: fail_ci_if_error: true diff --git a/build.sbt b/build.sbt index ac6548d1..484e4223 100644 --- a/build.sbt +++ b/build.sbt @@ -5,22 +5,23 @@ 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" -lazy val scalaVersionsAll = Seq(scala_2_11Version, scala_2_12Version, scala_2_13Version, scala_3Version) +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 lazy val scalaTestVersion = "3.2.9" // Library versions -lazy val reactiveMongoVersion = "1.0.0-RC6" +lazy val reactiveMongoVersion = "1.1.0-RC6" lazy val json4sVersion = "4.0.3" lazy val quillVersion = "4.1.0" def theDoobieVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor <= 11 => "0.7.1" - case Some(_) => "1.0.0-RC2" + case Some(_) => "1.0.0-RC2" case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Doobie") } @@ -28,7 +29,7 @@ def theDoobieVersion(scalaVersion: String) = def theArgonautVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor >= 11 => "6.2.5" - case Some(_) => "6.3.0" + case Some(_) => "6.3.0" case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Argonaut") @@ -37,7 +38,7 @@ def theArgonautVersion(scalaVersion: String) = def thePlayVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor >= 12 => "2.8.0" - case Some((3, _)) => "2.8.0" + case Some((3, _)) => "2.8.0" case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Play") } @@ -45,7 +46,7 @@ def thePlayVersion(scalaVersion: String) = def theSlickVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor <= 11 => "3.3.3" - case Some(_) => "3.3.3" + case Some(_) => "3.3.3" case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Slick") } @@ -53,7 +54,7 @@ def theSlickVersion(scalaVersion: String) = def theCatsVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor <= 11 => "2.0.0" - case Some(_) => "2.6.1" + case Some(_) => "2.6.1" case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Cats") } @@ -70,7 +71,7 @@ def thePlayJsonVersion(scalaVersion: String) = def theCirceVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { - case Some((3, _)) => "0.14.1" + case Some((3, _)) => "0.14.1" case Some((2, scalaMajor)) if scalaMajor >= 12 => "0.14.1" case Some((2, scalaMajor)) if scalaMajor >= 11 => "0.11.1" case _ => @@ -122,7 +123,7 @@ lazy val scala_2_13 = Project(id = "scala_2_13", base = file("scala_2_13")) // Do not publish this project (it just serves as an aggregate) publishArtifact := false, publishLocal := {}, - //doctestWithDependencies := false, // sbt-doctest is not yet compatible with this 2.13 + // doctestWithDependencies := false, // sbt-doctest is not yet compatible with this 2.13 aggregate in publish := false, aggregate in PgpKeys.publishSigned := false ) @@ -157,7 +158,7 @@ lazy val scala_2_11 = Project(id = "scala_2_11", base = file("scala_2_11")) // Do not publish this project (it just serves as an aggregate) publishArtifact := false, publishLocal := {}, - //doctestWithDependencies := false, // sbt-doctest is not yet compatible with this 2.13 + // doctestWithDependencies := false, // sbt-doctest is not yet compatible with this 2.13 aggregate in publish := false, aggregate in PgpKeys.publishSigned := false ) @@ -206,9 +207,7 @@ lazy val macros = crossProject(JSPlatform, JVMPlatform) .settings(testSettings) .jsSettings(jsTestSettings) .settings(commonWithPublishSettings) - .settings(withCompatUnmanagedSources( - jsJvmCrossProject = true, - includeTestSrcs = false)) + .settings(withCompatUnmanagedSources(jsJvmCrossProject = true, includeTestSrcs = false)) .settings( name := "enumeratum-macros", version := Versions.Macros.head, @@ -227,7 +226,7 @@ lazy val macros = crossProject(JSPlatform, JVMPlatform) lazy val macrosJS = macros.js lazy val macrosJVM = macros.jvm -lazy val devMacros = sys.props.get("enumeratum.devMacros").nonEmpty +lazy val useLocalVersion = sys.props.get("enumeratum.useLocalVersion").nonEmpty // Aggregates core lazy val coreAggregate = aggregateProject("core", coreJS, coreJVM) @@ -238,11 +237,11 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) .jsSettings(jsTestSettings) .settings(commonWithPublishSettings) .settings( - name := "enumeratum", - version := Versions.Core.head, - crossScalaVersions := scalaVersionsAll, + name := "enumeratum", + version := Versions.Core.head, + crossScalaVersions := scalaVersionsAll, libraryDependencies ++= { - if (devMacros) { + if (useLocalVersion) { Seq.empty } else { Seq("com.beachape" %% "enumeratum-macros" % Versions.Macros.stable) @@ -250,35 +249,42 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) } ) -def configureWithMacro(m: Project): Project => Project = { p => - if (devMacros) { // used for testing macros +def configureWithLocal(m: Project): Project => Project = { p => + if (useLocalVersion) { // used for testing macros p.dependsOn(m) } else { p } } -lazy val coreJS = core.js.configure(configureWithMacro(macrosJS)) -lazy val coreJVM = core.jvm.configure(configureWithMacro(macrosJVM)) +lazy val coreJS = core.js.configure(configureWithLocal(macrosJS)) +lazy val coreJVM = core.jvm.configure(configureWithLocal(macrosJVM)) lazy val testsAggregate = aggregateProject("test", enumeratumTestJs, enumeratumTestJvm) + // Project models used in test for some subprojects lazy val enumeratumTest = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("enumeratum-test")) - .settings(testSettings: _*) - .jsSettings(jsTestSettings: _*) - .settings(commonWithPublishSettings: _*) + .settings(testSettings) + .jsSettings(jsTestSettings) + .settings(commonWithPublishSettings) .settings( name := "enumeratum-test", version := Versions.Core.stable, crossScalaVersions := scalaVersionsAll, - libraryDependencies += { - "com.beachape" %%% "enumeratum" % Versions.Core.stable + libraryDependencies ++= { + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) + } } ) -lazy val enumeratumTestJs = enumeratumTest.js -lazy val enumeratumTestJvm = enumeratumTest.jvm + +lazy val enumeratumTestJs = enumeratumTest.js.configure(configureWithLocal(coreJS)) + +lazy val enumeratumTestJvm = enumeratumTest.jvm.configure(configureWithLocal(coreJVM)) lazy val coreJVMTests = Project(id = "coreJVMTests", base = file("enumeratum-core-jvm-tests")) .enablePlugins(BuildInfoPlugin) @@ -294,15 +300,20 @@ lazy val coreJVMTests = Project(id = "coreJVMTests", base = file("enumeratum-cor ), buildInfoPackage := "enumeratum" ) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) .settings( name := "coreJVMTests", version := Versions.Core.stable, crossScalaVersions := scalaVersionsAll, - libraryDependencies ++= Seq( - "org.scala-lang" % "scala-compiler" % scalaVersion.value % Test - ), + Test / sourceGenerators += CoreJVMTest.testsGenerator, + libraryDependencies += { + if (scalaBinaryVersion.value == "3") { + "org.scala-lang" %% "scala3-compiler" % scalaVersion.value % Test + } else { + "org.scala-lang" % "scala-compiler" % scalaVersion.value % Test + } + }, publishArtifact := false, publishLocal := {} ) @@ -310,40 +321,60 @@ lazy val coreJVMTests = Project(id = "coreJVMTests", base = file("enumeratum-cor lazy val enumeratumReactiveMongoBson = Project(id = "enumeratum-reactivemongo-bson", base = file("enumeratum-reactivemongo-bson")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) .settings( version := "1.7.0", crossScalaVersions := scalaVersionsAll, - libraryDependencies ++= Seq( - "org.reactivemongo" %% "reactivemongo-bson-api" % reactiveMongoVersion % Provided, - "com.beachape" %% "enumeratum" % Versions.Core.stable, - "com.beachape" %% "enumeratum-test" % Versions.Core.stable % Test - ) + libraryDependencies += { + "org.reactivemongo" %% "reactivemongo-bson-api" % reactiveMongoVersion % Provided + }, + libraryDependencies ++= { + if (useLocalVersion) { + Seq.empty + } else { + Seq( + "com.beachape" %% "enumeratum" % Versions.Core.stable, + "com.beachape" %% "enumeratum-test" % Versions.Core.stable % Test + ) + } + } ) + .configure(configureWithLocal(enumeratumTestJvm)) lazy val playJsonAggregate = aggregateProject("play-json", enumeratumPlayJsonJs, enumeratumPlayJsonJvm) + lazy val enumeratumPlayJson = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("enumeratum-play-json")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) - .jsSettings(jsTestSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) + .jsSettings(jsTestSettings) .settings( name := "enumeratum-play-json", version := "1.7.1-SNAPSHOT", crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version), + libraryDependencies += { + "com.typesafe.play" %%% "play-json" % thePlayJsonVersion(scalaVersion.value) + }, libraryDependencies ++= { - Seq( - "com.typesafe.play" %%% "play-json" % thePlayJsonVersion(scalaVersion.value), - "com.beachape" %%% "enumeratum" % Versions.Core.stable, - "com.beachape" %%% "enumeratum-test" % Versions.Core.stable % Test - ) + if (useLocalVersion) { + Seq.empty + } else { + Seq( + "com.beachape" %% "enumeratum" % Versions.Core.stable, + "com.beachape" %% "enumeratum-test" % Versions.Core.stable % Test + ) + } } ) -lazy val enumeratumPlayJsonJs = enumeratumPlayJson.js + +lazy val enumeratumPlayJsonJs = enumeratumPlayJson.js + .configure(configureWithLocal(enumeratumTestJs)) + lazy val enumeratumPlayJsonJvm = enumeratumPlayJson.jvm + .configure(configureWithLocal(enumeratumTestJvm)) lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratum-play")) .settings(commonWithPublishSettings) @@ -351,20 +382,27 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu .settings( version := "1.7.1-SNAPSHOT", crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version), - libraryDependencies ++= - Seq( - "com.typesafe.play" %% "play" % thePlayVersion(scalaVersion.value), - "com.beachape" %% "enumeratum" % Versions.Core.stable, - "com.beachape" %% "enumeratum-test" % Versions.Core.stable % Test, - scalaTestPlay(scalaVersion.value) - ) + libraryDependencies ++= Seq( + "com.typesafe.play" %% "play" % thePlayVersion(scalaVersion.value), + scalaTestPlay(scalaVersion.value) + ), + libraryDependencies ++= { + if (useLocalVersion) { + Seq.empty + } else { + Seq( + "com.beachape" %% "enumeratum" % Versions.Core.stable, + "com.beachape" %% "enumeratum-test" % Versions.Core.stable % Test + ) + } + } ) - .settings(withCompatUnmanagedSources( - jsJvmCrossProject = false, - includeTestSrcs = true)) + .settings(withCompatUnmanagedSources(jsJvmCrossProject = false, includeTestSrcs = true)) + .configure(configureWithLocal(enumeratumTestJvm)) .dependsOn(enumeratumPlayJsonJvm % "compile->compile;test->test") lazy val circeAggregate = aggregateProject("circe", enumeratumCirceJs, enumeratumCirceJvm) + lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("enumeratum-circe")) @@ -374,11 +412,15 @@ lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) .settings( name := "enumeratum-circe", version := "1.7.1-SNAPSHOT", + libraryDependencies += { + "io.circe" %%% "circe-core" % theCirceVersion(scalaVersion.value) + }, libraryDependencies ++= { - Seq( - "com.beachape" %%% "enumeratum" % Versions.Core.stable, - "io.circe" %%% "circe-core" % theCirceVersion(scalaVersion.value) - ) + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) + } } ) .jvmSettings( @@ -388,45 +430,63 @@ lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version) ) -lazy val enumeratumCirceJs = enumeratumCirce.js +lazy val enumeratumCirceJs = enumeratumCirce.js + .configure(configureWithLocal(enumeratumTestJs)) + lazy val enumeratumCirceJvm = enumeratumCirce.jvm + .configure(configureWithLocal(enumeratumTestJvm)) lazy val argonautAggregate = aggregateProject("argonaut", enumeratumArgonautJs, enumeratumArgonautJvm) + lazy val enumeratumArgonaut = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("enumeratum-argonaut")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) - .jsSettings(jsTestSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) + .jsSettings(jsTestSettings) .settings( name := "enumeratum-argonaut", version := "1.7.1-SNAPSHOT", crossScalaVersions := scalaVersionsAll, + libraryDependencies += { + "io.argonaut" %%% "argonaut" % theArgonautVersion(scalaVersion.value) + }, libraryDependencies ++= { - Seq( - "com.beachape" %%% "enumeratum" % Versions.Core.stable, - "io.argonaut" %%% "argonaut" % theArgonautVersion(scalaVersion.value) - ) + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) + } } ) -lazy val enumeratumArgonautJs = enumeratumArgonaut.js +lazy val enumeratumArgonautJs = enumeratumArgonaut.js + .configure(configureWithLocal(coreJS)) + lazy val enumeratumArgonautJvm = enumeratumArgonaut.jvm + .configure(configureWithLocal(coreJVM)) lazy val enumeratumJson4s = Project(id = "enumeratum-json4s", base = file("enumeratum-json4s")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) .settings( version := "1.7.2-SNAPSHOT", crossScalaVersions := scalaVersionsAll, libraryDependencies ++= Seq( - "org.json4s" %% "json4s-core" % json4sVersion, - "org.json4s" %% "json4s-native" % json4sVersion % Test, - "com.beachape" %% "enumeratum" % Versions.Core.stable - ) + "org.json4s" %% "json4s-core" % json4sVersion, + "org.json4s" %% "json4s-native" % json4sVersion % Test + ), + libraryDependencies ++= { + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) + } + } ) + .configure(configureWithLocal(coreJVM)) lazy val scalacheckAggregate = aggregateProject("scalacheck", enumeratumScalacheckJs, enumeratumScalacheckJvm) @@ -434,25 +494,34 @@ lazy val scalacheckAggregate = lazy val enumeratumScalacheck = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("enumeratum-scalacheck")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) - .jsSettings(jsTestSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) + .jsSettings(jsTestSettings) .settings( name := "enumeratum-scalacheck", version := "1.7.1-SNAPSHOT", crossScalaVersions := scalaVersionsAll, libraryDependencies ++= { Seq( - "com.beachape" %%% "enumeratum" % Versions.Core.stable, "org.scalacheck" %%% "scalacheck" % theScalacheckVersion(scalaVersion.value), "org.scalatestplus" %%% "scalacheck-1-14" % "3.1.1.1" % Test, "com.beachape" %%% "enumeratum-test" % Versions.Core.stable % Test ) + }, + libraryDependencies ++= { + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) + } } ) -lazy val enumeratumScalacheckJs = enumeratumScalacheck.js +lazy val enumeratumScalacheckJs = enumeratumScalacheck.js + .configure(configureWithLocal(coreJS)) + lazy val enumeratumScalacheckJvm = enumeratumScalacheck.jvm + .configure(configureWithLocal(coreJVM)) lazy val quillAggregate = aggregateProject( @@ -472,11 +541,17 @@ lazy val enumeratumQuill = crossScalaVersions := scalaVersionsAll, libraryDependencies ++= { Seq( - "com.beachape" %%% "enumeratum" % Versions.Core.stable, - "io.getquill" %%% "quill-core" % quillVersion, - "io.getquill" %%% "quill-sql" % quillVersion % Test + "io.getquill" %%% "quill-core" % quillVersion, + "io.getquill" %%% "quill-sql" % quillVersion % Test ) }, + libraryDependencies ++= { + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) + } + }, dependencyOverrides ++= { def pprintVersion(v: String) = { if (v.startsWith("2.11")) "0.5.4" else "0.5.5" @@ -487,52 +562,70 @@ lazy val enumeratumQuill = } ) // lazy val enumeratumQuillJs = enumeratumQuill.js // TODO re-enable once quill supports Scala.js 1.0 -lazy val enumeratumQuillJvm = enumeratumQuill.jvm +lazy val enumeratumQuillJvm = enumeratumQuill.jvm.configure(configureWithLocal(coreJVM)) lazy val enumeratumDoobie = Project(id = "enumeratum-doobie", base = file("enumeratum-doobie")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) .settings( crossScalaVersions := scalaVersionsAll, version := "1.7.2-SNAPSHOT", + libraryDependencies += { + "org.tpolecat" %% "doobie-core" % theDoobieVersion(scalaVersion.value) + }, libraryDependencies ++= { - Seq( - "com.beachape" %%% "enumeratum" % Versions.Core.stable, - "org.tpolecat" %% "doobie-core" % theDoobieVersion(scalaVersion.value) - ) + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %% "enumeratum" % Versions.Core.stable) + } } ) + .configure(configureWithLocal(coreJVM)) lazy val enumeratumSlick = Project(id = "enumeratum-slick", base = file("enumeratum-slick")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) .settings( version := "1.7.1-SNAPSHOT", crossScalaVersions := scalaVersionsAll, libraryDependencies ++= Seq( - "com.typesafe.slick" %% "slick" % theSlickVersion(scalaVersion.value), - "com.beachape" %% "enumeratum" % Versions.Core.stable, - "com.h2database" % "h2" % "1.4.197" % Test - ) + "com.typesafe.slick" %% "slick" % theSlickVersion(scalaVersion.value), + "com.h2database" % "h2" % "1.4.197" % Test + ), + libraryDependencies ++= { + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %% "enumeratum" % Versions.Core.stable) + } + } ) + .configure(configureWithLocal(coreJVM)) +// Cats lazy val catsAggregate = aggregateProject("cats", enumeratumCatsJs, enumeratumCatsJvm) + lazy val enumeratumCats = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .in(file("enumeratum-cats")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) - .jsSettings(jsTestSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) + .jsSettings(jsTestSettings) .settings( name := "enumeratum-cats", version := "1.7.1-SNAPSHOT", + libraryDependencies += { + "org.typelevel" %%% "cats-core" % theCatsVersion(scalaVersion.value) + }, libraryDependencies ++= { - Seq( - "com.beachape" %%% "enumeratum" % Versions.Core.stable, - "org.typelevel" %%% "cats-core" % theCatsVersion(scalaVersion.value) - ) + if (useLocalVersion) { + Seq.empty + } else { + Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) + } } ) .jvmSettings( @@ -542,8 +635,9 @@ lazy val enumeratumCats = crossProject(JSPlatform, JVMPlatform) crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version) ) -lazy val enumeratumCatsJs = enumeratumCats.js -lazy val enumeratumCatsJvm = enumeratumCats.jvm +lazy val enumeratumCatsJs = enumeratumCats.js.configure(configureWithLocal(coreJS)) + +lazy val enumeratumCatsJvm = enumeratumCats.jvm.configure(configureWithLocal(coreJVM)) lazy val commonSettings = Seq( organization := "com.beachape", @@ -626,8 +720,8 @@ lazy val compilerSettings = Seq( ) // unused-import breaks Circe Either shim case Some((2, 11)) => base ++ Seq("-deprecation:false", "-Xlint", "-Ywarn-unused-import") - case Some((2, _)) => base ++ Seq("-Xlint") - case _ => base + case Some((2, _)) => base ++ Seq("-Xlint") + case _ => base } }, Test / scalacOptions ++= { @@ -724,7 +818,7 @@ def withCompatUnmanagedSources( ): Seq[Setting[_]] = { def compatDirs(projectbase: File, scalaVersion: String, isMain: Boolean) = { val base = if (jsJvmCrossProject) projectbase / ".." else projectbase - val cat = if (isMain) "main" else "test" + val cat = if (isMain) "main" else "test" CrossVersion.partialVersion(scalaVersion) match { case Some((3, _)) => diff --git a/enumeratum-argonaut/src/main/scala/enumeratum/values/ArgonautValueEnum.scala b/enumeratum-argonaut/src/main/scala/enumeratum/values/ArgonautValueEnum.scala index dc040a3f..8bfb3bab 100644 --- a/enumeratum-argonaut/src/main/scala/enumeratum/values/ArgonautValueEnum.scala +++ b/enumeratum-argonaut/src/main/scala/enumeratum/values/ArgonautValueEnum.scala @@ -37,7 +37,7 @@ import Argonaut._ * }}} * * @tparam ValueType - * @tparam EntryType + * @tparam EntryType */ sealed trait ArgonautValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { this: ValueEnum[ValueType, EntryType] => diff --git a/enumeratum-core-jvm-tests/src/test/scala/enumeratum/EnumJVMSpec.scala b/enumeratum-core-jvm-tests/src/test/scala/enumeratum/EnumJVMSpec.scala index b6ebe4c4..86428a38 100644 --- a/enumeratum-core-jvm-tests/src/test/scala/enumeratum/EnumJVMSpec.scala +++ b/enumeratum-core-jvm-tests/src/test/scala/enumeratum/EnumJVMSpec.scala @@ -3,39 +3,5 @@ package enumeratum import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class EnumJVMSpec extends AnyFunSpec with Matchers { - - describe("findValues Vector") { - - // This is a fairly intense test. - it("should be in the same order that the objects were declared in") { - import scala.util._ - (1 to 100).foreach { i => - val members = Random.shuffle((1 to Random.nextInt(20)).map { m => - s"Member$m" - }) - val membersDefs = members - .map { m => - s"case object $m extends Enum$i" - } - .mkString("\n\n") - val objDefinition = - s""" - import enumeratum._ - sealed trait Enum$i extends EnumEntry - - case object Enum$i extends Enum[Enum$i] { - $membersDefs - val values = findValues - } - - Enum$i - """ - val obj = Eval.apply[Enum[_ <: EnumEntry]](objDefinition) - obj.values.map(_.entryName) shouldBe members - } - } - - } - -} +// See `projects/project/CoreJVMTest.scala`#generateEnumTests +final class EnumJVMSpec extends generated.EnumBaseSpec diff --git a/enumeratum-core-jvm-tests/src/test/scala/enumeratum/Eval.scala b/enumeratum-core-jvm-tests/src/test/scala/enumeratum/Eval.scala deleted file mode 100644 index 3f8a0e59..00000000 --- a/enumeratum-core-jvm-tests/src/test/scala/enumeratum/Eval.scala +++ /dev/null @@ -1,25 +0,0 @@ -package enumeratum - -import scala.tools.reflect.ToolBox - -/** Eval with bits and pieces stolen from here and there... - */ -object Eval { - - def apply[A]( - string: String, - compileOptions: String = s"-cp ${macroToolboxClassPath.mkString(";")}" - ): A = { - import scala.reflect.runtime.currentMirror - val toolbox = currentMirror.mkToolBox(options = compileOptions) - val tree = toolbox.parse(string) - toolbox.eval(tree).asInstanceOf[A] - } - - def macroToolboxClassPath = { - val paths = Seq( - BuildInfo.macrosJVMClassesDir - ) - paths.map(_.getAbsolutePath) - } -} diff --git a/enumeratum-core-jvm-tests/src/test/scala/enumeratum/values/ValueEnumJVMSpec.scala b/enumeratum-core-jvm-tests/src/test/scala/enumeratum/values/ValueEnumJVMSpec.scala index ad59cdb0..cd37ba53 100644 --- a/enumeratum-core-jvm-tests/src/test/scala/enumeratum/values/ValueEnumJVMSpec.scala +++ b/enumeratum-core-jvm-tests/src/test/scala/enumeratum/values/ValueEnumJVMSpec.scala @@ -1,91 +1,14 @@ package enumeratum.values -import enumeratum.Eval -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers - -import scala.reflect.ClassTag -import scala.util.Random - /** Created by Lloyd on 8/30/16. * * Copyright 2016 */ -class ValueEnumJVMSpec extends AnyFunSpec with Matchers { - - private def stringGenerator = - Random.alphanumeric.grouped(10).toStream.map(_.mkString.replaceAll("[0-9]", "")).distinct - - /* - Non-deterministically generates a bunch of different types of ValueEnums and tests the ability to resolve - proper members by value - */ - testValuesOf(Stream.continually(Random.nextInt())) - testValuesOf(Stream.continually(Random.nextLong()), valueSuffix = "L") - testValuesOf( - Stream - .continually(Random.nextInt(Short.MaxValue - Short.MinValue) + Short.MinValue) - .map(_.toShort) - ) - testValuesOf( - Stream.continually(Random.nextInt(Byte.MaxValue - Byte.MinValue) + Byte.MinValue).map(_.toByte) - ) - testValuesOf(stringGenerator, "\"", "\"") - testValuesOf( - Stream.continually(Random.nextPrintableChar()).filter(c => Character.isAlphabetic(c.toInt)), - "'", - "'" - ) - - private def testValuesOf[A: ClassTag]( - valuesGenerator: => Stream[A], - valuePrefix: String = "", - valueSuffix: String = "" - ): Unit = { - - val typeName = implicitly[ClassTag[A]].runtimeClass.getSimpleName.capitalize - - describe(s"${typeName}Enum withValue") { - - it("should return proper members for valid values but throw otherwise") { - (1 to 20).foreach { i => - val enumName = s"Generated${typeName}Enum$i" - val names = stringGenerator.take(5) - val values = valuesGenerator.distinct.take(5) - val namesToValues = names.zip(values) - val memberDefs = namesToValues - .map { case (n, v) => - s"""case object $n extends $enumName($valuePrefix$v$valueSuffix)""" - } - .mkString("\n\n") - val objDef = - s""" - |import enumeratum.values._ - | - |sealed abstract class $enumName(val value: $typeName) extends ${typeName}EnumEntry - | - |object $enumName extends ${typeName}Enum[$enumName] { - | val values = findValues - | - | $memberDefs - |} - |$enumName - """.stripMargin - val obj = Eval.apply[ValueEnum[A, _ <: ValueEnumEntry[A]]](objDef) - namesToValues.foreach { case (n, v) => - obj.withValue(v).toString shouldBe n - } - // filterNot is not lazy until 2.12 - valuesGenerator.filter(a => !values.contains(a)).take(5).foreach { invalidValue => - intercept[NoSuchElementException] { - obj.withValue(invalidValue) - } - } - } - } - - } - - } - -} +// See `projects/project/CoreJVMTest.scala`#generateValueEnumTests +final class ValueEnumJVMSpec + extends generated.IntValueEnumBaseSpec + with generated.LongValueEnumBaseSpec + with generated.ShortValueEnumBaseSpec + with generated.ByteValueEnumBaseSpec + with generated.StringValueEnumBaseSpec + with generated.CharValueEnumBaseSpec diff --git a/macros/src/main/scala-3/enumeratum/EnumHelper.scala b/macros/src/main/scala-3/enumeratum/EnumHelper.scala deleted file mode 100644 index ae004ea2..00000000 --- a/macros/src/main/scala-3/enumeratum/EnumHelper.scala +++ /dev/null @@ -1,92 +0,0 @@ -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 diff --git a/macros/src/main/scala-3/enumeratum/Macros.scala b/macros/src/main/scala-3/enumeratum/Macros.scala deleted file mode 100644 index 44654b1b..00000000 --- a/macros/src/main/scala-3/enumeratum/Macros.scala +++ /dev/null @@ -1,31 +0,0 @@ -package test - -object Macros { - import scala.quoted.{Expr, Quotes, Type} - - inline def show[A]: String = ${ showImpl[A] } - - private def showImpl[A](using tpe: Type[A], q: Quotes): Expr[String] = { - import q.reflect.* - - val repr = TypeRepr.of[A](using tpe) - - val tpeSym = repr.typeSymbol - /* - > _root_.test.Macros.show[_root_.test.Bar.type] - - val res0: String = @scala.annotation.internal.SourceFile("macros/src/main/scala-3/enumeratum/Foo.scala") object Bar extends test.Foo { this: test.Bar.type => - - } - */ - - // val tpeSym = repr.typeSymbol.companionModule - /* - > _root_.test.Macros.show[_root_.test.Bar.type] - - val res0: String = lazy val Bar: test.Bar.type - */ - - Expr(tpeSym.tree.show) - } -} diff --git a/project/CoreJVMTest.scala b/project/CoreJVMTest.scala new file mode 100644 index 00000000..7eedbb40 --- /dev/null +++ b/project/CoreJVMTest.scala @@ -0,0 +1,242 @@ +import scala.util.Random + +import sbt._ +import Keys._ + +object CoreJVMTest { + lazy val testsGenerator = Def.task[Seq[File]] { + val managed = (Test / sourceManaged).value + + generateEnumTests(managed) ++ generateValueEnumTest[Int]( + managed, + Iterator.continually(Random.nextInt()) + ) ++ generateValueEnumTest[Long]( + managed, + Iterator.continually(Random.nextLong()), + valueSuffix = "L" + ) ++ generateValueEnumTest[Short]( + managed, + Iterator + .continually(Random.nextInt(Short.MaxValue - Short.MinValue) + Short.MinValue) + .map(_.toShort) + ) ++ generateValueEnumTest[Byte]( + managed, + Iterator + .continually(Random.nextInt(Byte.MaxValue - Byte.MinValue) + Byte.MinValue) + .map(_.toByte) + ) ++ generateValueEnumTest[String]( + managed, + Random.alphanumeric.grouped(10).map(_.mkString), + "\"", + "\"" + ) ++ generateValueEnumTest[Char]( + managed, + Iterator.continually(Random.nextPrintableChar()).filter(c => Character.isAlphabetic(c.toInt)), + "'", + "'" + ) + + } + + private def generateEnumTests(outdir: File): Seq[File] = { + val bf = outdir / "EnumBaseSpec.scala" + + IO.writer[Seq[File]](bf, "", IO.defaultCharset, false) { w0 => + w0.append("""package generated + +trait EnumBaseSpec + extends org.scalatest.funspec.AnyFunSpec + with org.scalatest.matchers.should.Matchers +""") + + val res = (1 to 100).flatMap { i => + val enumName = s"Enum${i}" + + val ef = outdir / s"${enumName}.scala" + + IO.writer[Seq[File]](ef, "", IO.defaultCharset, false) { w1 => + // Generate enum file + + w1.append(s"""package generated + +import enumeratum._ + +sealed trait ${enumName} extends EnumEntry + +object ${enumName} extends Enum[${enumName}] { + val values = findValues + +""") + + val members = Random.shuffle(1 to Random.nextInt(20)).map { n => + val nme = s"Member$n" + + w1.append(s" case object ${nme} extends ${enumName}\n") + + nme + } + + w1.append(s"""} +""") + + // Generate tests in separate file/trait + val tf = outdir / s"${enumName}Test.scala" + + w0.append(s" with ${enumName}Test\n") + + IO.writer[Seq[File]](tf, "", IO.defaultCharset, false) { w2 => + val expectedNames = members.map('"' + _ + '"').mkString(", ") + + val expectedMembers = members.map(enumName + '.' + _).mkString(", ") + + w2.append(s"""package generated + +trait ${enumName}Test { _spec: EnumBaseSpec with enumeratum.EnumJVMSpec => + describe("${enumName}.findValues") { + // This is a fairly intense test. + it("should be in the same order as declaration on objects") { + ${enumName}.values.map(_.entryName).toSeq shouldBe Seq($expectedNames) + + ${enumName}.values.toSeq shouldBe Seq($expectedMembers) + } + } +} +""") + + Seq(ef, tf) + } + } + } + + w0.append(""" { self: enumeratum.EnumJVMSpec => +}""") + + bf +: res + } + } + + // --- + + private def generateValueEnumTest[A]( + outdir: File, + valuesGenerator: => Iterator[A], + valuePrefix: String = "", + valueSuffix: String = "" + )(implicit cls: scala.reflect.ClassTag[A]): Seq[File] = { + val typeName = cls.runtimeClass.getSimpleName.capitalize + + def renderValue(v: A): String = valuePrefix + v.toString + valueSuffix + + @annotation.tailrec + def genValues(rem: Int, out: IndexedSeq[A]): IndexedSeq[A] = { + if (rem == 0) { + out.reverse + } else { + val v = valuesGenerator.next() + + if (!out.contains(v)) { + genValues(rem - 1, v +: out) + } else { + genValues(rem, out) + } + } + } + + // + val bf = outdir / s"${typeName}ValueEnumBaseSpec.scala" + + IO.writer[Seq[File]](bf, "", IO.defaultCharset, false) { w0 => + w0.append(s"""package generated + +trait ${typeName}ValueEnumBaseSpec + extends org.scalatest.funspec.AnyFunSpec + with org.scalatest.matchers.should.Matchers +""") + + val res = (1 to 20).flatMap { i => + val enumName = s"${typeName}Enum$i" + val names = stringGenerator(5) + val values = genValues(5, IndexedSeq.empty) + val namesToValues = names.zip(values) + + // Generate value enum file + val ef = outdir / s"${enumName}.scala" + + IO.writer[Seq[File]](ef, "", IO.defaultCharset, false) { w1 => + w1.append(s"""package generated + +import enumeratum.values._ + +sealed abstract class $enumName( + val value: $typeName +) extends ${typeName}EnumEntry + +object $enumName extends ${typeName}Enum[$enumName] { + val values = findValues + +""") + + namesToValues.foreach { case (n, v) => + w1.append(s" case object $n extends $enumName($valuePrefix$v$valueSuffix)\n\n") + } + + w1.append("}\n") + + // Generate test in separate file + val tf = outdir / s"${enumName}Test.scala" + + w0.append(s" with ${enumName}Test\n") + + IO.writer[Seq[File]](tf, "", IO.defaultCharset, false) { w2 => + w2.append(s"""package generated + +trait ${enumName}Test { + _spec: ${typeName}ValueEnumBaseSpec with enumeratum.values.ValueEnumJVMSpec => + + describe("${enumName} withValue") { + it("should return proper members for valid values but throw otherwise") { +""") + + namesToValues.foreach { case (n, v) => + val value = renderValue(v) + + w2.append(s""" ${enumName}.withValue($value) shouldBe ${enumName}.${n} + +""") + } + + valuesGenerator.filter(a => !values.contains(a)).take(5).foreach { invalidValue => + val value = renderValue(invalidValue) + + w2.append(s""" intercept[NoSuchElementException] { + ${enumName}.withValue($value) + } + +""") + } + + w2.append(""" } + } +} +""") + + Seq(ef, tf) + } + } + } + + w0.append(""" { self: enumeratum.values.ValueEnumJVMSpec => +}""") + + bf +: res + } + } + + private def stringGenerator(n: Int) = scala.util.Random.alphanumeric + .grouped(10) + .toStream + .map(_.mkString.replaceAll("[0-9]", "")) + .distinct + .take(n) + .toSeq +} From 9dc1974e42d2572a3771ed1762f17f4872bbfada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Tue, 6 Sep 2022 16:30:56 +0200 Subject: [PATCH 04/16] Update build & tests --- build.sbt | 139 ++++++++---------- .../main/scala-2/enumeratum/EnumCompat.scala | 2 + .../enumeratum/values/ValueEnumCompat.scala | 2 + .../test/scala/enumeratum/values/Bites.scala | 2 +- .../scala/enumeratum/ScalacheckTest.scala | 2 - .../enumeratum/values/ScalacheckSpec.scala | 2 +- .../scala/enumeratum/values/Alphabet.scala | 18 --- .../main/scala/enumeratum/values/Animal.scala | 30 ---- .../main/scala/enumeratum/values/Bites.scala | 16 -- .../scala/enumeratum/values/ContentType.scala | 20 --- .../main/scala/enumeratum/values/Drinks.scala | 20 --- .../scala/enumeratum/values/LibraryItem.scala | 27 ---- .../scala/enumeratum/values/MovieGenre.scala | 23 --- .../enumeratum/values/OperatingSystem.scala | 18 --- 14 files changed, 70 insertions(+), 251 deletions(-) delete mode 100644 enumeratum-test/src/main/scala/enumeratum/values/Alphabet.scala delete mode 100644 enumeratum-test/src/main/scala/enumeratum/values/Animal.scala delete mode 100644 enumeratum-test/src/main/scala/enumeratum/values/Bites.scala delete mode 100644 enumeratum-test/src/main/scala/enumeratum/values/ContentType.scala delete mode 100644 enumeratum-test/src/main/scala/enumeratum/values/Drinks.scala delete mode 100644 enumeratum-test/src/main/scala/enumeratum/values/LibraryItem.scala delete mode 100644 enumeratum-test/src/main/scala/enumeratum/values/MovieGenre.scala delete mode 100644 enumeratum-test/src/main/scala/enumeratum/values/OperatingSystem.scala diff --git a/build.sbt b/build.sbt index 484e4223..01151186 100644 --- a/build.sbt +++ b/build.sbt @@ -124,8 +124,8 @@ lazy val scala_2_13 = Project(id = "scala_2_13", base = file("scala_2_13")) publishArtifact := false, publishLocal := {}, // doctestWithDependencies := false, // sbt-doctest is not yet compatible with this 2.13 - aggregate in publish := false, - aggregate in PgpKeys.publishSigned := false + publish / aggregate := false, + PgpKeys.publishSigned / aggregate := false ) .aggregate((baseProjectRefs ++ scala213ProjectRefs): _*) @@ -134,7 +134,7 @@ lazy val scala211ProjectRefs = Seq( enumeratumJson4s, enumeratumScalacheckJvm, enumeratumScalacheckJs, - enumeratumPlayJsonJvm, + // enumeratumPlayJsonJvm, // TODO drop 2.11 as play-json 2.7.x supporting Scala.js 1.x is unlikely? // enumeratumPlayJsonJs, TODO re-enable once play-json supports Scala.js 1.0 enumeratumArgonautJs, @@ -159,8 +159,8 @@ lazy val scala_2_11 = Project(id = "scala_2_11", base = file("scala_2_11")) publishArtifact := false, publishLocal := {}, // doctestWithDependencies := false, // sbt-doctest is not yet compatible with this 2.13 - aggregate in publish := false, - aggregate in PgpKeys.publishSigned := false + publish / aggregate := false, + PgpKeys.publishSigned / aggregate := false ) .aggregate((baseProjectRefs ++ scala211ProjectRefs): _*) @@ -186,17 +186,17 @@ lazy val integrationProjectRefs = Seq( lazy val root = Project(id = "enumeratum-root", base = file(".")) - .settings(commonWithPublishSettings: _*) + .settings(commonWithPublishSettings) .settings( name := "enumeratum-root", crossVersion := CrossVersion.binary, crossScalaVersions := Nil, git.gitRemoteRepo := "git@github.com:lloydmeta/enumeratum.git", // Do not publish the root project (it just serves as an aggregate) - publishArtifact := false, - publishLocal := {}, - aggregate in publish := false, - aggregate in PgpKeys.publishSigned := false + publishArtifact := false, + publishLocal := {}, + publish / aggregate := false, + PgpKeys.publishSigned / aggregate := false ) .aggregate(baseProjectRefs ++ integrationProjectRefs: _*) @@ -212,13 +212,11 @@ lazy val macros = crossProject(JSPlatform, JVMPlatform) name := "enumeratum-macros", version := Versions.Macros.head, crossScalaVersions := scalaVersionsAll, // eventually move this to aggregateProject once more 2.13 libs are out - libraryDependencies ++= { - if (scalaBinaryVersion.value startsWith "2.") { - Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value) + libraryDependencies += { + if (scalaBinaryVersion.value == "3") { + "org.scala-lang" %% "scala3-compiler" % scalaVersion.value % Provided } else { - Seq( - "org.scala-lang" %% "scala3-compiler" % scalaVersion.value % Provided - ) + "org.scala-lang" % "scala-reflect" % scalaVersion.value } } ) @@ -249,42 +247,33 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) } ) -def configureWithLocal(m: Project): Project => Project = { p => +def configureWithLocal( + dep: (Project, Option[String]), + deps: List[(Project, Option[String])] = Nil +): Project => Project = { if (useLocalVersion) { // used for testing macros - p.dependsOn(m) + { (prj: Project) => + (dep :: deps).foldLeft(prj) { + case (p, (m, Some(x))) => + p.dependsOn(m % x) + + case (p, (m, None)) => + p.dependsOn(m) + } + } } else { - p + identity[Project] } } -lazy val coreJS = core.js.configure(configureWithLocal(macrosJS)) -lazy val coreJVM = core.jvm.configure(configureWithLocal(macrosJVM)) - -lazy val testsAggregate = aggregateProject("test", enumeratumTestJs, enumeratumTestJvm) - -// Project models used in test for some subprojects -lazy val enumeratumTest = crossProject(JSPlatform, JVMPlatform) - .crossType(CrossType.Pure) - .in(file("enumeratum-test")) - .settings(testSettings) - .jsSettings(jsTestSettings) - .settings(commonWithPublishSettings) - .settings( - name := "enumeratum-test", - version := Versions.Core.stable, - crossScalaVersions := scalaVersionsAll, - libraryDependencies ++= { - if (useLocalVersion) { - Seq.empty - } else { - Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) - } - } - ) +def configureWithLocal(m: Project): Project => Project = + configureWithLocal(m -> Option.empty[String]) -lazy val enumeratumTestJs = enumeratumTest.js.configure(configureWithLocal(coreJS)) +def configureWithLocal(m: Project, x: String): Project => Project = + configureWithLocal(m -> Some(x)) -lazy val enumeratumTestJvm = enumeratumTest.jvm.configure(configureWithLocal(coreJVM)) +lazy val coreJS = core.js.configure(configureWithLocal(macrosJS)) +lazy val coreJVM = core.jvm.configure(configureWithLocal(macrosJVM)) lazy val coreJVMTests = Project(id = "coreJVMTests", base = file("enumeratum-core-jvm-tests")) .enablePlugins(BuildInfoPlugin) @@ -295,7 +284,7 @@ lazy val coreJVMTests = Project(id = "coreJVMTests", base = file("enumeratum-cor scalaVersion, sbtVersion, BuildInfoKey.action("macrosJVMClassesDir") { - ((macrosJVM / classDirectory) in Compile).value + (macrosJVM / Compile / classDirectory).value } ), buildInfoPackage := "enumeratum" @@ -340,7 +329,7 @@ lazy val enumeratumReactiveMongoBson = } } ) - .configure(configureWithLocal(enumeratumTestJvm)) + .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) lazy val playJsonAggregate = aggregateProject("play-json", enumeratumPlayJsonJs, enumeratumPlayJsonJvm) @@ -371,10 +360,10 @@ lazy val enumeratumPlayJson = crossProject(JSPlatform, JVMPlatform) ) lazy val enumeratumPlayJsonJs = enumeratumPlayJson.js - .configure(configureWithLocal(enumeratumTestJs)) + .configure(configureWithLocal(coreJS, "compile->compile;test->test")) lazy val enumeratumPlayJsonJvm = enumeratumPlayJson.jvm - .configure(configureWithLocal(enumeratumTestJvm)) + .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratum-play")) .settings(commonWithPublishSettings) @@ -398,7 +387,7 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu } ) .settings(withCompatUnmanagedSources(jsJvmCrossProject = false, includeTestSrcs = true)) - .configure(configureWithLocal(enumeratumTestJvm)) + .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) .dependsOn(enumeratumPlayJsonJvm % "compile->compile;test->test") lazy val circeAggregate = aggregateProject("circe", enumeratumCirceJs, enumeratumCirceJvm) @@ -431,10 +420,10 @@ lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) ) lazy val enumeratumCirceJs = enumeratumCirce.js - .configure(configureWithLocal(enumeratumTestJs)) + .configure(configureWithLocal(coreJS, "compile->compile;test->test")) lazy val enumeratumCirceJvm = enumeratumCirce.jvm - .configure(configureWithLocal(enumeratumTestJvm)) + .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) lazy val argonautAggregate = aggregateProject("argonaut", enumeratumArgonautJs, enumeratumArgonautJvm) @@ -504,24 +493,26 @@ lazy val enumeratumScalacheck = crossProject(JSPlatform, JVMPlatform) libraryDependencies ++= { Seq( "org.scalacheck" %%% "scalacheck" % theScalacheckVersion(scalaVersion.value), - "org.scalatestplus" %%% "scalacheck-1-14" % "3.1.1.1" % Test, - "com.beachape" %%% "enumeratum-test" % Versions.Core.stable % Test + "org.scalatestplus" %%% "scalacheck-1-14" % "3.1.1.1" % Test ) }, libraryDependencies ++= { if (useLocalVersion) { Seq.empty } else { - Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) + Seq( + "com.beachape" %%% "enumeratum" % Versions.Core.stable, + "com.beachape" %%% "enumeratum-test" % Versions.Core.stable % Test + ) } } ) lazy val enumeratumScalacheckJs = enumeratumScalacheck.js - .configure(configureWithLocal(coreJS)) + .configure(configureWithLocal(coreJS, "compile->compile;test->test")) lazy val enumeratumScalacheckJvm = enumeratumScalacheck.jvm - .configure(configureWithLocal(coreJVM)) + .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) lazy val quillAggregate = aggregateProject( @@ -532,8 +523,8 @@ lazy val enumeratumQuill = crossProject(JVMPlatform /*, JSPlatform TODO re-enable once quill supports Scala.js 1.0 */ ) .crossType(CrossType.Pure) .in(file("enumeratum-quill")) - .settings(commonWithPublishSettings: _*) - .settings(testSettings: _*) + .settings(commonWithPublishSettings) + .settings(testSettings) // .jsSettings(jsTestSettings: _*) TODO re-enable once quill supports Scala.js 1.0 */, .settings( name := "enumeratum-quill", @@ -553,9 +544,9 @@ lazy val enumeratumQuill = } }, dependencyOverrides ++= { - def pprintVersion(v: String) = { - if (v.startsWith("2.11")) "0.5.4" else "0.5.5" - } + def pprintVersion(v: String) = + if (v startsWith "2.11") "0.5.4" else "0.5.5" + Seq( "com.lihaoyi" %%% "pprint" % pprintVersion(scalaVersion.value) ) @@ -667,17 +658,17 @@ lazy val resolverSettings = Seq( lazy val ideSettings = Seq( // Faster "sbt gen-idea" - transitiveClassifiers in Global := Seq(Artifact.SourceClassifier) + Global / transitiveClassifiers := Seq(Artifact.SourceClassifier) ) lazy val compilerSettings = Seq( - scalaJSStage in ThisBuild := { + ThisBuild / scalaJSStage := { sys.props.get("sbt.scalajs.testOpt").orElse(sys.env.get("SCALAJS_TEST_OPT")) match { case Some("full") => FullOptStage case _ => FastOptStage } }, - scalacOptions in (Compile, compile) ++= { + Compile / compile / scalacOptions ++= { val minimal = Seq( "-encoding", "UTF-8", // yes, this is 2 args @@ -763,10 +754,10 @@ lazy val publishSettings = Seq( else Some("releases" at nexus + "service/local/staging/deploy/maven2") }, - pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toCharArray), - publishMavenStyle := true, - publishArtifact in Test := false, - PgpKeys.pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toCharArray), + pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toCharArray), + publishMavenStyle := true, + Test / publishArtifact := false, + PgpKeys.pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toCharArray), pomIncludeRepository := { _ => false } @@ -774,10 +765,8 @@ lazy val publishSettings = Seq( val testSettings = { Seq( - libraryDependencies ++= { - Seq( - "org.scalatest" %%% "scalatest" % scalaTestVersion % Test - ) + libraryDependencies += { + "org.scalatest" %%% "scalatest" % scalaTestVersion % Test }, doctestGenTests := { val originalValue = doctestGenTests.value @@ -838,7 +827,7 @@ def withCompatUnmanagedSources( } val unmanagedMainDirsSetting = Seq( - unmanagedSourceDirectories in Compile ++= { + Compile / unmanagedSourceDirectories ++= { compatDirs( projectbase = baseDirectory.value, scalaVersion = scalaVersion.value, @@ -848,7 +837,7 @@ def withCompatUnmanagedSources( ) if (includeTestSrcs) { unmanagedMainDirsSetting ++ { - unmanagedSourceDirectories in Test ++= { + Test / unmanagedSourceDirectories ++= { compatDirs( projectbase = baseDirectory.value, scalaVersion = scalaVersion.value, diff --git a/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala b/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala index 7e6aeb02..fb7408a7 100644 --- a/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala +++ b/enumeratum-core/src/main/scala-2/enumeratum/EnumCompat.scala @@ -1,5 +1,7 @@ package enumeratum +import scala.collection.immutable.IndexedSeq + import scala.language.experimental.macros private[enumeratum] trait EnumCompat[A <: EnumEntry] { _: Enum[A] => diff --git a/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala b/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala index 29a4f49d..25541c98 100644 --- a/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala +++ b/enumeratum-core/src/main/scala-2/enumeratum/values/ValueEnumCompat.scala @@ -2,6 +2,8 @@ package enumeratum.values import scala.language.experimental.macros +import scala.collection.immutable.IndexedSeq + import _root_.enumeratum.{EnumMacros, ValueEnumMacros} private[enumeratum] trait IntEnumCompanion { diff --git a/enumeratum-core/src/test/scala/enumeratum/values/Bites.scala b/enumeratum-core/src/test/scala/enumeratum/values/Bites.scala index 20e18f0c..663d573d 100644 --- a/enumeratum-core/src/test/scala/enumeratum/values/Bites.scala +++ b/enumeratum-core/src/test/scala/enumeratum/values/Bites.scala @@ -7,7 +7,7 @@ package enumeratum.values sealed abstract class Bites(val value: Byte) extends ByteEnumEntry object Bites extends ByteEnum[Bites] { - val values = findValues + lazy val values = findValues case object OneByte extends Bites(1) case object TwoByte extends Bites(2) diff --git a/enumeratum-scalacheck/src/test/scala/enumeratum/ScalacheckTest.scala b/enumeratum-scalacheck/src/test/scala/enumeratum/ScalacheckTest.scala index 442c2fd4..81baa827 100644 --- a/enumeratum-scalacheck/src/test/scala/enumeratum/ScalacheckTest.scala +++ b/enumeratum-scalacheck/src/test/scala/enumeratum/ScalacheckTest.scala @@ -48,7 +48,5 @@ trait ScalacheckTest { ) } } - } - } diff --git a/enumeratum-scalacheck/src/test/scala/enumeratum/values/ScalacheckSpec.scala b/enumeratum-scalacheck/src/test/scala/enumeratum/values/ScalacheckSpec.scala index a65ed67b..a377da05 100644 --- a/enumeratum-scalacheck/src/test/scala/enumeratum/values/ScalacheckSpec.scala +++ b/enumeratum-scalacheck/src/test/scala/enumeratum/values/ScalacheckSpec.scala @@ -1,6 +1,7 @@ package enumeratum.values import enumeratum.ScalacheckTest + import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks @@ -19,5 +20,4 @@ class ScalacheckSpec test[LongEnumEntry, ContentType]("LongEnumEntry") test[ShortEnumEntry, Drinks]("ShortEnumEntry") test[StringEnumEntry, OperatingSystem]("StringEnumEntry") - } diff --git a/enumeratum-test/src/main/scala/enumeratum/values/Alphabet.scala b/enumeratum-test/src/main/scala/enumeratum/values/Alphabet.scala deleted file mode 100644 index 5bc16df4..00000000 --- a/enumeratum-test/src/main/scala/enumeratum/values/Alphabet.scala +++ /dev/null @@ -1,18 +0,0 @@ -package enumeratum.values - -/** Created by Lloyd on 9/24/16. - * - * Copyright 2016 - */ -sealed abstract class Alphabet(val value: Char) extends CharEnumEntry - -case object Alphabet extends CharEnum[Alphabet] { - - val values = findValues - - case object A extends Alphabet('A') - case object B extends Alphabet('B') - case object C extends Alphabet('C') - case object D extends Alphabet('D') - -} diff --git a/enumeratum-test/src/main/scala/enumeratum/values/Animal.scala b/enumeratum-test/src/main/scala/enumeratum/values/Animal.scala deleted file mode 100644 index 75430fb9..00000000 --- a/enumeratum-test/src/main/scala/enumeratum/values/Animal.scala +++ /dev/null @@ -1,30 +0,0 @@ -package enumeratum.values - -/** Created by Lloyd on 8/22/16. - * - * Copyright 2016 - */ -sealed abstract class Animal(val value: Long) extends LongEnumEntry - -case object Animal extends LongEnum[Animal] { - - val values = findValues - - case object Plant extends Animal(1L) - case object Reptile extends Animal(2L) - case object Mammal extends Animal(3L) - - sealed abstract class Mammalian(val value: Int) extends IntEnumEntry - - object Mammalian extends IntEnum[Mammalian] { - - val values = findValues - - case object Dog extends Mammalian(1) - case object Cat extends Mammalian(2) - case object Whale extends Mammalian(3) - case object Mouse extends Mammalian(4) - case object Human extends Mammalian(5) - - } -} diff --git a/enumeratum-test/src/main/scala/enumeratum/values/Bites.scala b/enumeratum-test/src/main/scala/enumeratum/values/Bites.scala deleted file mode 100644 index 20e18f0c..00000000 --- a/enumeratum-test/src/main/scala/enumeratum/values/Bites.scala +++ /dev/null @@ -1,16 +0,0 @@ -package enumeratum.values - -/** Created by Lloyd on 9/24/16. - * - * Copyright 2016 - */ -sealed abstract class Bites(val value: Byte) extends ByteEnumEntry - -object Bites extends ByteEnum[Bites] { - val values = findValues - - case object OneByte extends Bites(1) - case object TwoByte extends Bites(2) - case object ThreeByte extends Bites(3) - case object FourByte extends Bites(4) -} diff --git a/enumeratum-test/src/main/scala/enumeratum/values/ContentType.scala b/enumeratum-test/src/main/scala/enumeratum/values/ContentType.scala deleted file mode 100644 index d2bc75f1..00000000 --- a/enumeratum-test/src/main/scala/enumeratum/values/ContentType.scala +++ /dev/null @@ -1,20 +0,0 @@ -package enumeratum.values - -/** Created by Lloyd on 4/12/16. - * - * Copyright 2016 - */ -sealed abstract class ContentType(val value: Long, name: String) extends LongEnumEntry - -case object ContentType extends LongEnum[ContentType] { - - case object Text extends ContentType(value = 1L, name = "text") - case object Image extends ContentType(value = 2L, name = "image") - case object Video extends ContentType(value = 3L, name = "video") - case object Audio extends ContentType(value = 4L, name = "audio") - - val values = findValues - -} - -case object Papyrus extends ContentType(5, "papyrus") diff --git a/enumeratum-test/src/main/scala/enumeratum/values/Drinks.scala b/enumeratum-test/src/main/scala/enumeratum/values/Drinks.scala deleted file mode 100644 index d00d67ad..00000000 --- a/enumeratum-test/src/main/scala/enumeratum/values/Drinks.scala +++ /dev/null @@ -1,20 +0,0 @@ -package enumeratum.values - -/** Created by Lloyd on 4/12/16. - * - * Copyright 2016 - */ -sealed abstract class Drinks(val value: Short, name: String) extends ShortEnumEntry - -case object Drinks extends ShortEnum[Drinks] { - - case object OrangeJuice extends Drinks(value = 1, name = "oj") - case object AppleJuice extends Drinks(value = 2, name = "aj") - case object Cola extends Drinks(value = 3, name = "cola") - case object Beer extends Drinks(value = 4, name = "beer") - - val values = findValues - -} - -case object CoughSyrup extends Drinks(5, "cough-syrup") diff --git a/enumeratum-test/src/main/scala/enumeratum/values/LibraryItem.scala b/enumeratum-test/src/main/scala/enumeratum/values/LibraryItem.scala deleted file mode 100644 index 54a2da52..00000000 --- a/enumeratum-test/src/main/scala/enumeratum/values/LibraryItem.scala +++ /dev/null @@ -1,27 +0,0 @@ -package enumeratum.values - -/** Created by Lloyd on 4/11/16. - * - * Copyright 2016 - */ -sealed abstract class LibraryItem(val value: Int, val name: String) extends IntEnumEntry - -case object LibraryItem extends IntEnum[LibraryItem] { - - /* - - A good mix of named, unnamed, named + unordered args - - Values are not in ordered consecutive order - */ - @SuppressWarnings( - Array("org.wartremover.warts.NonUnitStatements") - ) // out of order named-argument extending desugars to something interesting. - case object Movie extends LibraryItem(name = "movie", value = 2) - case object Book extends LibraryItem(value = 1, name = "book") - case object Magazine extends LibraryItem(10, "magazine") - case object CD extends LibraryItem(14, name = "cd") - - val values = findValues - -} - -case object Newspaper extends LibraryItem(5, "Zeitung") diff --git a/enumeratum-test/src/main/scala/enumeratum/values/MovieGenre.scala b/enumeratum-test/src/main/scala/enumeratum/values/MovieGenre.scala deleted file mode 100644 index f0da86ed..00000000 --- a/enumeratum-test/src/main/scala/enumeratum/values/MovieGenre.scala +++ /dev/null @@ -1,23 +0,0 @@ -package enumeratum.values - -/** Created by Lloyd on 4/13/16. - * - * Copyright 2016 - */ -sealed abstract class MovieGenre extends IntEnumEntry - -case object MovieGenre extends IntEnum[MovieGenre] { - - case object Action extends MovieGenre { - val value = 1 - } - case object Comedy extends MovieGenre { - val value: Int = 2 - } - case object Romance extends MovieGenre { - val value = 3 - } - - val values = findValues - -} diff --git a/enumeratum-test/src/main/scala/enumeratum/values/OperatingSystem.scala b/enumeratum-test/src/main/scala/enumeratum/values/OperatingSystem.scala deleted file mode 100644 index 712357e9..00000000 --- a/enumeratum-test/src/main/scala/enumeratum/values/OperatingSystem.scala +++ /dev/null @@ -1,18 +0,0 @@ -package enumeratum.values - -/** Created by Lloyd on 8/4/16. - * - * Copyright 2016 - */ -sealed abstract class OperatingSystem(val value: String) extends StringEnumEntry - -case object OperatingSystem extends StringEnum[OperatingSystem] { - - val values = findValues - - case object Linux extends OperatingSystem("linux") - case object OSX extends OperatingSystem("osx") - case object Windows extends OperatingSystem("windows") - case object Android extends OperatingSystem("android") - -} From 94a2589981ebbf549e289b64b419982389442665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Tue, 6 Sep 2022 21:39:30 +0200 Subject: [PATCH 05/16] Fix scalacheck for Scala 3 --- build.sbt | 53 +++++++++++++------ .../scala/enumeratum/ArbitraryInstances.scala | 4 +- project/Versions.scala | 2 +- project/plugins.sbt | 6 ++- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/build.sbt b/build.sbt index 01151186..f1c573cb 100644 --- a/build.sbt +++ b/build.sbt @@ -87,6 +87,7 @@ def theScalacheckVersion(scalaVersion: String) = def scalaTestPlay(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor >= 12 => "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test + case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for play-test") } @@ -137,10 +138,10 @@ lazy val scala211ProjectRefs = Seq( // enumeratumPlayJsonJvm, // TODO drop 2.11 as play-json 2.7.x supporting Scala.js 1.x is unlikely? // enumeratumPlayJsonJs, TODO re-enable once play-json supports Scala.js 1.0 + // enumeratumPlay, enumeratumArgonautJs, enumeratumArgonautJvm, enumeratumSlick, - enumeratumPlay, enumeratumCirceJvm, enumeratumReactiveMongoBson, enumeratumCatsJvm, @@ -342,7 +343,7 @@ lazy val enumeratumPlayJson = crossProject(JSPlatform, JVMPlatform) .jsSettings(jsTestSettings) .settings( name := "enumeratum-play-json", - version := "1.7.1-SNAPSHOT", + version := Versions.Core.head, crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version), libraryDependencies += { "com.typesafe.play" %%% "play-json" % thePlayJsonVersion(scalaVersion.value) @@ -369,10 +370,11 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu .settings(commonWithPublishSettings) .settings(testSettings) .settings( - version := "1.7.1-SNAPSHOT", + version := Versions.Core.head, crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version), libraryDependencies ++= Seq( - "com.typesafe.play" %% "play" % thePlayVersion(scalaVersion.value), + ("com.typesafe.play" %% "play" % thePlayVersion(scalaVersion.value)) + .exclude("org.scala-lang.modules", "*"), scalaTestPlay(scalaVersion.value) ), libraryDependencies ++= { @@ -390,6 +392,7 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) .dependsOn(enumeratumPlayJsonJvm % "compile->compile;test->test") +// Circe lazy val circeAggregate = aggregateProject("circe", enumeratumCirceJs, enumeratumCirceJvm) lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) @@ -400,7 +403,7 @@ lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) .jsSettings(jsTestSettings) .settings( name := "enumeratum-circe", - version := "1.7.1-SNAPSHOT", + version := Versions.Core.head, libraryDependencies += { "io.circe" %%% "circe-core" % theCirceVersion(scalaVersion.value) }, @@ -425,6 +428,7 @@ lazy val enumeratumCirceJs = enumeratumCirce.js lazy val enumeratumCirceJvm = enumeratumCirce.jvm .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) +// Argonaut lazy val argonautAggregate = aggregateProject("argonaut", enumeratumArgonautJs, enumeratumArgonautJvm) @@ -436,7 +440,7 @@ lazy val enumeratumArgonaut = crossProject(JSPlatform, JVMPlatform) .jsSettings(jsTestSettings) .settings( name := "enumeratum-argonaut", - version := "1.7.1-SNAPSHOT", + version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies += { "io.argonaut" %%% "argonaut" % theArgonautVersion(scalaVersion.value) @@ -456,12 +460,13 @@ lazy val enumeratumArgonautJs = enumeratumArgonaut.js lazy val enumeratumArgonautJvm = enumeratumArgonaut.jvm .configure(configureWithLocal(coreJVM)) +// JSON4S lazy val enumeratumJson4s = Project(id = "enumeratum-json4s", base = file("enumeratum-json4s")) .settings(commonWithPublishSettings) .settings(testSettings) .settings( - version := "1.7.2-SNAPSHOT", + version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies ++= Seq( "org.json4s" %% "json4s-core" % json4sVersion, @@ -477,6 +482,7 @@ lazy val enumeratumJson4s = ) .configure(configureWithLocal(coreJVM)) +// ScalaCheck lazy val scalacheckAggregate = aggregateProject("scalacheck", enumeratumScalacheckJs, enumeratumScalacheckJvm) @@ -488,14 +494,26 @@ lazy val enumeratumScalacheck = crossProject(JSPlatform, JVMPlatform) .jsSettings(jsTestSettings) .settings( name := "enumeratum-scalacheck", - version := "1.7.1-SNAPSHOT", + version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies ++= { Seq( "org.scalacheck" %%% "scalacheck" % theScalacheckVersion(scalaVersion.value), "org.scalatestplus" %%% "scalacheck-1-14" % "3.1.1.1" % Test + ).map( + _.exclude("org.scala-lang.modules", "*") + .exclude("org.scalatest", "*") + .cross(CrossVersion.for3Use2_13) ) }, + libraryDependencies += { + val ver: String = { + if (scalaBinaryVersion.value == "2.11") "1.3.0" + else "2.1.0" + } + + "org.scala-lang.modules" %% "scala-xml" % ver % Test + }, libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -514,6 +532,7 @@ lazy val enumeratumScalacheckJs = enumeratumScalacheck.js lazy val enumeratumScalacheckJvm = enumeratumScalacheck.jvm .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) +// Quill lazy val quillAggregate = aggregateProject( "quill", /*enumeratumQuillJs,*/ enumeratumQuillJvm @@ -528,7 +547,7 @@ lazy val enumeratumQuill = // .jsSettings(jsTestSettings: _*) TODO re-enable once quill supports Scala.js 1.0 */, .settings( name := "enumeratum-quill", - version := "1.7.2-SNAPSHOT", + version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies ++= { Seq( @@ -580,7 +599,7 @@ lazy val enumeratumSlick = .settings(commonWithPublishSettings) .settings(testSettings) .settings( - version := "1.7.1-SNAPSHOT", + version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies ++= Seq( "com.typesafe.slick" %% "slick" % theSlickVersion(scalaVersion.value), @@ -607,7 +626,7 @@ lazy val enumeratumCats = crossProject(JSPlatform, JVMPlatform) .jsSettings(jsTestSettings) .settings( name := "enumeratum-cats", - version := "1.7.1-SNAPSHOT", + version := Versions.Core.head, libraryDependencies += { "org.typelevel" %%% "cats-core" % theCatsVersion(scalaVersion.value) }, @@ -766,7 +785,13 @@ lazy val publishSettings = Seq( val testSettings = { Seq( libraryDependencies += { - "org.scalatest" %%% "scalatest" % scalaTestVersion % Test + val dep = "org.scalatest" %%% "scalatest" % scalaTestVersion % Test + + if (scalaBinaryVersion.value == "3") { + dep.exclude("org.scala-lang.modules", "*") + } else { + dep + } }, doctestGenTests := { val originalValue = doctestGenTests.value @@ -779,9 +804,7 @@ val testSettings = { val jsTestSettings = { Seq( coverageEnabled := false, // Disable until Scala.js 1.0 support is there https://github.com/scoverage/scalac-scoverage-plugin/pull/287 - doctestGenTests := { - Seq.empty - } + doctestGenTests := Seq.empty ) } diff --git a/enumeratum-scalacheck/src/main/scala/enumeratum/ArbitraryInstances.scala b/enumeratum-scalacheck/src/main/scala/enumeratum/ArbitraryInstances.scala index 250c45ec..d51cea08 100644 --- a/enumeratum-scalacheck/src/main/scala/enumeratum/ArbitraryInstances.scala +++ b/enumeratum-scalacheck/src/main/scala/enumeratum/ArbitraryInstances.scala @@ -5,7 +5,7 @@ import org.scalacheck.{Arbitrary, Gen} trait ArbitraryInstances { implicit def arbEnumEntry[EnumType <: EnumEntry](implicit - enum: Enum[EnumType] - ): Arbitrary[EnumType] = Arbitrary(Gen.oneOf(enum.values)) + myEnum: Enum[EnumType] + ): Arbitrary[EnumType] = Arbitrary(Gen.oneOf(myEnum.values)) } diff --git a/project/Versions.scala b/project/Versions.scala index c2ad7984..8786f5d4 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -2,7 +2,7 @@ object Versions { object Core { val stable = "1.7.0" - val head = "1.7.1-SNAPSHOT" + val head = "1.7.2-SNAPSHOT" } object Macros { diff --git a/project/plugins.sbt b/project/plugins.sbt index 1a913c71..d75ced21 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,8 +3,6 @@ resolvers ++= Seq( ) addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") -addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.6.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") @@ -12,3 +10,7 @@ addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.9.9") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") + +addSbtPlugin(("org.scoverage" % "sbt-scoverage" % "2.0.2").exclude("org.scala-lang.modules", "*")) + +addSbtPlugin(("org.scoverage" % "sbt-coveralls" % "1.3.2").exclude("org.scala-lang.modules", "*")) From 1a8942b7a997b552e125239b5914d25b59f0feb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Wed, 7 Sep 2022 20:32:32 +0200 Subject: [PATCH 06/16] Update more modules for Scala3 compatibility --- build.sbt | 74 +++++++++------ .../main/scala/enumeratum/Argonauter.scala | 31 ++++--- .../scala/enumeratum/values/Argonauter.scala | 13 +-- .../values/ArgonautValueEnumSpec.scala | 6 +- .../src/main/scala/enumeratum/Circe.scala | 55 ++++++----- .../main/scala/enumeratum/values/Circe.scala | 28 +++--- .../values/CirceValueEnumSpec.scala | 10 +- .../test/scala/enumeratum/values/Bites.scala | 2 +- .../src/main/scala/enumeratum/Json4s.scala | 20 ++-- .../main/scala/enumeratum/values/Json4s.scala | 60 +++++++----- .../test/scala/enumeratum/Json4sSpec.scala | 7 +- .../values/Json4sValueEnumSpec.scala | 7 +- .../main/scala/enumeratum/EnumFormats.scala | 92 ++++++++++++------- .../scala/enumeratum/values/EnumFormats.scala | 16 ++-- .../enumeratum/values/PlayJsonValueEnum.scala | 26 +++--- .../scala/enumeratum/EnumFormatsSpec.scala | 6 +- .../enumeratum/values/EnumFormatsSpec.scala | 16 ++-- .../values/EnumJsonFormatHelpers.scala | 39 ++++---- .../main/scala/enumeratum/EnumHandler.scala | 82 ++++++++++------- .../scala/enumeratum/values/EnumHandler.scala | 16 ++-- .../values/ReactiveMongoBsonValueEnum.scala | 5 +- .../src/test/scala/enumeratum/Dummy.scala | 1 + .../enumeratum/EnumBsonHandlerSpec.scala | 23 +++-- ...cala => ValueEnumBsonHandlerHelpers.scala} | 57 +++++++----- ...c.scala => ValueEnumBsonHandlerSpec.scala} | 8 +- .../scala/enumeratum/ArbitraryInstances.scala | 4 +- 26 files changed, 402 insertions(+), 302 deletions(-) rename enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/{EnumBsonHandlerHelpers.scala => ValueEnumBsonHandlerHelpers.scala} (67%) rename enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/{EnumBsonHandlerSpec.scala => ValueEnumBsonHandlerSpec.scala} (94%) diff --git a/build.sbt b/build.sbt index f1c573cb..e40e96fb 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ lazy val theScalaVersion = scala_2_12Version lazy val scalaTestVersion = "3.2.9" // Library versions -lazy val reactiveMongoVersion = "1.1.0-RC6" +lazy val reactiveMongoVersion = "1.1.0-RC7-SNAPSHOT" lazy val json4sVersion = "4.0.3" lazy val quillVersion = "4.1.0" @@ -29,7 +29,7 @@ def theDoobieVersion(scalaVersion: String) = def theArgonautVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor >= 11 => "6.2.5" - case Some(_) => "6.3.0" + case Some(_) => "6.3.8" case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Argonaut") @@ -64,7 +64,7 @@ def thePlayJsonVersion(scalaVersion: String) = case Some((2, scalaMajor)) if scalaMajor <= 11 => "2.7.3" // TODO drop 2.11 as play-json 2.7.x supporting Scala.js 1.x is unlikely? - case Some(_) => "2.9.0" + case Some(_) => "2.10.0-RC6" case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for play-json") } @@ -88,6 +88,10 @@ def scalaTestPlay(scalaVersion: String) = CrossVersion.partialVersion(scalaVersi case Some((2, scalaMajor)) if scalaMajor >= 12 => "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test + case Some((3, _)) => + ("org.scalatestplus.play" %% "scalatestplus-play" % "5.1.0" % Test) + .cross(CrossVersion.for3Use2_13) + case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for play-test") } @@ -309,16 +313,26 @@ lazy val coreJVMTests = Project(id = "coreJVMTests", base = file("enumeratum-cor ) .dependsOn(coreJVM, macrosJVM) +lazy val scalaXmlTest = Def.setting[ModuleID] { + val ver: String = { + if (scalaBinaryVersion.value == "2.11") "1.3.0" + else "2.1.0" + } + + "org.scala-lang.modules" %% "scala-xml" % ver % Test +} + lazy val enumeratumReactiveMongoBson = Project(id = "enumeratum-reactivemongo-bson", base = file("enumeratum-reactivemongo-bson")) .settings(commonWithPublishSettings) .settings(testSettings) .settings( - version := "1.7.0", + version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies += { "org.reactivemongo" %% "reactivemongo-bson-api" % reactiveMongoVersion % Provided }, + libraryDependencies += scalaXmlTest.value, libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -332,6 +346,7 @@ lazy val enumeratumReactiveMongoBson = ) .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) +// Play-JSON lazy val playJsonAggregate = aggregateProject("play-json", enumeratumPlayJsonJs, enumeratumPlayJsonJvm) @@ -342,12 +357,17 @@ lazy val enumeratumPlayJson = crossProject(JSPlatform, JVMPlatform) .settings(testSettings) .jsSettings(jsTestSettings) .settings( - name := "enumeratum-play-json", - version := Versions.Core.head, - crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version), - libraryDependencies += { - "com.typesafe.play" %%% "play-json" % thePlayJsonVersion(scalaVersion.value) - }, + name := "enumeratum-play-json", + version := Versions.Core.head, + crossScalaVersions := Seq( + scala_2_12Version, + scala_2_13Version, + scala_3Version + ), + libraryDependencies ++= Seq( + "com.typesafe.play" %%% "play-json" % thePlayJsonVersion(scalaVersion.value), + scalaXmlTest.value + ), libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -366,15 +386,17 @@ lazy val enumeratumPlayJsonJs = enumeratumPlayJson.js lazy val enumeratumPlayJsonJvm = enumeratumPlayJson.jvm .configure(configureWithLocal(coreJVM, "compile->compile;test->test")) +// Play lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratum-play")) .settings(commonWithPublishSettings) .settings(testSettings) .settings( version := Versions.Core.head, - crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version), + crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version, scala_3Version), libraryDependencies ++= Seq( ("com.typesafe.play" %% "play" % thePlayVersion(scalaVersion.value)) - .exclude("org.scala-lang.modules", "*"), + .exclude("org.scala-lang.modules", "*") + .cross(CrossVersion.for3Use2_13), scalaTestPlay(scalaVersion.value) ), libraryDependencies ++= { @@ -404,9 +426,10 @@ lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) .settings( name := "enumeratum-circe", version := Versions.Core.head, - libraryDependencies += { - "io.circe" %%% "circe-core" % theCirceVersion(scalaVersion.value) - }, + libraryDependencies ++= Seq( + "io.circe" %%% "circe-core" % theCirceVersion(scalaVersion.value), + scalaXmlTest.value + ), libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -442,9 +465,10 @@ lazy val enumeratumArgonaut = crossProject(JSPlatform, JVMPlatform) name := "enumeratum-argonaut", version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, - libraryDependencies += { - "io.argonaut" %%% "argonaut" % theArgonautVersion(scalaVersion.value) - }, + libraryDependencies ++= Seq( + "io.argonaut" %%% "argonaut" % theArgonautVersion(scalaVersion.value), + scalaXmlTest.value + ), libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -470,7 +494,8 @@ lazy val enumeratumJson4s = crossScalaVersions := scalaVersionsAll, libraryDependencies ++= Seq( "org.json4s" %% "json4s-core" % json4sVersion, - "org.json4s" %% "json4s-native" % json4sVersion % Test + "org.json4s" %% "json4s-native" % json4sVersion % Test, + scalaXmlTest.value ), libraryDependencies ++= { if (useLocalVersion) { @@ -506,14 +531,7 @@ lazy val enumeratumScalacheck = crossProject(JSPlatform, JVMPlatform) .cross(CrossVersion.for3Use2_13) ) }, - libraryDependencies += { - val ver: String = { - if (scalaBinaryVersion.value == "2.11") "1.3.0" - else "2.1.0" - } - - "org.scala-lang.modules" %% "scala-xml" % ver % Test - }, + libraryDependencies += scalaXmlTest.value, libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -701,7 +719,7 @@ lazy val compilerSettings = Seq( val base = { if (scalaBinaryVersion.value == "3") { - minimal + minimal :+ "-deprecation" } else { minimal ++ Seq( // "-Ywarn-adapted-args", diff --git a/enumeratum-argonaut/src/main/scala/enumeratum/Argonauter.scala b/enumeratum-argonaut/src/main/scala/enumeratum/Argonauter.scala index 8a99ac20..5cc902e2 100644 --- a/enumeratum-argonaut/src/main/scala/enumeratum/Argonauter.scala +++ b/enumeratum-argonaut/src/main/scala/enumeratum/Argonauter.scala @@ -1,7 +1,6 @@ package enumeratum -import argonaut._ -import Argonaut._ +import argonaut._, Argonaut._ /** Created by alonsodomin on 14/10/2016. */ @@ -10,33 +9,39 @@ object Argonauter { private def encoder0[A <: EnumEntry](f: A => String): EncodeJson[A] = stringEncoder.contramap(f) - def encoder[A <: EnumEntry](enum: Enum[A]): EncodeJson[A] = + def encoder[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): EncodeJson[A] = encoder0[A](_.entryName) - def encoderLowercase[A <: EnumEntry](enum: Enum[A]): EncodeJson[A] = + def encoderLowercase[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): EncodeJson[A] = encoder0[A](_.entryName.toLowerCase) - def encoderUppercase[A <: EnumEntry](enum: Enum[A]): EncodeJson[A] = + def encoderUppercase[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): EncodeJson[A] = encoder0[A](_.entryName.toUpperCase) - private def decoder0[A <: EnumEntry](enum: Enum[A])(f: String => Option[A]): DecodeJson[A] = + private def decoder0[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + )(f: String => Option[A]): DecodeJson[A] = DecodeJson { cursor => stringDecoder(cursor).flatMap { enumStr => f(enumStr) match { case Some(a) => okResult(a) - case _ => failResult(s"'$enumStr' is not a member of enum $enum", cursor.history) + case _ => failResult(s"'$enumStr' is not a member of enum $e", cursor.history) } } } - def decoder[A <: EnumEntry](enum: Enum[A]): DecodeJson[A] = - decoder0(enum)(enum.withNameOption) + def decoder[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): DecodeJson[A] = + decoder0(e)(e.withNameOption) - def decoderLowercaseOnly[A <: EnumEntry](enum: Enum[A]): DecodeJson[A] = - decoder0(enum)(enum.withNameLowercaseOnlyOption) + def decoderLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): DecodeJson[A] = + decoder0(e)(e.withNameLowercaseOnlyOption) - def decoderUppercaseOnly[A <: EnumEntry](enum: Enum[A]): DecodeJson[A] = - decoder0(enum)(enum.withNameUppercaseOnlyOption) + def decoderUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): DecodeJson[A] = + decoder0(e)(e.withNameUppercaseOnlyOption) private val stringEncoder = implicitly[EncodeJson[String]] private val stringDecoder = implicitly[DecodeJson[String]] diff --git a/enumeratum-argonaut/src/main/scala/enumeratum/values/Argonauter.scala b/enumeratum-argonaut/src/main/scala/enumeratum/values/Argonauter.scala index 40b70a0b..a68cc57b 100644 --- a/enumeratum-argonaut/src/main/scala/enumeratum/values/Argonauter.scala +++ b/enumeratum-argonaut/src/main/scala/enumeratum/values/Argonauter.scala @@ -1,30 +1,31 @@ package enumeratum.values -import argonaut._ -import Argonaut._ +import argonaut._, Argonaut._ /** Created by alonsodomin on 14/10/2016. */ object Argonauter { def encoder[ValueType: EncodeJson, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] ): EncodeJson[EntryType] = { val encodeValue = implicitly[EncodeJson[ValueType]] + EncodeJson { entry => encodeValue(entry.value) } } def decoder[ValueType: DecodeJson, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] ): DecodeJson[EntryType] = { val decodeValue = implicitly[DecodeJson[ValueType]] + DecodeJson { cursor => decodeValue(cursor).flatMap { value => - enum.withValueOpt(value) match { + e.withValueOpt(value) match { case Some(entry) => okResult(entry) - case _ => failResult(s"$value is not a member of enum $enum", cursor.history) + case _ => failResult(s"$value is not a member of enum $e", cursor.history) } } } diff --git a/enumeratum-argonaut/src/test/scala/enumeratum/values/ArgonautValueEnumSpec.scala b/enumeratum-argonaut/src/test/scala/enumeratum/values/ArgonautValueEnumSpec.scala index 2a39a94c..0d96ce95 100644 --- a/enumeratum-argonaut/src/test/scala/enumeratum/values/ArgonautValueEnumSpec.scala +++ b/enumeratum-argonaut/src/test/scala/enumeratum/values/ArgonautValueEnumSpec.scala @@ -20,13 +20,13 @@ class ArgonautValueEnumSpec extends AnyFunSpec with Matchers { ValueType ]: EncodeJson: DecodeJson]( enumKind: String, - enum: ValueEnum[ValueType, EntryType] with ArgonautValueEnum[ValueType, EntryType] + e: ValueEnum[ValueType, EntryType] with ArgonautValueEnum[ValueType, EntryType] ): Unit = { describe(enumKind) { describe("from JSON") { it("should work") { - enum.values.foreach { entry => + e.values.foreach { entry => entry.asJson shouldBe entry.value.asJson } } @@ -34,7 +34,7 @@ class ArgonautValueEnumSpec extends AnyFunSpec with Matchers { describe("from JSON") { it("should parse members when passing proper JSON values") { - enum.values.foreach { entry => + e.values.foreach { entry => entry.asJson.as[EntryType] shouldBe okResult(entry) } } diff --git a/enumeratum-circe/src/main/scala/enumeratum/Circe.scala b/enumeratum-circe/src/main/scala/enumeratum/Circe.scala index 261287f9..b273af26 100644 --- a/enumeratum-circe/src/main/scala/enumeratum/Circe.scala +++ b/enumeratum-circe/src/main/scala/enumeratum/Circe.scala @@ -1,6 +1,7 @@ package enumeratum import cats.syntax.either._ + import io.circe.Decoder.Result import io.circe.{Encoder, Decoder, Json, HCursor, DecodingFailure, KeyEncoder, KeyDecoder} @@ -12,17 +13,18 @@ object Circe { /** Returns an Encoder for the given enum */ - def encoder[A <: EnumEntry](enum: Enum[A]): Encoder[A] = new Encoder[A] { - final def apply(a: A): Json = stringEncoder.apply(a.entryName) - } + def encoder[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Encoder[A] = + new Encoder[A] { + final def apply(a: A): Json = stringEncoder.apply(a.entryName) + } - def encoderLowercase[A <: EnumEntry](enum: Enum[A]): Encoder[A] = + def encoderLowercase[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Encoder[A] = new Encoder[A] { final def apply(a: A): Json = stringEncoder.apply(a.entryName.toLowerCase) } - def encoderUppercase[A <: EnumEntry](enum: Enum[A]): Encoder[A] = + def encoderUppercase[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Encoder[A] = new Encoder[A] { final def apply(a: A): Json = stringEncoder.apply(a.entryName.toUpperCase) @@ -30,61 +32,64 @@ object Circe { /** Returns a Decoder for the given enum */ - def decoder[A <: EnumEntry](enum: Enum[A]): Decoder[A] = new Decoder[A] { - final def apply(c: HCursor): Result[A] = stringDecoder.apply(c).flatMap { s => - val maybeMember = enum.withNameOption(s) - maybeMember match { - case Some(member) => Right(member) - case _ => - Left(DecodingFailure(s"'$s' is not a member of enum $enum", c.history)) + def decoder[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Decoder[A] = + new Decoder[A] { + final def apply(c: HCursor): Result[A] = stringDecoder.apply(c).flatMap { s => + e.withNameOption(s) match { + case Some(member) => Right(member) + case _ => + Left(DecodingFailure(s"'$s' is not a member of enum $e", c.history)) + } } } - } - def decoderLowercaseOnly[A <: EnumEntry](enum: Enum[A]): Decoder[A] = + def decoderLowercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Decoder[A] = new Decoder[A] { final def apply(c: HCursor): Result[A] = stringDecoder.apply(c).flatMap { s => - val maybeMember = enum.withNameLowercaseOnlyOption(s) + val maybeMember = e.withNameLowercaseOnlyOption(s) maybeMember match { case Some(member) => Right(member) case _ => - Left(DecodingFailure(s"'$s' is not a member of enum $enum", c.history)) + Left(DecodingFailure(s"'$s' is not a member of enum $e", c.history)) } } } - def decoderUppercaseOnly[A <: EnumEntry](enum: Enum[A]): Decoder[A] = + def decoderUppercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Decoder[A] = new Decoder[A] { final def apply(c: HCursor): Result[A] = stringDecoder.apply(c).flatMap { s => - val maybeMember = enum.withNameUppercaseOnlyOption(s) + val maybeMember = e.withNameUppercaseOnlyOption(s) maybeMember match { case Some(member) => Right(member) case _ => - Left(DecodingFailure(s"'$s' is not a member of enum $enum", c.history)) + Left(DecodingFailure(s"'$s' is not a member of enum $e", c.history)) } } } - def decodeCaseInsensitive[A <: EnumEntry](enum: Enum[A]): Decoder[A] = + def decodeCaseInsensitive[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): Decoder[A] = new Decoder[A] { final def apply(c: HCursor): Result[A] = stringDecoder.apply(c).flatMap { s => - val maybeMember = enum.withNameInsensitiveOption(s) + val maybeMember = e.withNameInsensitiveOption(s) maybeMember match { case Some(member) => Right(member) case _ => - Left(DecodingFailure(s"'$s' is not a member of enum $enum", c.history)) + Left(DecodingFailure(s"'$s' is not a member of enum $e", c.history)) } } } /** Returns a KeyEncoder for the given enum */ - def keyEncoder[A <: EnumEntry](enum: Enum[A]): KeyEncoder[A] = KeyEncoder.instance(_.entryName) + def keyEncoder[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): KeyEncoder[A] = + KeyEncoder.instance(_.entryName) /** Returns a KeyDecoder for the given enum */ - def keyDecoder[A <: EnumEntry](enum: Enum[A]): KeyDecoder[A] = - KeyDecoder.instance(enum.withNameOption) + def keyDecoder[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): KeyDecoder[A] = + KeyDecoder.instance(e.withNameOption) private val stringEncoder = implicitly[Encoder[String]] private val stringDecoder = implicitly[Decoder[String]] diff --git a/enumeratum-circe/src/main/scala/enumeratum/values/Circe.scala b/enumeratum-circe/src/main/scala/enumeratum/values/Circe.scala index 3e545b40..779059f8 100644 --- a/enumeratum-circe/src/main/scala/enumeratum/values/Circe.scala +++ b/enumeratum-circe/src/main/scala/enumeratum/values/Circe.scala @@ -1,6 +1,7 @@ package enumeratum.values import cats.syntax.either._ + import io.circe.Decoder.Result import io.circe._ @@ -13,41 +14,40 @@ object Circe { /** Returns an Encoder for the provided ValueEnum */ def encoder[ValueType: Encoder, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] ): Encoder[EntryType] = { new Encoder[EntryType] { - private val valueEncoder = implicitly[Encoder[ValueType]] - def apply(a: EntryType): Json = valueEncoder.apply(a.value) + private val valueEncoder = implicitly[Encoder[ValueType]] + + def apply(a: EntryType): Json = valueEncoder(a.value) } } /** Returns a Decoder for the provided ValueEnum */ def decoder[ValueType: Decoder, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] ): Decoder[EntryType] = { new Decoder[EntryType] { private val valueDecoder = implicitly[Decoder[ValueType]] + def apply(c: HCursor): Result[EntryType] = - valueDecoder.apply(c).flatMap { v => - val maybeBound: Option[EntryType] = enum.withValueOpt(v) - maybeBound match { + valueDecoder(c).flatMap { v => + e.withValueOpt(v) match { case Some(member) => Right(member) case _ => - Left(DecodingFailure(s"$v is not a member of enum $enum", c.history)) + Left(DecodingFailure(s"$v is not a member of enum $e", c.history)) } } } } def keyEncoder[EntryType <: ValueEnumEntry[String]]( - enum: ValueEnum[String, EntryType] - ): KeyEncoder[EntryType] = - KeyEncoder.instance(_.value) + @deprecatedName(Symbol("enum")) e: ValueEnum[String, EntryType] + ): KeyEncoder[EntryType] = KeyEncoder.instance(_.value) def keyDecoder[EntryType <: ValueEnumEntry[String]]( - enum: ValueEnum[String, EntryType] - ): KeyDecoder[EntryType] = - KeyDecoder.instance(enum.withValueOpt) + @deprecatedName(Symbol("enum")) e: ValueEnum[String, EntryType] + ): KeyDecoder[EntryType] = KeyDecoder.instance(e.withValueOpt) } diff --git a/enumeratum-circe/src/test/scala/enumeratum/values/CirceValueEnumSpec.scala b/enumeratum-circe/src/test/scala/enumeratum/values/CirceValueEnumSpec.scala index e76a04f1..e205eae2 100644 --- a/enumeratum-circe/src/test/scala/enumeratum/values/CirceValueEnumSpec.scala +++ b/enumeratum-circe/src/test/scala/enumeratum/values/CirceValueEnumSpec.scala @@ -26,14 +26,14 @@ class CirceValueEnumSpec extends AnyFunSpec with Matchers { ValueType ]: Encoder: Decoder]( enumKind: String, - enum: ValueEnum[ValueType, EntryType] with CirceValueEnum[ValueType, EntryType] + myEnum: ValueEnum[ValueType, EntryType] with CirceValueEnum[ValueType, EntryType] ): Unit = { describe(enumKind) { describe("to JSON") { it("should work") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => entry.asJson shouldBe entry.value.asJson } } @@ -43,7 +43,7 @@ class CirceValueEnumSpec extends AnyFunSpec with Matchers { describe("from Json") { it("should parse to members when given proper JSON") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => entry.value.asJson.as[EntryType] shouldBe Right(entry) } } @@ -64,12 +64,12 @@ class CirceValueEnumSpec extends AnyFunSpec with Matchers { private def testCirceKeyEnum[EntryType <: ValueEnumEntry[String]: KeyEncoder: KeyDecoder]( enumKind: String, - enum: ValueEnum[String, EntryType] with CirceValueEnum[String, EntryType] + myEnum: ValueEnum[String, EntryType] with CirceValueEnum[String, EntryType] ): Unit = { describe(s"$enumKind as Key") { describe("to JSON") { it("should work") { - val map = enum.values.toStream.zip(Stream.from(1)).toMap + val map = myEnum.values.toStream.zip(Stream.from(1)).toMap map.asJson.as[Map[EntryType, Int]] shouldBe Right(map) } } diff --git a/enumeratum-core/src/test/scala/enumeratum/values/Bites.scala b/enumeratum-core/src/test/scala/enumeratum/values/Bites.scala index 663d573d..20e18f0c 100644 --- a/enumeratum-core/src/test/scala/enumeratum/values/Bites.scala +++ b/enumeratum-core/src/test/scala/enumeratum/values/Bites.scala @@ -7,7 +7,7 @@ package enumeratum.values sealed abstract class Bites(val value: Byte) extends ByteEnumEntry object Bites extends ByteEnum[Bites] { - lazy val values = findValues + val values = findValues case object OneByte extends Bites(1) case object TwoByte extends Bites(2) diff --git a/enumeratum-json4s/src/main/scala/enumeratum/Json4s.scala b/enumeratum-json4s/src/main/scala/enumeratum/Json4s.scala index 6a862705..a4dad23e 100644 --- a/enumeratum-json4s/src/main/scala/enumeratum/Json4s.scala +++ b/enumeratum-json4s/src/main/scala/enumeratum/Json4s.scala @@ -30,14 +30,16 @@ object Json4s { * res1: Boolean = true * }}} * - * @param enum + * @param e * the enum you want to generate a Json4s serialiser for */ - def serializer[A <: EnumEntry: Manifest](enum: Enum[A]): CustomSerializer[A] = + def serializer[A <: EnumEntry: Manifest]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): CustomSerializer[A] = new CustomSerializer[A](_ => ( { - case JString(s) if enum.withNameOption(s).isDefined => enum.withName(s) + case JString(s) if e.withNameOption(s).isDefined => e.withName(s) }, { case x: A => JString(x.entryName) @@ -71,18 +73,18 @@ object Json4s { * res1: Boolean = true * }}} * - * @param enum + * @param e * the enum you want to generate a Json4s key serialiser for */ - def keySerializer[A <: EnumEntry: Manifest](enum: Enum[A]): CustomKeySerializer[A] = + def keySerializer[A <: EnumEntry: Manifest]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): CustomKeySerializer[A] = new CustomKeySerializer[A](_ => ( { - case s: String if enum.withNameOption(s).isDefined => enum.withName(s) + case s: String if e.withNameOption(s).isDefined => e.withName(s) }, - { case x: A => - x.entryName - } + { case x: A => x.entryName } ) ) } diff --git a/enumeratum-json4s/src/main/scala/enumeratum/values/Json4s.scala b/enumeratum-json4s/src/main/scala/enumeratum/values/Json4s.scala index 5d2c8926..b90c0966 100644 --- a/enumeratum-json4s/src/main/scala/enumeratum/values/Json4s.scala +++ b/enumeratum-json4s/src/main/scala/enumeratum/values/Json4s.scala @@ -27,14 +27,16 @@ import org.json4s.JsonAST.{JInt, JLong, JString} @SuppressWarnings(Array("org.wartremover.warts.Any")) object Json4s { - def serializer[A <: IntEnumEntry: Manifest](enum: IntEnum[A]): CustomSerializer[A] = + def serializer[A <: IntEnumEntry: Manifest]( + @deprecatedName(Symbol("enum")) e: IntEnum[A] + ): CustomSerializer[A] = new CustomSerializer[A](_ => ( { - case JInt(i) if inBounds(i, Int.MaxValue) && enum.withValueOpt(i.toInt).isDefined => - enum.withValue(i.toInt) - case JLong(i) if inBounds(i, Int.MaxValue) && enum.withValueOpt(i.toInt).isDefined => - enum.withValue(i.toInt) + case JInt(i) if inBounds(i, Int.MaxValue) && e.withValueOpt(i.toInt).isDefined => + e.withValue(i.toInt) + case JLong(i) if inBounds(i, Int.MaxValue) && e.withValueOpt(i.toInt).isDefined => + e.withValue(i.toInt) }, { case x: A => JLong(x.value.toLong) @@ -42,13 +44,15 @@ object Json4s { ) ) - def serializer[A <: LongEnumEntry: Manifest](enum: LongEnum[A]): CustomSerializer[A] = + def serializer[A <: LongEnumEntry: Manifest]( + @deprecatedName(Symbol("enum")) e: LongEnum[A] + ): CustomSerializer[A] = new CustomSerializer[A](_ => ( { - case JInt(i) if isValidLong(i) && enum.withValueOpt(i.toLong).isDefined => - enum.withValue(i.toLong) - case JLong(i) if enum.withValueOpt(i).isDefined => enum.withValue(i) + case JInt(i) if isValidLong(i) && e.withValueOpt(i.toLong).isDefined => + e.withValue(i.toLong) + case JLong(i) if e.withValueOpt(i).isDefined => e.withValue(i) }, { case x: A => JLong(x.value) @@ -56,14 +60,16 @@ object Json4s { ) ) - def serializer[A <: ShortEnumEntry: Manifest](enum: ShortEnum[A]): CustomSerializer[A] = + def serializer[A <: ShortEnumEntry: Manifest]( + @deprecatedName(Symbol("enum")) e: ShortEnum[A] + ): CustomSerializer[A] = new CustomSerializer[A](_ => ( { - case JInt(i) if inBounds(i, Short.MaxValue) && enum.withValueOpt(i.toShort).isDefined => - enum.withValue(i.toShort) - case JLong(i) if inBounds(i, Short.MaxValue) && enum.withValueOpt(i.toShort).isDefined => - enum.withValue(i.toShort) + case JInt(i) if inBounds(i, Short.MaxValue) && e.withValueOpt(i.toShort).isDefined => + e.withValue(i.toShort) + case JLong(i) if inBounds(i, Short.MaxValue) && e.withValueOpt(i.toShort).isDefined => + e.withValue(i.toShort) }, { case x: A => JLong(x.value.toLong) @@ -71,11 +77,13 @@ object Json4s { ) ) - def serializer[A <: StringEnumEntry: Manifest](enum: StringEnum[A]): CustomSerializer[A] = + def serializer[A <: StringEnumEntry: Manifest]( + @deprecatedName(Symbol("enum")) e: StringEnum[A] + ): CustomSerializer[A] = new CustomSerializer[A](_ => ( { - case JString(s) if enum.withValueOpt(s).isDefined => enum.withValue(s) + case JString(s) if e.withValueOpt(s).isDefined => e.withValue(s) }, { case x: A => JString(x.value) @@ -83,14 +91,16 @@ object Json4s { ) ) - def serializer[A <: ByteEnumEntry: Manifest](enum: ByteEnum[A]): CustomSerializer[A] = + def serializer[A <: ByteEnumEntry: Manifest]( + @deprecatedName(Symbol("enum")) e: ByteEnum[A] + ): CustomSerializer[A] = new CustomSerializer[A](_ => ( { - case JInt(i) if inBounds(i, Byte.MaxValue) && enum.withValueOpt(i.toByte).isDefined => - enum.withValue(i.toByte) - case JLong(i) if inBounds(i, Byte.MaxValue) && enum.withValueOpt(i.toByte).isDefined => - enum.withValue(i.toByte) + case JInt(i) if inBounds(i, Byte.MaxValue) && e.withValueOpt(i.toByte).isDefined => + e.withValue(i.toByte) + case JLong(i) if inBounds(i, Byte.MaxValue) && e.withValueOpt(i.toByte).isDefined => + e.withValue(i.toByte) }, { case x: A => JLong(x.value.toLong) @@ -98,12 +108,14 @@ object Json4s { ) ) - def serializer[A <: CharEnumEntry: Manifest](enum: CharEnum[A]): CustomSerializer[A] = + def serializer[A <: CharEnumEntry: Manifest]( + @deprecatedName(Symbol("enum")) e: CharEnum[A] + ): CustomSerializer[A] = new CustomSerializer[A](_ => ( { - case JString(s) if s.length == 1 && enum.withValueOpt(s.head).isDefined => - enum.withValue(s.head) + case JString(s) if s.length == 1 && e.withValueOpt(s.head).isDefined => + e.withValue(s.head) }, { case x: A => JString(x.value.toString) diff --git a/enumeratum-json4s/src/test/scala/enumeratum/Json4sSpec.scala b/enumeratum-json4s/src/test/scala/enumeratum/Json4sSpec.scala index ccb26ecc..74d37553 100644 --- a/enumeratum-json4s/src/test/scala/enumeratum/Json4sSpec.scala +++ b/enumeratum-json4s/src/test/scala/enumeratum/Json4sSpec.scala @@ -1,13 +1,14 @@ package enumeratum -import org.json4s.{DefaultFormats, MappingException} +import org.json4s.{DefaultFormats, Formats, MappingException} import org.json4s.native.Serialization + import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class Json4sSpec extends AnyFunSpec with Matchers { +final class Json4sSpec extends AnyFunSpec with Matchers { - implicit val formats = + implicit val formats: Formats = DefaultFormats + Json4s.serializer(TrafficLight) + Json4s.keySerializer(TrafficLight) case class Data(tr: TrafficLight) diff --git a/enumeratum-json4s/src/test/scala/enumeratum/values/Json4sValueEnumSpec.scala b/enumeratum-json4s/src/test/scala/enumeratum/values/Json4sValueEnumSpec.scala index 961c60fd..cef6da7e 100644 --- a/enumeratum-json4s/src/test/scala/enumeratum/values/Json4sValueEnumSpec.scala +++ b/enumeratum-json4s/src/test/scala/enumeratum/values/Json4sValueEnumSpec.scala @@ -1,14 +1,15 @@ package enumeratum.values -import org.json4s.{DefaultFormats, JObject, MappingException} +import org.json4s.{DefaultFormats, Formats, JObject, MappingException} import org.json4s.JsonDSL._ import org.json4s.native.Serialization + import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class Json4sValueEnumSpec extends AnyFunSpec with Matchers { +final class Json4sValueEnumSpec extends AnyFunSpec with Matchers { - implicit val formats = DefaultFormats + + implicit val formats: Formats = DefaultFormats + Json4s.serializer(Json4sMediaType) + Json4s.serializer(Json4sJsonLibs) + Json4s.serializer(Json4sDevice) + Json4s.serializer(Json4sHttpMethod) + Json4s.serializer(Json4sBool) + Json4s.serializer(Json4sDigits) diff --git a/enumeratum-play-json/src/main/scala/enumeratum/EnumFormats.scala b/enumeratum-play-json/src/main/scala/enumeratum/EnumFormats.scala index b08c4ad8..4ce4c83a 100644 --- a/enumeratum-play-json/src/main/scala/enumeratum/EnumFormats.scala +++ b/enumeratum-play-json/src/main/scala/enumeratum/EnumFormats.scala @@ -8,109 +8,131 @@ object EnumFormats { /** Returns an Json Reads for a given enum [[Enum]] * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false */ - def reads[A <: EnumEntry](enum: Enum[A], insensitive: Boolean = false): Reads[A] = - readsAndExtracts[A](enum) { s => - if (insensitive) enum.withNameInsensitiveOption(s) - else enum.withNameOption(s) + def reads[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A], + insensitive: Boolean = false + ): Reads[A] = + readsAndExtracts[A](e) { s => + if (insensitive) e.withNameInsensitiveOption(s) + else e.withNameOption(s) } - def readsLowercaseOnly[A <: EnumEntry](enum: Enum[A]): Reads[A] = - readsAndExtracts[A](enum)(enum.withNameLowercaseOnlyOption) + def readsLowercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Reads[A] = + readsAndExtracts[A](e)(e.withNameLowercaseOnlyOption) - def readsUppercaseOnly[A <: EnumEntry](enum: Enum[A]): Reads[A] = - readsAndExtracts[A](enum)(enum.withNameUppercaseOnlyOption) + def readsUppercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Reads[A] = + readsAndExtracts[A](e)(e.withNameUppercaseOnlyOption) - def keyReads[A <: EnumEntry](enum: Enum[A], insensitive: Boolean = false): KeyReads[A] = - readsKeyAndExtracts[A](enum) { s => - if (insensitive) enum.withNameInsensitiveOption(s) - else enum.withNameOption(s) + def keyReads[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A], + insensitive: Boolean = false + ): KeyReads[A] = + readsKeyAndExtracts[A](e) { s => + if (insensitive) e.withNameInsensitiveOption(s) + else e.withNameOption(s) } - def keyReadsLowercaseOnly[A <: EnumEntry](enum: Enum[A]): KeyReads[A] = - readsKeyAndExtracts[A](enum)(enum.withNameLowercaseOnlyOption) + def keyReadsLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): KeyReads[A] = + readsKeyAndExtracts[A](e)(e.withNameLowercaseOnlyOption) - def keyReadsUppercaseOnly[A <: EnumEntry](enum: Enum[A]): KeyReads[A] = - readsKeyAndExtracts[A](enum)(enum.withNameUppercaseOnlyOption) + def keyReadsUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): KeyReads[A] = + readsKeyAndExtracts[A](e)(e.withNameUppercaseOnlyOption) /** Returns a Json writes for a given enum [[Enum]] */ - def writes[A <: EnumEntry](enum: Enum[A]): Writes[A] = Writes[A] { e => - JsString(e.entryName) + def writes[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Writes[A] = Writes[A] { + e => + JsString(e.entryName) } /** Returns a Json writes for a given enum [[Enum]] and transforms it to lower case */ - def writesLowercaseOnly[A <: EnumEntry](enum: Enum[A]): Writes[A] = + def writesLowercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Writes[A] = Writes[A] { e => JsString(e.entryName.toLowerCase) } /** Returns a Json writes for a given enum [[Enum]] and transforms it to upper case */ - def writesUppercaseOnly[A <: EnumEntry](enum: Enum[A]): Writes[A] = + def writesUppercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Writes[A] = Writes[A] { e => JsString(e.entryName.toUpperCase) } /** Returns a Json key writes for a given enum [[Enum]] */ - def keyWrites[A <: EnumEntry](enum: Enum[A]): KeyWrites[A] = + def keyWrites[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): KeyWrites[A] = new KeyWrites[A] { def writeKey(e: A): String = e.entryName } /** Returns a Json key writes for a given enum [[Enum]] and transforms it to lower case */ - def keyWritesLowercaseOnly[A <: EnumEntry](enum: Enum[A]): KeyWrites[A] = + def keyWritesLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): KeyWrites[A] = new KeyWrites[A] { def writeKey(e: A) = e.entryName.toLowerCase } /** Returns a Json key writes for a given enum [[Enum]] and transforms it to upper case */ - def keyWritesUppercaseOnly[A <: EnumEntry](enum: Enum[A]): KeyWrites[A] = + def keyWritesUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): KeyWrites[A] = new KeyWrites[A] { def writeKey(e: A) = e.entryName.toUpperCase } /** Returns a Json format for a given enum [[Enum]] * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false */ - def formats[A <: EnumEntry](enum: Enum[A], insensitive: Boolean = false): Format[A] = { - Format(reads(enum, insensitive), writes(enum)) + def formats[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A], + insensitive: Boolean = false + ): Format[A] = { + Format(reads(e, insensitive), writes(e)) } /** Returns a Json format for a given enum [[Enum]] for handling lower case transformations * - * @param enum + * @param e * The enum */ - def formatsLowerCaseOnly[A <: EnumEntry](enum: Enum[A]): Format[A] = { - Format(readsLowercaseOnly(enum), writesLowercaseOnly(enum)) + def formatsLowerCaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): Format[A] = { + Format(readsLowercaseOnly(e), writesLowercaseOnly(e)) } /** Returns a Json format for a given enum [[Enum]] for handling upper case transformations * - * @param enum + * @param e * The enum */ - def formatsUppercaseOnly[A <: EnumEntry](enum: Enum[A]): Format[A] = { - Format(readsUppercaseOnly(enum), writesUppercaseOnly(enum)) + def formatsUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): Format[A] = { + Format(readsUppercaseOnly(e), writesUppercaseOnly(e)) } // --- private def readsAndExtracts[A <: EnumEntry]( - enum: Enum[A] + @deprecatedName(Symbol("enum")) e: Enum[A] )(extract: String => Option[A]): Reads[A] = Reads[A] { case JsString(s) => extract(s) match { @@ -122,7 +144,7 @@ object EnumFormats { } private def readsKeyAndExtracts[A <: EnumEntry]( - enum: Enum[A] + @deprecatedName(Symbol("enum")) e: Enum[A] )(extract: String => Option[A]): KeyReads[A] = new KeyReads[A] { def readKey(s: String): JsResult[A] = extract(s) match { case Some(obj) => JsSuccess(obj) diff --git a/enumeratum-play-json/src/main/scala/enumeratum/values/EnumFormats.scala b/enumeratum-play-json/src/main/scala/enumeratum/values/EnumFormats.scala index a1ac8f0c..745fbce3 100644 --- a/enumeratum-play-json/src/main/scala/enumeratum/values/EnumFormats.scala +++ b/enumeratum-play-json/src/main/scala/enumeratum/values/EnumFormats.scala @@ -12,15 +12,14 @@ object EnumFormats { * type */ def reads[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseReads: Reads[ValueType] ): Reads[EntryType] = new Reads[EntryType] { def reads(json: JsValue): JsResult[EntryType] = baseReads.reads(json).flatMap { s => - val maybeBound = enum.withValueOpt(s) - maybeBound match { + e.withValueOpt(s) match { case Some(obj) => JsSuccess(obj) case None => JsError("error.expected.validenumvalue") } @@ -31,21 +30,18 @@ object EnumFormats { * value type */ def writes[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseWrites: Writes[ValueType] - ): Writes[EntryType] = - new Writes[EntryType] { - def writes(o: EntryType): JsValue = baseWrites.writes(o.value) - } + ): Writes[EntryType] = Writes[EntryType] { o => baseWrites.writes(o.value) } /** Returns a Formats for the provided ValueEnum based on the given base Reads and Writes for the * Enum's value type */ def formats[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseReads: Reads[ValueType], baseWrites: Writes[ValueType]): Format[EntryType] = { - Format(reads(enum), writes(enum)) + Format(reads(e), writes(e)) } /** Format for Char diff --git a/enumeratum-play-json/src/main/scala/enumeratum/values/PlayJsonValueEnum.scala b/enumeratum-play-json/src/main/scala/enumeratum/values/PlayJsonValueEnum.scala index f22200ca..e07354ed 100644 --- a/enumeratum-play-json/src/main/scala/enumeratum/values/PlayJsonValueEnum.scala +++ b/enumeratum-play-json/src/main/scala/enumeratum/values/PlayJsonValueEnum.scala @@ -8,7 +8,7 @@ import EnumFormats.charFormat * Copyright 2016 */ trait PlayJsonValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { - enum: ValueEnum[ValueType, EntryType] => + _enum: ValueEnum[ValueType, EntryType] => /** Implicit JSON format for the entries of this enum */ @@ -19,41 +19,41 @@ trait PlayJsonValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { /** Enum implementation for Int enum members that contains an implicit Play JSON Format */ trait IntPlayJsonValueEnum[EntryType <: IntEnumEntry] extends PlayJsonValueEnum[Int, EntryType] { - this: IntEnum[EntryType] => - implicit val format: Format[EntryType] = EnumFormats.formats(this) + self: IntEnum[EntryType] => + implicit val format: Format[EntryType] = EnumFormats.formats(self) } /** Enum implementation for Long enum members that contains an implicit Play JSON Format */ trait LongPlayJsonValueEnum[EntryType <: LongEnumEntry] extends PlayJsonValueEnum[Long, EntryType] { - this: LongEnum[EntryType] => - implicit val format: Format[EntryType] = EnumFormats.formats(this) + self: LongEnum[EntryType] => + implicit val format: Format[EntryType] = EnumFormats.formats(self) } /** Enum implementation for Short enum members that contains an implicit Play JSON Format */ trait ShortPlayJsonValueEnum[EntryType <: ShortEnumEntry] - extends PlayJsonValueEnum[Short, EntryType] { this: ShortEnum[EntryType] => - implicit val format: Format[EntryType] = EnumFormats.formats(this) + extends PlayJsonValueEnum[Short, EntryType] { self: ShortEnum[EntryType] => + implicit val format: Format[EntryType] = EnumFormats.formats(self) } /** Enum implementation for String enum members that contains an implicit Play JSON Format */ trait StringPlayJsonValueEnum[EntryType <: StringEnumEntry] - extends PlayJsonValueEnum[String, EntryType] { this: StringEnum[EntryType] => - implicit val format: Format[EntryType] = EnumFormats.formats(this) + extends PlayJsonValueEnum[String, EntryType] { self: StringEnum[EntryType] => + implicit val format: Format[EntryType] = EnumFormats.formats(self) } /** Enum implementation for Char enum members that contains an implicit Play JSON Format */ trait CharPlayJsonValueEnum[EntryType <: CharEnumEntry] extends PlayJsonValueEnum[Char, EntryType] { - this: CharEnum[EntryType] => - implicit val format: Format[EntryType] = EnumFormats.formats(this) + self: CharEnum[EntryType] => + implicit val format: Format[EntryType] = EnumFormats.formats(self) } /** Enum implementation for Byte enum members that contains an implicit Play JSON Format */ trait BytePlayJsonValueEnum[EntryType <: ByteEnumEntry] extends PlayJsonValueEnum[Byte, EntryType] { - this: ByteEnum[EntryType] => - implicit val format: Format[EntryType] = EnumFormats.formats(this) + self: ByteEnum[EntryType] => + implicit val format: Format[EntryType] = EnumFormats.formats(self) } diff --git a/enumeratum-play-json/src/test/scala/enumeratum/EnumFormatsSpec.scala b/enumeratum-play-json/src/test/scala/enumeratum/EnumFormatsSpec.scala index 2b4d202c..c06e2758 100644 --- a/enumeratum-play-json/src/test/scala/enumeratum/EnumFormatsSpec.scala +++ b/enumeratum-play-json/src/test/scala/enumeratum/EnumFormatsSpec.scala @@ -28,7 +28,7 @@ class EnumFormatsSpec extends AnyFunSpec with Matchers { testScenario( descriptor = "case insensitive", - reads = EnumFormats.reads(enum = Dummy, insensitive = true), + reads = EnumFormats.reads(e = Dummy, insensitive = true), readSuccessExpectations = Map( "A" -> Dummy.A, "a" -> Dummy.A @@ -36,12 +36,12 @@ class EnumFormatsSpec extends AnyFunSpec with Matchers { readErrors = Map.empty, writes = EnumFormats.writes(Dummy), writeExpectations = Map(Dummy.A -> "A"), - formats = EnumFormats.formats(enum = Dummy, insensitive = true) + formats = EnumFormats.formats(e = Dummy, insensitive = true) ) testKeyScenario( descriptor = "case insensitive", - reads = EnumFormats.keyReads(enum = Dummy, insensitive = true), + reads = EnumFormats.keyReads(e = Dummy, insensitive = true), readSuccessExpectations = Map( "A" -> Dummy.A, "a" -> Dummy.A diff --git a/enumeratum-play-json/src/test/scala/enumeratum/values/EnumFormatsSpec.scala b/enumeratum-play-json/src/test/scala/enumeratum/values/EnumFormatsSpec.scala index 8b7626d5..f1c25b78 100644 --- a/enumeratum-play-json/src/test/scala/enumeratum/values/EnumFormatsSpec.scala +++ b/enumeratum-play-json/src/test/scala/enumeratum/values/EnumFormatsSpec.scala @@ -16,18 +16,18 @@ class EnumFormatsSpec extends AnyFunSpec with Matchers with EnumJsonFormatHelper testNumericReads("IntEnum", LibraryItem) testNumericReads("LongEnum", ContentType) testNumericReads("ShortEnum", Drinks) - testReads("StringEnum", OperatingSystem, JsString) + testReads("StringEnum", OperatingSystem, JsString(_)) testReads( "CharEnum", Alphabet, - { c: Char => + { (c: Char) => JsString(s"$c") } ) testReads( "ByteEnum", Bites, - { b: Byte => + { (b: Byte) => JsNumber(b.toInt) } ) @@ -39,18 +39,18 @@ class EnumFormatsSpec extends AnyFunSpec with Matchers with EnumJsonFormatHelper testNumericWrites("IntEnum", LibraryItem) testNumericWrites("LongEnum", ContentType) testNumericWrites("ShortEnum", Drinks) - testWrites("StringEnum", OperatingSystem, JsString) + testWrites("StringEnum", OperatingSystem, JsString(_)) testWrites( "CharEnum", Alphabet, - { c: Char => + { (c: Char) => JsString(s"$c") } ) testWrites( "ByteEnum", Bites, - { b: Byte => + { (b: Byte) => JsNumber(b.toInt) } ) @@ -62,11 +62,11 @@ class EnumFormatsSpec extends AnyFunSpec with Matchers with EnumJsonFormatHelper testNumericFormats("IntEnum", LibraryItem) testNumericFormats("LongEnum", ContentType) testNumericFormats("ShortEnum", Drinks) - testFormats("StringEnum", OperatingSystem, JsString) + testFormats("StringEnum", OperatingSystem, JsString(_)) testFormats( "ByteEnum", Bites, - { b: Byte => + { (b: Byte) => JsNumber(b.toInt) } ) diff --git a/enumeratum-play-json/src/test/scala/enumeratum/values/EnumJsonFormatHelpers.scala b/enumeratum-play-json/src/test/scala/enumeratum/values/EnumJsonFormatHelpers.scala index cee81fc5..b81f7236 100644 --- a/enumeratum-play-json/src/test/scala/enumeratum/values/EnumJsonFormatHelpers.scala +++ b/enumeratum-play-json/src/test/scala/enumeratum/values/EnumJsonFormatHelpers.scala @@ -15,14 +15,14 @@ trait EnumJsonFormatHelpers { this: AnyFunSpec with Matchers => ValueType ], ValueType <: AnyVal: Numeric: Writes]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], providedWrites: Option[Writes[EntryType]] = None ): Unit = { val numeric = implicitly[Numeric[ValueType]] testWrites( enumKind, - enum, - { i: ValueType => + myEnum, + { (i: ValueType) => JsNumber(numeric.toInt(i)) }, providedWrites @@ -31,14 +31,14 @@ trait EnumJsonFormatHelpers { this: AnyFunSpec with Matchers => def testWrites[EntryType <: ValueEnumEntry[ValueType], ValueType: Writes]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], jsWrapper: ValueType => JsValue, providedWrites: Option[Writes[EntryType]] = None ): Unit = { - val writes = providedWrites.getOrElse(EnumFormats.writes(enum)) + val writes = providedWrites.getOrElse(EnumFormats.writes(myEnum)) describe(enumKind) { it("should write proper JsValues") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => writes.writes(entry) shouldBe jsWrapper(entry.value) } } @@ -47,14 +47,14 @@ trait EnumJsonFormatHelpers { this: AnyFunSpec with Matchers => def testNumericReads[EntryType <: ValueEnumEntry[ValueType], ValueType <: AnyVal: Numeric: Reads]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], providedReads: Option[Reads[EntryType]] = None ): Unit = { val numeric = implicitly[Numeric[ValueType]] testReads( enumKind, - enum, - { i: ValueType => + myEnum, + { (i: ValueType) => JsNumber(numeric.toInt(i)) }, providedReads @@ -63,17 +63,18 @@ trait EnumJsonFormatHelpers { this: AnyFunSpec with Matchers => def testReads[EntryType <: ValueEnumEntry[ValueType], ValueType: Reads]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], jsWrapper: ValueType => JsValue, providedReads: Option[Reads[EntryType]] = None ): Unit = { - val reads = providedReads.getOrElse(EnumFormats.reads(enum)) + val reads = providedReads.getOrElse(EnumFormats.reads(myEnum)) describe(enumKind) { it("should read valid values") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => reads.reads(jsWrapper(entry.value)).asOpt.value shouldBe entry } } + it("should fail to read with invalid values") { reads.reads(JsNumber(Int.MaxValue)).isError shouldBe true reads.reads(JsString("boon")).isError shouldBe true @@ -85,22 +86,22 @@ trait EnumJsonFormatHelpers { this: AnyFunSpec with Matchers => ValueType ], ValueType <: AnyVal: Numeric: Reads: Writes]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], providedFormat: Option[Format[EntryType]] = None ): Unit = { - testNumericReads(enumKind, enum, providedFormat) - testNumericWrites(enumKind, enum, providedFormat) + testNumericReads(enumKind, myEnum, providedFormat) + testNumericWrites(enumKind, myEnum, providedFormat) } def testFormats[EntryType <: ValueEnumEntry[ValueType], ValueType: Reads: Writes]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], jsWrapper: ValueType => JsValue, providedFormat: Option[Format[EntryType]] = None ): Unit = { - val format = providedFormat.getOrElse(EnumFormats.formats(enum)) - testReads(enumKind, enum, jsWrapper, Some(format)) - testWrites(enumKind, enum, jsWrapper, Some(format)) + val format = providedFormat.getOrElse(EnumFormats.formats(myEnum)) + testReads(enumKind, myEnum, jsWrapper, Some(format)) + testWrites(enumKind, myEnum, jsWrapper, Some(format)) } } diff --git a/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/EnumHandler.scala b/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/EnumHandler.scala index 2befdfa4..d57a6f40 100644 --- a/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/EnumHandler.scala +++ b/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/EnumHandler.scala @@ -15,65 +15,73 @@ object EnumHandler { /** Returns a BSONReader for a given enum [[Enum]] * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false */ def reader[A <: EnumEntry]( - enum: Enum[A], + @deprecatedName(Symbol("enum")) e: Enum[A], insensitive: Boolean = false ): BSONReader[A] = { - if (insensitive) collect[A](enum.withNameInsensitiveOption) - else collect[A](enum.withNameOption) + if (insensitive) collect[A](e.withNameInsensitiveOption) + else collect[A](e.withNameOption) } /** Returns a KeyReader for a given enum [[Enum]] * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false */ def keyReader[A <: EnumEntry]( - enum: Enum[A], + @deprecatedName(Symbol("enum")) e: Enum[A], insensitive: Boolean = false ): KeyReader[A] = { - if (insensitive) collectKey[A](enum.withNameInsensitiveOption) - else collectKey[A](enum.withNameOption) + if (insensitive) collectKey[A](e.withNameInsensitiveOption) + else collectKey[A](e.withNameOption) } /** Returns a BSONReader for a given enum [[Enum]] transformed to lower case * - * @param enum + * @param e * The enum */ - def readerLowercaseOnly[A <: EnumEntry](enum: Enum[A]): BSONReader[A] = - collect[A](enum.withNameLowercaseOnlyOption) + def readerLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): BSONReader[A] = + collect[A](e.withNameLowercaseOnlyOption) /** Returns a KeyReader for a given enum [[Enum]] transformed to lower case * - * @param enum + * @param e * The enum */ - def keyReaderLowercaseOnly[A <: EnumEntry](enum: Enum[A]): KeyReader[A] = - collectKey[A](enum.withNameLowercaseOnlyOption) + def keyReaderLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): KeyReader[A] = + collectKey[A](e.withNameLowercaseOnlyOption) /** Returns a BSONReader for a given enum [[Enum]] transformed to upper case * - * @param enum + * @param e * The enum */ - def readerUppercaseOnly[A <: EnumEntry](enum: Enum[A]): BSONReader[A] = - collect[A](enum.withNameUppercaseOnlyOption) + def readerUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): BSONReader[A] = + collect[A](e.withNameUppercaseOnlyOption) /** Returns a KeyReader for a given enum [[Enum]] transformed to upper case * - * @param enum + * @param e * The enum */ - def keyReaderUppercaseOnly[A <: EnumEntry](enum: Enum[A]): KeyReader[A] = - collectKey[A](enum.withNameUppercaseOnlyOption) + def keyReaderUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): KeyReader[A] = + collectKey[A](e.withNameUppercaseOnlyOption) private def collect[A](f: String => Option[A]): BSONReader[A] = BSONReader.option[A] { @@ -91,65 +99,69 @@ object EnumHandler { /** Returns a BSONWriter for a given enum [[Enum]] */ - def writer[A <: EnumEntry](enum: Enum[A]): BSONWriter[A] = + def writer[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): BSONWriter[A] = BSONWriter[A] { t => BSONString(t.entryName) } /** Returns a KeyWriter for a given enum [[Enum]] */ - def keyWriter[A <: EnumEntry](enum: Enum[A]): KeyWriter[A] = + def keyWriter[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): KeyWriter[A] = KeyWriter[A](_.entryName) /** Returns a BSONWriter for a given enum [[Enum]], outputting the value as lower case */ - def writerLowercase[A <: EnumEntry](enum: Enum[A]): BSONWriter[A] = + def writerLowercase[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): BSONWriter[A] = BSONWriter[A] { t => BSONString(t.entryName.toLowerCase) } /** Returns a KeyWriter for a given enum [[Enum]], outputting the value as lower case */ - def keyWriterLowercase[A <: EnumEntry](enum: Enum[A]): KeyWriter[A] = + def keyWriterLowercase[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): KeyWriter[A] = KeyWriter[A](_.entryName.toLowerCase) /** Returns a BSONWriter for a given enum [[Enum]], outputting the value as upper case */ - def writerUppercase[A <: EnumEntry](enum: Enum[A]): BSONWriter[A] = + def writerUppercase[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): BSONWriter[A] = BSONWriter[A] { t => BSONString(t.entryName.toUpperCase) } /** Returns a KeyWriter for a given enum [[Enum]], outputting the value as upper case */ - def keyWriterUppercase[A <: EnumEntry](enum: Enum[A]): KeyWriter[A] = + def keyWriterUppercase[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): KeyWriter[A] = KeyWriter[A](_.entryName.toUpperCase) /** Returns a BSONHandler for a given enum [[Enum]] * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false */ def handler[A <: EnumEntry]( - enum: Enum[A], + @deprecatedName(Symbol("enum")) e: Enum[A], insensitive: Boolean = false - ): BSONHandler[A] = BSONHandler.provided[A](reader(enum, insensitive), writer(enum)) + ): BSONHandler[A] = BSONHandler.provided[A](reader(e, insensitive), writer(e)) /** Returns a BSONHandler for a given enum [[Enum]], handling a lower case transformation * - * @param enum + * @param e * The enum */ - def handlerLowercaseOnly[A <: EnumEntry](enum: Enum[A]): BSONHandler[A] = - BSONHandler.provided[A](readerLowercaseOnly(enum), writerLowercase(enum)) + def handlerLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): BSONHandler[A] = + BSONHandler.provided[A](readerLowercaseOnly(e), writerLowercase(e)) /** Returns a BSONHandler for a given enum [[Enum]], handling an upper case transformation * - * @param enum + * @param e * The enum */ - def handlerUppercaseOnly[A <: EnumEntry](enum: Enum[A]): BSONHandler[A] = - BSONHandler.provided[A](readerUppercaseOnly(enum), writerUppercase(enum)) + def handlerUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): BSONHandler[A] = + BSONHandler.provided[A](readerUppercaseOnly(e), writerUppercase(e)) } diff --git a/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/values/EnumHandler.scala b/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/values/EnumHandler.scala index 71658ab0..12bedb75 100644 --- a/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/values/EnumHandler.scala +++ b/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/values/EnumHandler.scala @@ -12,29 +12,29 @@ object EnumHandler { * Enum's value type. */ def reader[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseBsonReader: BSONReader[ValueType] ): BSONReader[EntryType] = BSONReader.from[EntryType] { bson => - baseBsonReader.readTry(bson).map(enum.withValue) + baseBsonReader.readTry(bson).map(e.withValue) } /** Returns a KeyReader for the provided ValueEnum based on the given base KeyReader for the * Enum's value type. */ def keyReader[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseBsonReader: KeyReader[ValueType] ): KeyReader[EntryType] = KeyReader.from[EntryType] { bson => - baseBsonReader.readTry(bson).map(enum.withValue) + baseBsonReader.readTry(bson).map(e.withValue) } /** Returns a BSONWriter for the provided ValueEnum based on the given base BSONWriter for the * Enum's value type. */ def writer[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseBsonWriter: BSONWriter[ValueType] ): BSONWriter[EntryType] = BSONWriter.from[EntryType] { t => @@ -45,7 +45,7 @@ object EnumHandler { * Enum's value type. */ def keyWriter[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseBsonWriter: KeyWriter[ValueType] ): KeyWriter[EntryType] = KeyWriter.from[EntryType] { t => @@ -56,8 +56,8 @@ object EnumHandler { * BSONWriter for the Enum's value type. */ def handler[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseBsonHandler: BSONHandler[ValueType] - ): BSONHandler[EntryType] = BSONHandler.provided[EntryType](reader(enum), writer(enum)) + ): BSONHandler[EntryType] = BSONHandler.provided[EntryType](reader(e), writer(e)) } diff --git a/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/values/ReactiveMongoBsonValueEnum.scala b/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/values/ReactiveMongoBsonValueEnum.scala index 11665a71..b5964989 100644 --- a/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/values/ReactiveMongoBsonValueEnum.scala +++ b/enumeratum-reactivemongo-bson/src/main/scala/enumeratum/values/ReactiveMongoBsonValueEnum.scala @@ -1,14 +1,13 @@ package enumeratum.values -import enumeratum.values.BSONValueHandlers._ -import reactivemongo.api.bson._ +import reactivemongo.api.bson.{BSONHandler, KeyReader, KeyWriter} /** @author * Alessandro Lacava (@lambdista) * @since 2016-04-23 */ sealed trait ReactiveMongoBsonValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { - enum: ValueEnum[ValueType, EntryType] => + selfEnum: ValueEnum[ValueType, EntryType] => /** Implicit BSON handler for the entries of this enum */ diff --git a/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/Dummy.scala b/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/Dummy.scala index 14e618fa..854b3c94 100644 --- a/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/Dummy.scala +++ b/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/Dummy.scala @@ -5,6 +5,7 @@ package enumeratum * @since 2016-04-23 */ sealed trait Dummy extends EnumEntry + object Dummy extends Enum[Dummy] with ReactiveMongoBsonEnum[Dummy] { case object A extends Dummy case object B extends Dummy diff --git a/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/EnumBsonHandlerSpec.scala b/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/EnumBsonHandlerSpec.scala index 636ff68a..a358ab62 100644 --- a/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/EnumBsonHandlerSpec.scala +++ b/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/EnumBsonHandlerSpec.scala @@ -1,17 +1,26 @@ package enumeratum +import scala.util.Success + +import reactivemongo.api.bson.{ + BSONHandler, + BSONInteger, + BSONReader, + BSONString, + BSONWriter, + KeyReader, + KeyWriter +} + import org.scalatest.OptionValues._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -import reactivemongo.api.bson._ - -import scala.util.Success /** @author * Alessandro Lacava (@lambdista) * @since 2016-04-23 */ -class EnumBsonHandlerSpec extends AnyFunSpec with Matchers { +final class EnumBsonHandlerSpec extends AnyFunSpec with Matchers { testScenario( descriptor = "normal operation (no transformations)", @@ -34,7 +43,7 @@ class EnumBsonHandlerSpec extends AnyFunSpec with Matchers { testScenario( descriptor = "case insensitive", - reader = EnumHandler.reader(enum = Dummy, insensitive = true), + reader = EnumHandler.reader(e = Dummy, insensitive = true), expectedReadSuccesses = Map("A" -> Dummy.A, "a" -> Dummy.A, "C" -> Dummy.c), expectedReadFails = Nil, writer = EnumHandler.writer(Dummy), @@ -44,7 +53,7 @@ class EnumBsonHandlerSpec extends AnyFunSpec with Matchers { testKeyScenario( descriptor = "case insensitive", - reader = EnumHandler.keyReader(enum = Dummy, insensitive = true), + reader = EnumHandler.keyReader(e = Dummy, insensitive = true), expectedReadSuccesses = Map("A" -> Dummy.A, "a" -> Dummy.A, "C" -> Dummy.c), expectedReadFails = Nil, writer = EnumHandler.keyWriter(Dummy), @@ -89,6 +98,8 @@ class EnumBsonHandlerSpec extends AnyFunSpec with Matchers { expectedWrites = Map(Dummy.A -> "A", Dummy.c -> "C") ) + // --- + private def testScenario( descriptor: String, reader: BSONReader[Dummy], diff --git a/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/EnumBsonHandlerHelpers.scala b/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/ValueEnumBsonHandlerHelpers.scala similarity index 67% rename from enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/EnumBsonHandlerHelpers.scala rename to enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/ValueEnumBsonHandlerHelpers.scala index 8b9640af..5c59d792 100644 --- a/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/EnumBsonHandlerHelpers.scala +++ b/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/ValueEnumBsonHandlerHelpers.scala @@ -2,25 +2,35 @@ package enumeratum.values import scala.util.Success +import reactivemongo.api.bson.{ + BSONHandler, + BSONInteger, + BSONReader, + BSONString, + BSONWriter, + KeyReader, + KeyWriter +} + import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -import reactivemongo.api.bson._ /** @author * Alessandro Lacava (@lambdista) * @since 2016-04-23 */ -trait EnumBsonHandlerHelpers { this: AnyFunSpec with Matchers => +trait ValueEnumBsonHandlerHelpers { this: AnyFunSpec with Matchers => def testWriter[EntryType <: ValueEnumEntry[ValueType], ValueType]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], providedWriter: Option[BSONWriter[EntryType]] = None )(implicit baseHandler: BSONHandler[ValueType]): Unit = { - val writer = providedWriter.getOrElse(EnumHandler.writer(enum)) + val writer = providedWriter.getOrElse(EnumHandler.writer(myEnum)) + describe(enumKind) { it("should write proper BSONValue") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => writer.writeTry(entry) shouldBe baseHandler.writeTry(entry.value) } } @@ -29,13 +39,14 @@ trait EnumBsonHandlerHelpers { this: AnyFunSpec with Matchers => def testKeyWriter[EntryType <: ValueEnumEntry[ValueType], ValueType]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], providedWriter: Option[KeyWriter[EntryType]] = None )(implicit baseHandler: KeyWriter[ValueType]): Unit = { - val writer = providedWriter.getOrElse(EnumHandler.keyWriter(enum)) + val writer = providedWriter.getOrElse(EnumHandler.keyWriter(myEnum)) + describe(enumKind) { it("should write proper key") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => writer.writeTry(entry) shouldBe baseHandler.writeTry(entry.value) } } @@ -44,53 +55,55 @@ trait EnumBsonHandlerHelpers { this: AnyFunSpec with Matchers => def testReader[EntryType <: ValueEnumEntry[ValueType], ValueType]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], providedReader: Option[BSONReader[EntryType]] = None )(implicit baseHandler: BSONHandler[ValueType]): Unit = { - val reader = providedReader.getOrElse(EnumHandler.reader(enum)) + val reader = providedReader.getOrElse(EnumHandler.reader(myEnum)) + describe(enumKind) { it("should read valid values") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => reader.readTry(baseHandler.writeTry(entry.value).get).get shouldBe entry } } + it("should fail to read with invalid values") { - reader.readTry(BSONInteger(Int.MaxValue)) shouldBe 'failure - reader.readTry(BSONString("boon")) shouldBe 'failure + reader.readTry(BSONInteger(Int.MaxValue)) shouldBe Symbol("failure") + reader.readTry(BSONString("boon")) shouldBe Symbol("failure") } } } def testKeyReader[EntryType <: ValueEnumEntry[ValueType], ValueType]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], providedReader: Option[KeyReader[EntryType]] = None )(implicit baseWriter: KeyWriter[ValueType], baseReader: KeyReader[ValueType]): Unit = { - val reader = providedReader.getOrElse(EnumHandler.keyReader(enum)) + val reader = providedReader.getOrElse(EnumHandler.keyReader(myEnum)) describe(enumKind) { it("should read valid key") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => baseWriter.writeTry(entry.value).flatMap(reader.readTry) shouldBe Success(entry) } } it("should fail to read with invalid key") { - reader.readTry(Int.MaxValue.toString) shouldBe 'failure - reader.readTry("boon") shouldBe 'failure + reader.readTry(Int.MaxValue.toString) shouldBe Symbol("failure") + reader.readTry("boon") shouldBe Symbol("failure") } } } def testHandler[EntryType <: ValueEnumEntry[ValueType], ValueType]( enumKind: String, - enum: ValueEnum[ValueType, EntryType], + myEnum: ValueEnum[ValueType, EntryType], providedHandler: Option[BSONHandler[EntryType]] = None )(implicit baseHandler: BSONHandler[ValueType]): Unit = { - val handler = providedHandler.getOrElse(EnumHandler.handler(enum)) + val handler = providedHandler.getOrElse(EnumHandler.handler(myEnum)) describe(s"$enumKind Handler") { - testReader(enumKind, enum, Some(handler)) - testWriter(enumKind, enum, Some(handler)) + testReader(enumKind, myEnum, Some(handler)) + testWriter(enumKind, myEnum, Some(handler)) } } diff --git a/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/EnumBsonHandlerSpec.scala b/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/ValueEnumBsonHandlerSpec.scala similarity index 94% rename from enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/EnumBsonHandlerSpec.scala rename to enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/ValueEnumBsonHandlerSpec.scala index d2c113b7..b2912346 100644 --- a/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/EnumBsonHandlerSpec.scala +++ b/enumeratum-reactivemongo-bson/src/test/scala/enumeratum/values/ValueEnumBsonHandlerSpec.scala @@ -1,6 +1,5 @@ package enumeratum.values -import enumeratum.values.BSONValueHandlers._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers @@ -8,17 +7,18 @@ import org.scalatest.matchers.should.Matchers * Alessandro Lacava (@lambdista) * @since 2016-04-23 */ -class EnumBsonHandlerSpec extends AnyFunSpec with Matchers with EnumBsonHandlerHelpers { +final class ValueEnumBsonHandlerSpec + extends AnyFunSpec + with Matchers + with ValueEnumBsonHandlerHelpers { describe(".reader") { - testReader("IntEnum", LibraryItem) testReader("LongEnum", ContentType) testReader("ShortEnum", Drinks) testReader("StringEnum", OperatingSystem) testReader("ByteEnum", Bites) testReader("CharEnum", Alphabet) - } describe(".keyReader") { diff --git a/enumeratum-scalacheck/src/main/scala/enumeratum/ArbitraryInstances.scala b/enumeratum-scalacheck/src/main/scala/enumeratum/ArbitraryInstances.scala index d51cea08..69e887c2 100644 --- a/enumeratum-scalacheck/src/main/scala/enumeratum/ArbitraryInstances.scala +++ b/enumeratum-scalacheck/src/main/scala/enumeratum/ArbitraryInstances.scala @@ -5,7 +5,7 @@ import org.scalacheck.{Arbitrary, Gen} trait ArbitraryInstances { implicit def arbEnumEntry[EnumType <: EnumEntry](implicit - myEnum: Enum[EnumType] - ): Arbitrary[EnumType] = Arbitrary(Gen.oneOf(myEnum.values)) + @deprecatedName(Symbol("enum")) e: Enum[EnumType] + ): Arbitrary[EnumType] = Arbitrary(Gen.oneOf(e.values)) } From 51e8f84b5fc864445cc72b126c4cccbeaf661a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Wed, 7 Sep 2022 22:55:56 +0200 Subject: [PATCH 07/16] Update play modules --- build.sbt | 37 +++++---- .../enumeratum/helpers/ActionHelper.scala | 11 +++ .../main/scala-2/enumeratum/FormsCompat.scala | 50 +++++++++++ .../enumeratum/values/FormsCompat.scala | 22 +++++ .../src/main/scala/enumeratum/Forms.scala | 82 +++++++------------ .../scala/enumeratum/PlayFormFieldEnum.scala | 2 +- .../main/scala/enumeratum/UrlBinders.scala | 67 +++++++++------ .../main/scala/enumeratum/values/Forms.scala | 20 +---- .../enumeratum/values/PlayFormValueEnum.scala | 17 ++-- .../values/PlayPathBindableValueEnum.scala | 26 +++--- .../values/PlayQueryBindableValueEnum.scala | 26 +++--- .../scala/enumeratum/values/UrlBinders.scala | 14 ++-- .../src/test/scala/enumeratum/FormSpec.scala | 31 +++---- .../test/scala/enumeratum/PlayEnumSpec.scala | 17 ++-- .../scala/enumeratum/UrlBindersSpec.scala | 12 +-- .../values/PlayValueEnumHelpers.scala | 53 ++++++------ .../enumeratum/values/PlayValueEnumSpec.scala | 6 +- project/plugins.sbt | 21 +++-- 18 files changed, 287 insertions(+), 227 deletions(-) create mode 100644 enumeratum-play/compat/src/test/scala-3/enumeratum/helpers/ActionHelper.scala create mode 100644 enumeratum-play/src/main/scala-2/enumeratum/FormsCompat.scala create mode 100644 enumeratum-play/src/main/scala-2/enumeratum/values/FormsCompat.scala diff --git a/build.sbt b/build.sbt index e40e96fb..f3f0787b 100644 --- a/build.sbt +++ b/build.sbt @@ -35,14 +35,6 @@ def theArgonautVersion(scalaVersion: String) = throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Argonaut") } -def thePlayVersion(scalaVersion: String) = - CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor >= 12 => "2.8.0" - case Some((3, _)) => "2.8.0" - case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Play") - } - def theSlickVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor <= 11 => "3.3.3" @@ -91,6 +83,10 @@ def scalaTestPlay(scalaVersion: String) = CrossVersion.partialVersion(scalaVersi case Some((3, _)) => ("org.scalatestplus.play" %% "scalatestplus-play" % "5.1.0" % Test) .cross(CrossVersion.for3Use2_13) + .exclude("org.scalactic", "*") + .exclude("org.scalatest", "*") + .exclude("org.scala-lang.modules", "*") + .exclude("com.typesafe.play", "play-json_2.13") case _ => throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for play-test") @@ -393,12 +389,18 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu .settings( version := Versions.Core.head, crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version, scala_3Version), - libraryDependencies ++= Seq( - ("com.typesafe.play" %% "play" % thePlayVersion(scalaVersion.value)) - .exclude("org.scala-lang.modules", "*") - .cross(CrossVersion.for3Use2_13), - scalaTestPlay(scalaVersion.value) - ), + libraryDependencies += { + val dep = "com.typesafe.play" %% "play" % "2.8.0" + + if (scalaBinaryVersion.value == "3") { + dep.exclude("org.scala-lang.modules", "*") + .exclude("com.typesafe.play", "play-json_2.13") + .cross(CrossVersion.for3Use2_13) + } else { + dep + } + }, + libraryDependencies += scalaTestPlay(scalaVersion.value), libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -408,6 +410,13 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu "com.beachape" %% "enumeratum-test" % Versions.Core.stable % Test ) } + }, + scalacOptions ++= { + if (scalaBinaryVersion.value == "3") { + Seq("-Wconf:cat=deprecation&msg=.*right-biased.*:s") + } else { + Seq.empty + } } ) .settings(withCompatUnmanagedSources(jsJvmCrossProject = false, includeTestSrcs = true)) diff --git a/enumeratum-play/compat/src/test/scala-3/enumeratum/helpers/ActionHelper.scala b/enumeratum-play/compat/src/test/scala-3/enumeratum/helpers/ActionHelper.scala new file mode 100644 index 00000000..d5c3847b --- /dev/null +++ b/enumeratum-play/compat/src/test/scala-3/enumeratum/helpers/ActionHelper.scala @@ -0,0 +1,11 @@ +package enumeratum.helpers + +import play.api.mvc.{Action, ActionBuilder, AnyContent, AnyContentAsEmpty, BodyParsers, Result} + +object ActionHelper { + + import scala.concurrent.ExecutionContext.Implicits.global + + def apply(block: => Result): Action[AnyContent] = + new ActionBuilder.IgnoringBody().apply(block) +} diff --git a/enumeratum-play/src/main/scala-2/enumeratum/FormsCompat.scala b/enumeratum-play/src/main/scala-2/enumeratum/FormsCompat.scala new file mode 100644 index 00000000..915c3762 --- /dev/null +++ b/enumeratum-play/src/main/scala-2/enumeratum/FormsCompat.scala @@ -0,0 +1,50 @@ +package enumeratum + +import play.api.data.{Mapping, Forms => PlayForms} + +private[enumeratum] trait FormsCompat { _: Forms.type => + + @deprecated("Use `enumMapping`", "1.7.2") + def enum[A <: EnumEntry]( + enum: Enum[A], + insensitive: Boolean = false + ): Mapping[A] = PlayForms.of(format(enum, insensitive)) + + /** Returns an [[Enum]] mapping + * + * Example: + * + * {{{ + * scala> import enumeratum._ + * scala> import play.api.data.Form + * + * scala> sealed trait Greeting extends EnumEntry + * + * scala> object Greeting extends Enum[Greeting] { + * | val values = findValues + * | case object Hello extends Greeting + * | case object GoodBye extends Greeting + * | case object Hi extends Greeting + * | case object Bye extends Greeting + * | } + * + * scala> val form = Form("greeting" -> Forms.enum(Greeting)) + * scala> form.bind(Map("greeting" -> "Hello")).value + * res0: Option[Greeting] = Some(Hello) + * + * scala> val formInsensitive = Form("greeting" -> Forms.enum(Greeting, true)) + * scala> formInsensitive.bind(Map("greeting" -> "hElLo")).value + * res1: Option[Greeting] = Some(Hello) + * }}} + * + * @param enum + * The enum + * @param insensitive + * bind in a case-insensitive way, defaults to false + */ + @inline def enumMapping[A <: EnumEntry]( + e: Enum[A], + insensitive: Boolean = false + ): Mapping[A] = enum[A](e, insensitive) + +} diff --git a/enumeratum-play/src/main/scala-2/enumeratum/values/FormsCompat.scala b/enumeratum-play/src/main/scala-2/enumeratum/values/FormsCompat.scala new file mode 100644 index 00000000..54f0fd0f --- /dev/null +++ b/enumeratum-play/src/main/scala-2/enumeratum/values/FormsCompat.scala @@ -0,0 +1,22 @@ +package enumeratum.values + +import play.api.data.{Mapping, Forms => PlayForms} +import play.api.data.format.Formatter + +private[values] trait FormsCompat { _: Forms.type => + + /** Returns a [[ValueEnum]] mapping for Play form fields + */ + @deprecated("Use `enumMapping`", "1.7.2") + def enum[ValueType, EntryType <: ValueEnumEntry[ValueType], EnumType <: ValueEnum[ + ValueType, + EntryType + ]](baseFormatter: Formatter[ValueType])(enum: EnumType): Mapping[EntryType] = + PlayForms.of(formatter(baseFormatter)(enum)) + + @inline def enumMapping[ValueType, EntryType <: ValueEnumEntry[ValueType], EnumType <: ValueEnum[ + ValueType, + EntryType + ]](baseFormatter: Formatter[ValueType])(e: EnumType): Mapping[EntryType] = + enum[ValueType, EntryType, EnumType](baseFormatter)(e) +} diff --git a/enumeratum-play/src/main/scala/enumeratum/Forms.scala b/enumeratum-play/src/main/scala/enumeratum/Forms.scala index 1ff8b2c6..756e9b7d 100644 --- a/enumeratum-play/src/main/scala/enumeratum/Forms.scala +++ b/enumeratum-play/src/main/scala/enumeratum/Forms.scala @@ -5,42 +5,7 @@ import play.api.data.{FormError, Forms => PlayForms, Mapping} /** Created by Lloyd on 2/3/15. */ -object Forms { - - /** Returns an [[Enum]] mapping - * - * Example: - * - * {{{ - * scala> import enumeratum._ - * scala> import play.api.data.Form - * - * scala> sealed trait Greeting extends EnumEntry - * - * scala> object Greeting extends Enum[Greeting] { - * | val values = findValues - * | case object Hello extends Greeting - * | case object GoodBye extends Greeting - * | case object Hi extends Greeting - * | case object Bye extends Greeting - * | } - * - * scala> val form = Form("greeting" -> Forms.enum(Greeting)) - * scala> form.bind(Map("greeting" -> "Hello")).value - * res0: Option[Greeting] = Some(Hello) - * - * scala> val formInsensitive = Form("greeting" -> Forms.enum(Greeting, true)) - * scala> formInsensitive.bind(Map("greeting" -> "hElLo")).value - * res1: Option[Greeting] = Some(Hello) - * }}} - * - * @param enum - * The enum - * @param insensitive - * bind in a case-insensitive way, defaults to false - */ - def enum[A <: EnumEntry](enum: Enum[A], insensitive: Boolean = false): Mapping[A] = - PlayForms.of(format(enum, insensitive)) +object Forms extends FormsCompat { /** Returns an [[Enum]] mapping for lower case binding only * @@ -68,11 +33,11 @@ object Forms { * res1: Option[Greeting] = None * }}} * - * @param enum + * @param e * The enum */ - def enumLowerCaseOnly[A <: EnumEntry](enum: Enum[A]): Mapping[A] = - PlayForms.of(formatLowercaseOnly(enum)) + def enumLowerCaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Mapping[A] = + PlayForms.of(formatLowercaseOnly(e)) /** Returns an [[Enum]] mapping for upper case binding only * @@ -100,26 +65,31 @@ object Forms { * res1: Option[Greeting] = None * }}} * - * @param enum + * @param e * The enum */ - def enumUppercaseOnly[A <: EnumEntry](enum: Enum[A]): Mapping[A] = - PlayForms.of(formatUppercaseOnly(enum)) + def enumUppercaseOnly[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Mapping[A] = + PlayForms.of(formatUppercaseOnly(e)) /** Returns a Formatter for [[Enum]] * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false */ - def format[A <: EnumEntry](enum: Enum[A], insensitive: Boolean = false): Formatter[A] = + def format[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A], + insensitive: Boolean = false + ): Formatter[A] = new Formatter[A] { def bind(key: String, data: Map[String, String]) = { play.api.data.format.Formats.stringFormat.bind(key, data).right.flatMap { s => - val maybeBound = - if (insensitive) enum.withNameInsensitiveOption(s) - else enum.withNameOption(s) + val maybeBound = { + if (insensitive) e.withNameInsensitiveOption(s) + else e.withNameOption(s) + } + maybeBound match { case Some(obj) => Right(obj) case None => Left(Seq(FormError(key, "error.enum", Nil))) @@ -131,38 +101,44 @@ object Forms { /** Returns a Formatter for [[Enum]] that transforms to lower case * - * @param enum + * @param e * The enum */ - def formatLowercaseOnly[A <: EnumEntry](enum: Enum[A]): Formatter[A] = + def formatLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): Formatter[A] = new Formatter[A] { def bind(key: String, data: Map[String, String]) = { play.api.data.format.Formats.stringFormat.bind(key, data).right.flatMap { s => - enum.withNameLowercaseOnlyOption(s) match { + e.withNameLowercaseOnlyOption(s) match { case Some(obj) => Right(obj) case None => Left(Seq(FormError(key, "error.enum", Nil))) } } } + def unbind(key: String, value: A) = Map(key -> value.entryName.toLowerCase) } /** Returns a Formatter for [[Enum]] that transforms to upper case * - * @param enum + * @param e * The enum */ - def formatUppercaseOnly[A <: EnumEntry](enum: Enum[A]): Formatter[A] = + def formatUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): Formatter[A] = new Formatter[A] { def bind(key: String, data: Map[String, String]) = { play.api.data.format.Formats.stringFormat.bind(key, data).right.flatMap { s => - enum.withNameUppercaseOnlyOption(s) match { + e.withNameUppercaseOnlyOption(s) match { case Some(obj) => Right(obj) case None => Left(Seq(FormError(key, "error.enum", Nil))) } } } + def unbind(key: String, value: A) = Map(key -> value.entryName.toUpperCase) } diff --git a/enumeratum-play/src/main/scala/enumeratum/PlayFormFieldEnum.scala b/enumeratum-play/src/main/scala/enumeratum/PlayFormFieldEnum.scala index f2909e79..8b8cd24d 100644 --- a/enumeratum-play/src/main/scala/enumeratum/PlayFormFieldEnum.scala +++ b/enumeratum-play/src/main/scala/enumeratum/PlayFormFieldEnum.scala @@ -6,5 +6,5 @@ trait PlayFormFieldEnum[A <: EnumEntry] { self: Enum[A] => /** Form field for this enum */ - val formField: Mapping[A] = Forms.enum(self) + val formField: Mapping[A] = Forms.enumMapping(self) } diff --git a/enumeratum-play/src/main/scala/enumeratum/UrlBinders.scala b/enumeratum-play/src/main/scala/enumeratum/UrlBinders.scala index f6c8b959..b5120f2f 100644 --- a/enumeratum-play/src/main/scala/enumeratum/UrlBinders.scala +++ b/enumeratum-play/src/main/scala/enumeratum/UrlBinders.scala @@ -9,66 +9,73 @@ object UrlBinders { /** Builds a [[PathBindable]] A for a given Enum A * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false */ - def pathBinder[A <: EnumEntry](enum: Enum[A], insensitive: Boolean = false): PathBindable[A] = + def pathBinder[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A], + insensitive: Boolean = false + ): PathBindable[A] = new PathBindable[A] { def unbind(key: String, value: A): String = value.entryName def bind(key: String, value: String): Either[String, A] = { val maybeBound = - if (insensitive) enum.withNameInsensitiveOption(value) - else enum.withNameOption(value) + if (insensitive) e.withNameInsensitiveOption(value) + else e.withNameOption(value) maybeBound match { case Some(v) => Right(v) - case _ => Left(s"Unknown value supplied for ${enum.toString} '$value'") + case _ => Left(s"Unknown value supplied for ${e.toString} '$value'") } } } /** Builds a [[PathBindable]] A for a given Enum A that transforms to lower case * - * @param enum + * @param e * The enum */ - def pathBinderLowercaseOnly[A <: EnumEntry](enum: Enum[A]): PathBindable[A] = + def pathBinderLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): PathBindable[A] = new PathBindable[A] { def unbind(key: String, value: A): String = value.entryName.toLowerCase def bind(key: String, value: String): Either[String, A] = { - enum.withNameLowercaseOnlyOption(value) match { + e.withNameLowercaseOnlyOption(value) match { case Some(v) => Right(v) - case _ => Left(s"Unknown value supplied for ${enum.toString} '$value'") + case _ => Left(s"Unknown value supplied for ${e.toString} '$value'") } } } /** Builds a [[PathBindable]] A for a given Enum A that transforms to upper case * - * @param enum + * @param e * The enum */ - def pathBinderUppercaseOnly[A <: EnumEntry](enum: Enum[A]): PathBindable[A] = + def pathBinderUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): PathBindable[A] = new PathBindable[A] { def unbind(key: String, value: A): String = value.entryName.toUpperCase def bind(key: String, value: String): Either[String, A] = { - enum.withNameUppercaseOnlyOption(value) match { + e.withNameUppercaseOnlyOption(value) match { case Some(v) => Right(v) - case _ => Left(s"Unknown value supplied for ${enum.toString} '$value'") + case _ => Left(s"Unknown value supplied for ${e.toString} '$value'") } } } /** Builds a [[QueryStringBindable]] A for a given Enum A * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false */ def queryBinder[A <: EnumEntry]( - enum: Enum[A], + @deprecatedName(Symbol("enum")) e: Enum[A], insensitive: Boolean = false ): QueryStringBindable[A] = new QueryStringBindable[A] { @@ -77,12 +84,14 @@ object UrlBinders { def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, A]] = { params.get(key).flatMap(_.headOption).map { p => - val maybeBound = - if (insensitive) enum.withNameInsensitiveOption(p) - else enum.withNameOption(p) + val maybeBound = { + if (insensitive) e.withNameInsensitiveOption(p) + else e.withNameOption(p) + } + maybeBound match { case Some(v) => Right(v) - case _ => Left(s"Cannot parse parameter $key as an Enum: ${enum.toString}") + case _ => Left(s"Cannot parse parameter $key as an Enum: ${e.toString}") } } } @@ -90,10 +99,12 @@ object UrlBinders { /** Builds a [[QueryStringBindable]] A for a given Enum A that transforms to lower case * - * @param enum + * @param e * The enum */ - def queryBinderLowercaseOnly[A <: EnumEntry](enum: Enum[A]): QueryStringBindable[A] = + def queryBinderLowercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): QueryStringBindable[A] = new QueryStringBindable[A] { def unbind(key: String, value: A): String = @@ -101,9 +112,9 @@ object UrlBinders { def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, A]] = { params.get(key).flatMap(_.headOption).map { p => - enum.withNameLowercaseOnlyOption(p) match { + e.withNameLowercaseOnlyOption(p) match { case Some(v) => Right(v) - case _ => Left(s"Cannot parse parameter $key as an Enum: ${enum.toString}") + case _ => Left(s"Cannot parse parameter $key as an Enum: ${e.toString}") } } } @@ -111,10 +122,12 @@ object UrlBinders { /** Builds a [[QueryStringBindable]] A for a given Enum A that transforms to upper case * - * @param enum + * @param e * The enum */ - def queryBinderUppercaseOnly[A <: EnumEntry](enum: Enum[A]): QueryStringBindable[A] = + def queryBinderUppercaseOnly[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): QueryStringBindable[A] = new QueryStringBindable[A] { def unbind(key: String, value: A): String = @@ -122,9 +135,9 @@ object UrlBinders { def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, A]] = { params.get(key).flatMap(_.headOption).map { p => - enum.withNameUppercaseOnlyOption(p) match { + e.withNameUppercaseOnlyOption(p) match { case Some(v) => Right(v) - case _ => Left(s"Cannot parse parameter $key as an Enum: ${enum.toString}") + case _ => Left(s"Cannot parse parameter $key as an Enum: ${e.toString}") } } } diff --git a/enumeratum-play/src/main/scala/enumeratum/values/Forms.scala b/enumeratum-play/src/main/scala/enumeratum/values/Forms.scala index 809b9d1a..0d13040e 100644 --- a/enumeratum-play/src/main/scala/enumeratum/values/Forms.scala +++ b/enumeratum-play/src/main/scala/enumeratum/values/Forms.scala @@ -7,29 +7,17 @@ import play.api.data.{FormError, Mapping, Forms => PlayForms} * * Copyright 2016 */ -object Forms { +object Forms extends FormsCompat { - /** Returns a [[ValueEnum]] mapping for Play form fields - */ - def enum[ValueType, EntryType <: ValueEnumEntry[ValueType], EnumType <: ValueEnum[ - ValueType, - EntryType - ]](baseFormatter: Formatter[ValueType])( - enum: EnumType - ): Mapping[EntryType] = { - PlayForms.of(formatter(baseFormatter)(enum)) - } - - private[this] def formatter[ValueType, EntryType <: ValueEnumEntry[ + protected[this] def formatter[ValueType, EntryType <: ValueEnumEntry[ ValueType ], EnumType <: ValueEnum[ValueType, EntryType]]( baseFormatter: Formatter[ValueType] - )(enum: EnumType) = { + )(@deprecatedName(Symbol("enum")) e: EnumType) = { new Formatter[EntryType] { def bind(key: String, data: Map[String, String]): Either[Seq[FormError], EntryType] = baseFormatter.bind(key, data).right.flatMap { s => - val maybeBound = enum.withValueOpt(s) - maybeBound match { + e.withValueOpt(s) match { case Some(obj) => Right(obj) case None => Left(Seq(FormError(key, "error.enum", Nil))) } diff --git a/enumeratum-play/src/main/scala/enumeratum/values/PlayFormValueEnum.scala b/enumeratum-play/src/main/scala/enumeratum/values/PlayFormValueEnum.scala index 1345ffd9..e070d2d9 100644 --- a/enumeratum-play/src/main/scala/enumeratum/values/PlayFormValueEnum.scala +++ b/enumeratum-play/src/main/scala/enumeratum/values/PlayFormValueEnum.scala @@ -8,7 +8,7 @@ import play.api.data.Mapping * Copyright 2016 */ sealed trait PlayFormValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { - enum: ValueEnum[ValueType, EntryType] => + self: ValueEnum[ValueType, EntryType] => /** The [[Formatter]] for binding the ValueType of this ValueEnum. * @@ -18,48 +18,49 @@ sealed trait PlayFormValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType] /** Field for mapping this enum in Forms */ - lazy val formField: Mapping[EntryType] = Forms.enum(baseFormatter)(enum) + lazy val formField: Mapping[EntryType] = + Forms.enumMapping(baseFormatter)(self) } /** Form Bindable implicits for IntEnum */ trait IntPlayFormValueEnum[EntryType <: IntEnumEntry] extends PlayFormValueEnum[Int, EntryType] { - this: IntEnum[EntryType] => + _enum: IntEnum[EntryType] => protected val baseFormatter: Formatter[Int] = Formats.intFormat } /** Form Bindable implicits for LongEnum */ trait LongPlayFormValueEnum[EntryType <: LongEnumEntry] extends PlayFormValueEnum[Long, EntryType] { - this: LongEnum[EntryType] => + _enum: LongEnum[EntryType] => protected val baseFormatter: Formatter[Long] = Formats.longFormat } /** Form Bindable implicits for ShortEnum */ trait ShortPlayFormValueEnum[EntryType <: ShortEnumEntry] - extends PlayFormValueEnum[Short, EntryType] { this: ShortEnum[EntryType] => + extends PlayFormValueEnum[Short, EntryType] { _enum: ShortEnum[EntryType] => protected val baseFormatter: Formatter[Short] = Formats.shortFormat } /** Form Bindable implicits for StringEnum */ trait StringPlayFormValueEnum[EntryType <: StringEnumEntry] - extends PlayFormValueEnum[String, EntryType] { this: StringEnum[EntryType] => + extends PlayFormValueEnum[String, EntryType] { _enum: StringEnum[EntryType] => protected val baseFormatter: Formatter[String] = Formats.stringFormat } /** Form Bindable implicits for CharEnum */ trait CharPlayFormValueEnum[EntryType <: CharEnumEntry] extends PlayFormValueEnum[Char, EntryType] { - this: CharEnum[EntryType] => + _enum: CharEnum[EntryType] => protected val baseFormatter: Formatter[Char] = Forms.charFormatter } /** Form Bindable implicits for ByteEnum */ trait BytePlayFormValueEnum[EntryType <: ByteEnumEntry] extends PlayFormValueEnum[Byte, EntryType] { - this: ByteEnum[EntryType] => + _enum: ByteEnum[EntryType] => protected val baseFormatter: Formatter[Byte] = Formats.byteFormat } diff --git a/enumeratum-play/src/main/scala/enumeratum/values/PlayPathBindableValueEnum.scala b/enumeratum-play/src/main/scala/enumeratum/values/PlayPathBindableValueEnum.scala index c1f84155..804a1c42 100644 --- a/enumeratum-play/src/main/scala/enumeratum/values/PlayPathBindableValueEnum.scala +++ b/enumeratum-play/src/main/scala/enumeratum/values/PlayPathBindableValueEnum.scala @@ -8,7 +8,7 @@ import play.api.routing.sird.PathBindableExtractor * Copyright 2016 */ sealed trait PlayPathBindableValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { - enum: ValueEnum[ValueType, EntryType] => + _enum: ValueEnum[ValueType, EntryType] => /** Implicit path binder for Play's default router */ @@ -48,47 +48,47 @@ sealed trait PlayPathBindableValueEnum[ValueType, EntryType <: ValueEnumEntry[Va /** Path Bindable implicits for IntEnum */ trait IntPlayPathBindableValueEnum[EntryType <: IntEnumEntry] - extends PlayPathBindableValueEnum[Int, EntryType] { this: IntEnum[EntryType] => + extends PlayPathBindableValueEnum[Int, EntryType] { self: IntEnum[EntryType] => implicit val pathBindable: PathBindable[EntryType] = - UrlBinders.pathBinder(this) + UrlBinders.pathBinder(self) } /** Path Bindable implicits for LongEnum */ trait LongPlayPathBindableValueEnum[EntryType <: LongEnumEntry] - extends PlayPathBindableValueEnum[Long, EntryType] { this: LongEnum[EntryType] => + extends PlayPathBindableValueEnum[Long, EntryType] { self: LongEnum[EntryType] => implicit val pathBindable: PathBindable[EntryType] = - UrlBinders.pathBinder(this) + UrlBinders.pathBinder(self) } /** Path Bindable implicits for ShortEnum */ trait ShortPlayPathBindableValueEnum[EntryType <: ShortEnumEntry] - extends PlayPathBindableValueEnum[Short, EntryType] { this: ShortEnum[EntryType] => + extends PlayPathBindableValueEnum[Short, EntryType] { self: ShortEnum[EntryType] => implicit val pathBindable: PathBindable[EntryType] = - UrlBinders.pathBinder(this)(PathBindable.bindableInt.transform(_.toShort, _.toInt)) + UrlBinders.pathBinder(self)(PathBindable.bindableInt.transform(_.toShort, _.toInt)) } /** Path Bindable implicits for StringEnum */ trait StringPlayPathBindableValueEnum[EntryType <: StringEnumEntry] - extends PlayPathBindableValueEnum[String, EntryType] { this: StringEnum[EntryType] => + extends PlayPathBindableValueEnum[String, EntryType] { self: StringEnum[EntryType] => implicit val pathBindable: PathBindable[EntryType] = - UrlBinders.pathBinder(this)(PathBindable.bindableString) + UrlBinders.pathBinder(self)(PathBindable.bindableString) } /** Path Bindable implicits for CharEnum */ trait CharPlayPathBindableValueEnum[EntryType <: CharEnumEntry] - extends PlayPathBindableValueEnum[Char, EntryType] { this: CharEnum[EntryType] => + extends PlayPathBindableValueEnum[Char, EntryType] { self: CharEnum[EntryType] => implicit val pathBindable: PathBindable[EntryType] = - UrlBinders.pathBinder(this)(PathBindable.bindableChar) + UrlBinders.pathBinder(self)(PathBindable.bindableChar) } /** Path Bindable implicits for ByteEnum */ trait BytePlayPathBindableValueEnum[EntryType <: ByteEnumEntry] - extends PlayPathBindableValueEnum[Byte, EntryType] { this: ByteEnum[EntryType] => + extends PlayPathBindableValueEnum[Byte, EntryType] { self: ByteEnum[EntryType] => implicit val pathBindable: PathBindable[EntryType] = - UrlBinders.pathBinder(this)(PathBindable.bindableInt.transform(_.toByte, _.toInt)) + UrlBinders.pathBinder(self)(PathBindable.bindableInt.transform(_.toByte, _.toInt)) } diff --git a/enumeratum-play/src/main/scala/enumeratum/values/PlayQueryBindableValueEnum.scala b/enumeratum-play/src/main/scala/enumeratum/values/PlayQueryBindableValueEnum.scala index 7cf7ece5..983e45a8 100644 --- a/enumeratum-play/src/main/scala/enumeratum/values/PlayQueryBindableValueEnum.scala +++ b/enumeratum-play/src/main/scala/enumeratum/values/PlayQueryBindableValueEnum.scala @@ -7,7 +7,7 @@ import play.api.mvc.QueryStringBindable * Copyright 2016 */ sealed trait PlayQueryBindableValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { - enum: ValueEnum[ValueType, EntryType] => + _enum: ValueEnum[ValueType, EntryType] => /** Implicit path binder for Play's default router */ @@ -17,47 +17,47 @@ sealed trait PlayQueryBindableValueEnum[ValueType, EntryType <: ValueEnumEntry[V /** Query Bindable implicits for IntEnum */ trait IntPlayQueryBindableValueEnum[EntryType <: IntEnumEntry] - extends PlayQueryBindableValueEnum[Int, EntryType] { this: IntEnum[EntryType] => + extends PlayQueryBindableValueEnum[Int, EntryType] { self: IntEnum[EntryType] => implicit val queryBindable: QueryStringBindable[EntryType] = - UrlBinders.queryBinder(this) + UrlBinders.queryBinder(self) } /** Query Bindable implicits for LongEnum */ trait LongPlayQueryBindableValueEnum[EntryType <: LongEnumEntry] - extends PlayQueryBindableValueEnum[Long, EntryType] { this: LongEnum[EntryType] => + extends PlayQueryBindableValueEnum[Long, EntryType] { self: LongEnum[EntryType] => implicit val queryBindable: QueryStringBindable[EntryType] = - UrlBinders.queryBinder(this) + UrlBinders.queryBinder(self) } /** Query Bindable implicits for ShortEnum */ trait ShortPlayQueryBindableValueEnum[EntryType <: ShortEnumEntry] - extends PlayQueryBindableValueEnum[Short, EntryType] { this: ShortEnum[EntryType] => + extends PlayQueryBindableValueEnum[Short, EntryType] { self: ShortEnum[EntryType] => implicit val queryBindable: QueryStringBindable[EntryType] = - UrlBinders.queryBinder(this)(QueryStringBindable.bindableInt.transform(_.toShort, _.toInt)) + UrlBinders.queryBinder(self)(QueryStringBindable.bindableInt.transform(_.toShort, _.toInt)) } /** Query Bindable implicits for StringEnum */ trait StringPlayQueryBindableValueEnum[EntryType <: StringEnumEntry] - extends PlayQueryBindableValueEnum[String, EntryType] { this: StringEnum[EntryType] => + extends PlayQueryBindableValueEnum[String, EntryType] { self: StringEnum[EntryType] => implicit val queryBindable: QueryStringBindable[EntryType] = - UrlBinders.queryBinder(this)(QueryStringBindable.bindableString) + UrlBinders.queryBinder(self)(QueryStringBindable.bindableString) } /** Query Bindable implicits for CharEnum */ trait CharPlayQueryBindableValueEnum[EntryType <: CharEnumEntry] - extends PlayQueryBindableValueEnum[Char, EntryType] { this: CharEnum[EntryType] => + extends PlayQueryBindableValueEnum[Char, EntryType] { self: CharEnum[EntryType] => implicit val queryBindable: QueryStringBindable[EntryType] = - UrlBinders.queryBinder(this)(QueryStringBindable.bindableChar) + UrlBinders.queryBinder(self)(QueryStringBindable.bindableChar) } /** Query Bindable implicits for ByteEnum */ trait BytePlayQueryBindableValueEnum[EntryType <: ByteEnumEntry] - extends PlayQueryBindableValueEnum[Byte, EntryType] { this: ByteEnum[EntryType] => + extends PlayQueryBindableValueEnum[Byte, EntryType] { self: ByteEnum[EntryType] => implicit val queryBindable: QueryStringBindable[EntryType] = - UrlBinders.queryBinder(this)(QueryStringBindable.bindableInt.transform(_.toByte, _.toInt)) + UrlBinders.queryBinder(self)(QueryStringBindable.bindableInt.transform(_.toByte, _.toInt)) } diff --git a/enumeratum-play/src/main/scala/enumeratum/values/UrlBinders.scala b/enumeratum-play/src/main/scala/enumeratum/values/UrlBinders.scala index 4845383b..90dba312 100644 --- a/enumeratum-play/src/main/scala/enumeratum/values/UrlBinders.scala +++ b/enumeratum-play/src/main/scala/enumeratum/values/UrlBinders.scala @@ -11,18 +11,17 @@ object UrlBinders { /** Returns a [[PathBindable]] for the provided ValueEnum and base [[PathBindable]] */ def pathBinder[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseBindable: PathBindable[ValueType] ): PathBindable[EntryType] = new PathBindable[EntryType] { def bind(key: String, value: String): Either[String, EntryType] = baseBindable.bind(key, value).right.flatMap { b => - val maybeBound = enum.withValueOpt(b) - maybeBound match { + e.withValueOpt(b) match { case Some(obj) => Right(obj) case None => - Left(s"Unknown value supplied for ${enum.toString} '" + value + "'") + Left(s"Unknown value supplied for ${e.toString} '" + value + "'") } } @@ -33,7 +32,7 @@ object UrlBinders { /** Returns a [[QueryStringBindable]] for the provided ValueEnum and base [[PathBindable]] */ def queryBinder[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit baseBindable: QueryStringBindable[ValueType] ): QueryStringBindable[EntryType] = @@ -42,10 +41,9 @@ object UrlBinders { baseBindable .bind(key, params) .map(_.right.flatMap { s => - val maybeBound = enum.withValueOpt(s) - maybeBound match { + e.withValueOpt(s) match { case Some(obj) => Right(obj) - case None => Left(s"Unknown value supplied for ${enum.toString} '${s.toString}'") + case None => Left(s"Unknown value supplied for ${e.toString} '${s.toString}'") } }) } diff --git a/enumeratum-play/src/test/scala/enumeratum/FormSpec.scala b/enumeratum-play/src/test/scala/enumeratum/FormSpec.scala index b4396b79..2b30d8f9 100644 --- a/enumeratum-play/src/test/scala/enumeratum/FormSpec.scala +++ b/enumeratum-play/src/test/scala/enumeratum/FormSpec.scala @@ -7,13 +7,13 @@ import org.scalatest.OptionValues._ /** Created by Lloyd on 2/3/15. */ -class FormSpec extends AnyFunSpec with Matchers { +final class FormSpec extends AnyFunSpec with Matchers { import Forms._ describe(".enum") { - val subject = Form("hello" -> enum(Dummy)) + val subject = Form("hello" -> enumMapping(Dummy)) it("should bind proper strings into an Enum value") { val r1 = subject.bind(Map("hello" -> "A")) @@ -31,7 +31,7 @@ class FormSpec extends AnyFunSpec with Matchers { describe(".enum insensitive") { - val subject = Form("hello" -> enum(Dummy, true)) + val subject = Form("hello" -> enumMapping(Dummy, true)) it("should bind proper strings into an Enum value disregarding case") { val r1 = subject.bind(Map("hello" -> "A")) @@ -81,8 +81,7 @@ class FormSpec extends AnyFunSpec with Matchers { } it("should fail to bind random strings") { - val r = subject.bind(Map("hello" -> "a")) - r.value shouldBe None + subject.bind(Map("hello" -> "a")).value shouldBe None } } @@ -99,15 +98,12 @@ class FormSpec extends AnyFunSpec with Matchers { } it("should fail to bind random strings") { - val r = subject.bind("hello", Map("hello" -> "AARSE")) - r should be('left) + subject.bind("hello", Map("hello" -> "AARSE")) should be(Symbol("left")) } it("should unbind ") { - val r = subject.unbind("hello", Dummy.A) - r shouldBe Map("hello" -> "A") + subject.unbind("hello", Dummy.A) shouldBe Map("hello" -> "A") } - } describe(".format lower case") { @@ -122,13 +118,11 @@ class FormSpec extends AnyFunSpec with Matchers { } it("should fail to bind random strings") { - val r = subject.bind("hello", Map("hello" -> "A")) - r should be('left) + subject.bind("hello", Map("hello" -> "A")) should be(Symbol("left")) } it("should unbind ") { - val r = subject.unbind("hello", Dummy.A) - r shouldBe Map("hello" -> "a") + subject.unbind("hello", Dummy.A) shouldBe Map("hello" -> "a") } } @@ -144,13 +138,11 @@ class FormSpec extends AnyFunSpec with Matchers { } it("should fail to bind random strings") { - val r = subject.bind("hello", Map("hello" -> "a")) - r should be('left) + subject.bind("hello", Map("hello" -> "a")) should be(Symbol("left")) } it("should unbind ") { - val r = subject.unbind("hello", Dummy.A) - r shouldBe Map("hello" -> "A") + subject.unbind("hello", Dummy.A) shouldBe Map("hello" -> "A") } } @@ -170,8 +162,7 @@ class FormSpec extends AnyFunSpec with Matchers { } it("should fail to bind random strings") { - val r = subject.bind("hello", Map("hello" -> "AARSE")) - r should be('left) + subject.bind("hello", Map("hello" -> "AARSE")) should be(Symbol("left")) } it("should unbind ") { diff --git a/enumeratum-play/src/test/scala/enumeratum/PlayEnumSpec.scala b/enumeratum-play/src/test/scala/enumeratum/PlayEnumSpec.scala index e6e2084b..a992923c 100644 --- a/enumeratum-play/src/test/scala/enumeratum/PlayEnumSpec.scala +++ b/enumeratum-play/src/test/scala/enumeratum/PlayEnumSpec.scala @@ -12,11 +12,11 @@ import play.api.routing.sird.PathBindableExtractor import play.api.test.FakeRequest import enumeratum.helpers.ActionHelper -class PlayEnumSpec extends AnyFunSpec with Matchers { +final class PlayEnumSpec extends AnyFunSpec with Matchers { testScenarios( descriptor = "ordinary operation (no tarnsforms)", - enum = PlayDummyNormal, + myEnum = PlayDummyNormal, validTransforms = Map("A" -> PlayDummyNormal.A, "B" -> PlayDummyNormal.B, "c" -> PlayDummyNormal.c), expectedFailures = Seq("1.234"), @@ -28,7 +28,7 @@ class PlayEnumSpec extends AnyFunSpec with Matchers { testScenarios( descriptor = "lower case transformed", - enum = PlayDummyLowerOnly, + myEnum = PlayDummyLowerOnly, validTransforms = Map("a" -> PlayDummyLowerOnly.A, "b" -> PlayDummyLowerOnly.B, "c" -> PlayDummyLowerOnly.c), expectedFailures = Seq("C"), @@ -40,7 +40,7 @@ class PlayEnumSpec extends AnyFunSpec with Matchers { testScenarios( descriptor = "upper case transformed", - enum = PlayDummyUpperOnly, + myEnum = PlayDummyUpperOnly, validTransforms = Map("A" -> PlayDummyUpperOnly.A, "B" -> PlayDummyUpperOnly.B, "C" -> PlayDummyUpperOnly.c), expectedFailures = Seq("c"), @@ -52,7 +52,7 @@ class PlayEnumSpec extends AnyFunSpec with Matchers { private def testScenarios[A <: EnumEntry: Format]( descriptor: String, - enum: Enum[A], + myEnum: Enum[A], validTransforms: Map[String, A], expectedFailures: Seq[String], formMapping: Mapping[A], @@ -67,7 +67,7 @@ class PlayEnumSpec extends AnyFunSpec with Matchers { def testJson(): Unit = { - val failures: Seq[JsValue] = expectedFailures.map(JsString) ++ Seq( + val failures: Seq[JsValue] = expectedFailures.map(JsString(_)) ++ Seq( JsString("AVADSGDSAFA"), JsNumber(Int.MaxValue) ) @@ -176,6 +176,7 @@ class PlayEnumSpec extends AnyFunSpec with Matchers { validTransforms.foreach { case (k, v) => router.routes.isDefinedAt(FakeRequest(HttpVerbs.GET, s"/$k")) shouldBe true } + expectedErrors.foreach { v => router.routes.isDefinedAt(FakeRequest(HttpVerbs.GET, s"/$v")) shouldBe false } @@ -197,7 +198,9 @@ class PlayEnumSpec extends AnyFunSpec with Matchers { it("should not bind strings not found in the enumeration") { expectedErrors.foreach { v => - queryStringBindable.bind("hello", Map("hello" -> Seq(v))).value shouldBe 'left + queryStringBindable.bind("hello", Map("hello" -> Seq(v))).value shouldBe Symbol( + "left" + ) queryStringBindable.bind("hello", Map("helloz" -> Seq(v))) shouldBe None } } diff --git a/enumeratum-play/src/test/scala/enumeratum/UrlBindersSpec.scala b/enumeratum-play/src/test/scala/enumeratum/UrlBindersSpec.scala index 9683757f..b4977181 100644 --- a/enumeratum-play/src/test/scala/enumeratum/UrlBindersSpec.scala +++ b/enumeratum-play/src/test/scala/enumeratum/UrlBindersSpec.scala @@ -135,7 +135,7 @@ class UrlBindersSpec extends AnyFunSpec with Matchers { it( "should create an enumeration binder that cannot bind strings not found in the enumeration" ) { - subject.bind("hello", Map("hello" -> Seq("Z"))).value should be('left) + subject.bind("hello", Map("hello" -> Seq("Z"))).value should be(Symbol("left")) subject.bind("hello", Map("helloz" -> Seq("A"))) shouldBe None } @@ -165,7 +165,7 @@ class UrlBindersSpec extends AnyFunSpec with Matchers { it( "should create an enumeration binder that cannot bind strings not found in the enumeration" ) { - subject.bind("hello", Map("hello" -> Seq("Z"))).value should be('left) + subject.bind("hello", Map("hello" -> Seq("Z"))).value should be(Symbol("left")) subject.bind("hello", Map("helloz" -> Seq("A"))) shouldBe None } @@ -189,14 +189,14 @@ class UrlBindersSpec extends AnyFunSpec with Matchers { it( "should create an enumeration binder that cannot bind strings not found in the enumeration" ) { - subject.bind("hello", Map("hello" -> Seq("Z"))).value should be('left) + subject.bind("hello", Map("hello" -> Seq("Z"))).value should be(Symbol("left")) subject.bind("hello", Map("helloz" -> Seq("a"))) shouldBe None } it( "should create an enumeration binder that cannot bind strings that aren't lower case but are mixed case" ) { - subject.bind("hello", Map("hello" -> Seq("A"))).value should be('left) + subject.bind("hello", Map("hello" -> Seq("A"))).value should be(Symbol("left")) } it("should create an enumeration binder that can unbind values") { @@ -224,14 +224,14 @@ class UrlBindersSpec extends AnyFunSpec with Matchers { it( "should create an enumeration binder that cannot bind strings not found in the enumeration" ) { - subject.bind("hello", Map("hello" -> Seq("Z"))).value should be('left) + subject.bind("hello", Map("hello" -> Seq("Z"))).value should be(Symbol("left")) subject.bind("hello", Map("helloz" -> Seq("A"))) shouldBe None } it( "should create an enumeration binder that cannot bind strings that aren't upper case but are mixed case" ) { - subject.bind("hello", Map("hello" -> Seq("a"))).value should be('left) + subject.bind("hello", Map("hello" -> Seq("a"))).value should be(Symbol("left")) } it("should create an enumeration binder that can unbind values") { diff --git a/enumeratum-play/src/test/scala/enumeratum/values/PlayValueEnumHelpers.scala b/enumeratum-play/src/test/scala/enumeratum/values/PlayValueEnumHelpers.scala index 7364e1c6..432adc43 100644 --- a/enumeratum-play/src/test/scala/enumeratum/values/PlayValueEnumHelpers.scala +++ b/enumeratum-play/src/test/scala/enumeratum/values/PlayValueEnumHelpers.scala @@ -20,7 +20,7 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with ValueType ], ValueType <: AnyVal: Numeric: Format]( enumKind: String, - enum: ValueEnum[ValueType, EntryType] + myEnum: ValueEnum[ValueType, EntryType] with PlayFormValueEnum[ValueType, EntryType] with PlayPathBindableValueEnum[ValueType, EntryType] with PlayQueryBindableValueEnum[ValueType, EntryType] @@ -29,8 +29,8 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with val numeric = implicitly[Numeric[ValueType]] testPlayEnum( enumKind, - enum, - { i: ValueType => + myEnum, + { (i: ValueType) => JsNumber(numeric.toInt(i)) } ) @@ -39,36 +39,33 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with def testPlayEnum[EntryType <: ValueEnumEntry[ValueType], ValueType: Format]( enumKind: String, - enum: ValueEnum[ValueType, EntryType] + myEnum: ValueEnum[ValueType, EntryType] with PlayFormValueEnum[ValueType, EntryType] with PlayPathBindableValueEnum[ValueType, EntryType] with PlayQueryBindableValueEnum[ValueType, EntryType] with PlayJsonValueEnum[ValueType, EntryType], jsWrapper: ValueType => JsValue ) = { - describe(enumKind) { describe("Form binding") { - val subject = Form("hello" -> enum.formField) + val subject = Form("hello" -> myEnum.formField) it("should bind proper strings into an Enum value") { - enum.values.foreach { entry => - val r = subject.bind(Map("hello" -> s"${entry.value}")) - r.value.value shouldBe entry + myEnum.values.foreach { entry => + subject.bind(Map("hello" -> s"${entry.value}")).value.value shouldBe entry } } it("should fail to bind random strings") { - val r1 = subject.bind(Map("hello" -> "AARS143515123E")) - val r2 = subject.bind(Map("hello" -> s"${Int.MaxValue}")) - r1.value shouldBe None - r2.value shouldBe None + subject.bind(Map("hello" -> "AARS143515123E")).value shouldBe None + + subject.bind(Map("hello" -> s"${Int.MaxValue}")).value shouldBe None } it("should unbind") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => val r = subject.mapping.unbind(entry) r shouldBe Map("hello" -> s"${entry.value}") } @@ -80,10 +77,10 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with describe("PathBindable") { - val subject = enum.pathBindable + val subject = myEnum.pathBindable it("should bind strings corresponding to enum values") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => subject.bind("hello", s"${entry.value}").right.value shouldBe entry } } @@ -94,7 +91,7 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with } it("should unbind values") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => subject.unbind("hello", entry) shouldBe entry.value.toString } } @@ -103,10 +100,10 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with describe("PathBindableExtractor") { - val subject = enum.fromPath + val subject = myEnum.fromPath it("should extract strings corresponding to enum values") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => subject.unapply(s"${entry.value}") shouldBe Some(entry) } } @@ -120,8 +117,8 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with import play.api.routing.sird._ import play.api.routing._ import play.api.mvc._ - enum.values.foreach { entry => - val router = Router.from { case GET(p"/${enum.fromPath(greeting)}") => + myEnum.values.foreach { entry => + val router = Router.from { case GET(p"/${myEnum.fromPath(greeting)}") => ActionHelper { Results.Ok(s"$greeting") } @@ -139,10 +136,10 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with describe("QueryStringBindable") { - val subject = enum.queryBindable + val subject = myEnum.queryBindable it("should bind strings corresponding to enum values regardless of case") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => subject .bind("hello", Map("hello" -> Seq(s"${entry.value}"))) .value @@ -152,13 +149,15 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with } it("should not bind strings not found as values in the enumeration") { - subject.bind("hello", Map("hello" -> Seq("Z"))).value shouldBe 'left - subject.bind("hello", Map("hello" -> Seq(s"${Int.MaxValue}"))).value shouldBe 'left + subject.bind("hello", Map("hello" -> Seq("Z"))).value shouldBe Symbol("left") + subject.bind("hello", Map("hello" -> Seq(s"${Int.MaxValue}"))).value shouldBe Symbol( + "left" + ) subject.bind("hello", Map("helloz" -> Seq("1"))) shouldBe None } it("should unbind values") { - enum.values.foreach { entry => + myEnum.values.foreach { entry => subject.unbind("hello", entry) shouldBe s"hello=${entry.value}" } } @@ -166,7 +165,7 @@ trait PlayValueEnumHelpers extends EnumJsonFormatHelpers { this: AnyFunSpec with } describe("JSON formats") { - testFormats(enumKind, enum, jsWrapper, Some(enum.format)) + testFormats(enumKind, myEnum, jsWrapper, Some(myEnum.format)) } } diff --git a/enumeratum-play/src/test/scala/enumeratum/values/PlayValueEnumSpec.scala b/enumeratum-play/src/test/scala/enumeratum/values/PlayValueEnumSpec.scala index 36c94399..cb789e26 100644 --- a/enumeratum-play/src/test/scala/enumeratum/values/PlayValueEnumSpec.scala +++ b/enumeratum-play/src/test/scala/enumeratum/values/PlayValueEnumSpec.scala @@ -14,18 +14,18 @@ class PlayValueEnumSpec extends AnyFunSpec with Matchers with PlayValueEnumHelpe testNumericPlayEnum("LongPlayEnum", PlayContentType) testNumericPlayEnum("ShortPlayEnum", PlayDrinks) testNumericPlayEnum("IntPlayEnum", PlayLibraryItem) - testPlayEnum("StringPlayEnum", PlayOperatingSystem, JsString) + testPlayEnum("StringPlayEnum", PlayOperatingSystem, JsString(_)) testPlayEnum( "BytePlayEnum", PlayBites, - { s: Byte => + { (s: Byte) => JsNumber(s.toInt) } ) testPlayEnum( "CharPlayEnum", PlayAlphabet, - { s: Char => + { (s: Char) => JsString(s"$s") } ) diff --git a/project/plugins.sbt b/project/plugins.sbt index d75ced21..3c8a6920 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,15 +1,14 @@ -resolvers ++= Seq( - Classpaths.sbtPluginReleases -) +resolvers += Classpaths.sbtPluginReleases -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") -addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.6.0") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") -addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.9.9") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") +addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") +addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.9.9") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") addSbtPlugin(("org.scoverage" % "sbt-scoverage" % "2.0.2").exclude("org.scala-lang.modules", "*")) From e96bf140bd6f570854330390b16ef63e3d4eb79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Wed, 7 Sep 2022 23:36:02 +0200 Subject: [PATCH 08/16] Update doobie/quill --- build.sbt | 79 +++++++----- .../src/main/scala/enumeratum/Doobie.scala | 4 +- .../main/scala/enumeratum/values/Doobie.scala | 4 +- .../enumeratum/values/DoobieValueEnum.scala | 2 +- .../src/main/scala/enumeratum/Quill.scala | 10 +- .../main/scala/enumeratum/values/Quill.scala | 6 +- .../enumeratum/values/QuillValueEnum.scala | 2 +- .../test/scala/enumeratum/QuillEnumSpec.scala | 22 ++-- .../values/QuillValueEnumSpec.scala | 120 +++++++++--------- 9 files changed, 133 insertions(+), 116 deletions(-) diff --git a/build.sbt b/build.sbt index f3f0787b..2da308b0 100644 --- a/build.sbt +++ b/build.sbt @@ -16,15 +16,6 @@ lazy val scalaTestVersion = "3.2.9" // Library versions lazy val reactiveMongoVersion = "1.1.0-RC7-SNAPSHOT" lazy val json4sVersion = "4.0.3" -lazy val quillVersion = "4.1.0" - -def theDoobieVersion(scalaVersion: String) = - CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor <= 11 => "0.7.1" - case Some(_) => "1.0.0-RC2" - case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Doobie") - } def theArgonautVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { @@ -43,14 +34,6 @@ def theSlickVersion(scalaVersion: String) = throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Slick") } -def theCatsVersion(scalaVersion: String) = - CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor <= 11 => "2.0.0" - case Some(_) => "2.6.1" - case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Cats") - } - def thePlayJsonVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor <= 11 => "2.7.3" @@ -393,7 +376,8 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu val dep = "com.typesafe.play" %% "play" % "2.8.0" if (scalaBinaryVersion.value == "3") { - dep.exclude("org.scala-lang.modules", "*") + dep + .exclude("org.scala-lang.modules", "*") .exclude("com.typesafe.play", "play-json_2.13") .cross(CrossVersion.for3Use2_13) } else { @@ -577,9 +561,18 @@ lazy val enumeratumQuill = version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies ++= { + val (core, ver) = { + if (scalaBinaryVersion.value == "3") { + "quill-engine" -> "4.4.0" + } else { + "quill-core" -> "4.1.0" + } + } + Seq( - "io.getquill" %%% "quill-core" % quillVersion, - "io.getquill" %%% "quill-sql" % quillVersion % Test + "io.getquill" %%% core % ver, + "io.getquill" %%% "quill-sql" % ver % Test, + scalaXmlTest.value ) }, libraryDependencies ++= { @@ -589,15 +582,17 @@ lazy val enumeratumQuill = Seq("com.beachape" %%% "enumeratum" % Versions.Core.stable) } }, - dependencyOverrides ++= { - def pprintVersion(v: String) = - if (v startsWith "2.11") "0.5.4" else "0.5.5" + dependencyOverrides += { + val ver = scalaBinaryVersion.value match { + case "3" => "0.7.3" + case "2.11" => "0.5.4" + case _ => "0.5.5" + } - Seq( - "com.lihaoyi" %%% "pprint" % pprintVersion(scalaVersion.value) - ) + "com.lihaoyi" %%% "pprint" % ver } ) + // lazy val enumeratumQuillJs = enumeratumQuill.js // TODO re-enable once quill supports Scala.js 1.0 lazy val enumeratumQuillJvm = enumeratumQuill.jvm.configure(configureWithLocal(coreJVM)) @@ -607,10 +602,19 @@ lazy val enumeratumDoobie = .settings(testSettings) .settings( crossScalaVersions := scalaVersionsAll, - version := "1.7.2-SNAPSHOT", + version := Versions.Macros.head, libraryDependencies += { - "org.tpolecat" %% "doobie-core" % theDoobieVersion(scalaVersion.value) + val ver = { + if (scalaBinaryVersion.value == "2.11") { + "0.7.1" + } else { + "1.0.0-RC2" + } + } + + "org.tpolecat" %% "doobie-core" % ver }, + libraryDependencies += scalaXmlTest.value, libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -655,8 +659,17 @@ lazy val enumeratumCats = crossProject(JSPlatform, JVMPlatform) name := "enumeratum-cats", version := Versions.Core.head, libraryDependencies += { - "org.typelevel" %%% "cats-core" % theCatsVersion(scalaVersion.value) + val ver = { + if (scalaBinaryVersion.value == "2.11") { + "2.0.0" + } else { + "2.6.1" + } + } + + "org.typelevel" %%% "cats-core" % ver }, + libraryDependencies += scalaXmlTest.value, libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -837,17 +850,17 @@ val jsTestSettings = { lazy val benchmarking = Project(id = "benchmarking", base = file("benchmarking")) - .settings(commonWithPublishSettings: _*) + .settings(commonWithPublishSettings) .settings( - name := "benchmarking", - crossVersion := CrossVersion.binary, + name := "benchmarking", + crossVersion := CrossVersion.binary, + libraryDependencies += "org.slf4j" % "slf4j-simple" % "1.7.21", // Do not publish publishArtifact := false, publishLocal := {} ) .dependsOn((baseProjectRefs ++ integrationProjectRefs).map(ClasspathDependency(_, None)): _*) .enablePlugins(JmhPlugin) - .settings(libraryDependencies += "org.slf4j" % "slf4j-simple" % "1.7.21") /** Helper function to add unmanaged source compat directories for different scala versions */ diff --git a/enumeratum-doobie/src/main/scala/enumeratum/Doobie.scala b/enumeratum-doobie/src/main/scala/enumeratum/Doobie.scala index 3301c507..a04c3fb0 100644 --- a/enumeratum-doobie/src/main/scala/enumeratum/Doobie.scala +++ b/enumeratum-doobie/src/main/scala/enumeratum/Doobie.scala @@ -7,6 +7,6 @@ object Doobie { /** Returns an Encoder for the given enum */ - def meta[A <: EnumEntry](enum: Enum[A]): Meta[A] = - Meta[String].imap(enum.withName)(_.entryName) + def meta[A <: EnumEntry](@deprecatedName(Symbol("enum")) e: Enum[A]): Meta[A] = + Meta[String].imap(e.withName)(_.entryName) } diff --git a/enumeratum-doobie/src/main/scala/enumeratum/values/Doobie.scala b/enumeratum-doobie/src/main/scala/enumeratum/values/Doobie.scala index ce578b65..90518104 100644 --- a/enumeratum-doobie/src/main/scala/enumeratum/values/Doobie.scala +++ b/enumeratum-doobie/src/main/scala/enumeratum/values/Doobie.scala @@ -6,10 +6,10 @@ import doobie.Meta object Doobie { def meta[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] )(implicit get: Get[ValueType], put: Put[ValueType] ): Meta[EntryType] = - new Meta[ValueType](get, put).imap(enum.withValue)(_.value) + new Meta[ValueType](get, put).imap(e.withValue)(_.value) } diff --git a/enumeratum-doobie/src/main/scala/enumeratum/values/DoobieValueEnum.scala b/enumeratum-doobie/src/main/scala/enumeratum/values/DoobieValueEnum.scala index 67f18eab..e32fd0cd 100644 --- a/enumeratum-doobie/src/main/scala/enumeratum/values/DoobieValueEnum.scala +++ b/enumeratum-doobie/src/main/scala/enumeratum/values/DoobieValueEnum.scala @@ -63,7 +63,7 @@ trait CharDoobieEnum[EntryType <: CharEnumEntry] extends DoobieValueEnum[Char, E this: ValueEnum[Char, EntryType] => implicit val meta: Meta[EntryType] = - Meta[String].imap(str => withValue(str.charAt(0)))(enum => String.valueOf(enum.value)) + Meta[String].imap(str => withValue(str.charAt(0)))(e => String.valueOf(e.value)) } /** DoobieEnum for ByteEnumEntry diff --git a/enumeratum-quill/src/main/scala/enumeratum/Quill.scala b/enumeratum-quill/src/main/scala/enumeratum/Quill.scala index 02ac8e15..83d0068c 100644 --- a/enumeratum-quill/src/main/scala/enumeratum/Quill.scala +++ b/enumeratum-quill/src/main/scala/enumeratum/Quill.scala @@ -6,11 +6,15 @@ object Quill { /** Returns an Encoder for the given enum */ - def encoder[A <: EnumEntry](enum: Enum[A]): MappedEncoding[A, String] = + def encoder[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): MappedEncoding[A, String] = MappedEncoding(_.entryName) /** Returns a Decoder for the given enum */ - def decoder[A <: EnumEntry](enum: Enum[A]): MappedEncoding[String, A] = - MappedEncoding(enum.withName) + def decoder[A <: EnumEntry]( + @deprecatedName(Symbol("enum")) e: Enum[A] + ): MappedEncoding[String, A] = + MappedEncoding(e.withName) } diff --git a/enumeratum-quill/src/main/scala/enumeratum/values/Quill.scala b/enumeratum-quill/src/main/scala/enumeratum/values/Quill.scala index eabae102..f1aace88 100644 --- a/enumeratum-quill/src/main/scala/enumeratum/values/Quill.scala +++ b/enumeratum-quill/src/main/scala/enumeratum/values/Quill.scala @@ -7,12 +7,12 @@ object Quill { /** Returns an Encoder for the provided ValueEnum */ def encoder[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] ): MappedEncoding[EntryType, ValueType] = MappedEncoding(_.value) /** Returns a Decoder for the provided ValueEnum */ def decoder[ValueType, EntryType <: ValueEnumEntry[ValueType]]( - enum: ValueEnum[ValueType, EntryType] - ): MappedEncoding[ValueType, EntryType] = MappedEncoding(enum.withValue) + @deprecatedName(Symbol("enum")) e: ValueEnum[ValueType, EntryType] + ): MappedEncoding[ValueType, EntryType] = MappedEncoding(e.withValue) } diff --git a/enumeratum-quill/src/main/scala/enumeratum/values/QuillValueEnum.scala b/enumeratum-quill/src/main/scala/enumeratum/values/QuillValueEnum.scala index 5e67e36f..bb597e82 100644 --- a/enumeratum-quill/src/main/scala/enumeratum/values/QuillValueEnum.scala +++ b/enumeratum-quill/src/main/scala/enumeratum/values/QuillValueEnum.scala @@ -79,7 +79,7 @@ trait CharQuillEnum[EntryType <: CharEnumEntry] extends QuillValueEnum[Char, Ent * String instead. */ implicit val quillEncoder: MappedEncoding[EntryType, String] = - MappedEncoding(enum => String.valueOf(enum.value)) + MappedEncoding(e => String.valueOf(e.value)) /** Because all existing Quill contexts do not have built-in Decoders for Char, convert it from a * String instead. diff --git a/enumeratum-quill/src/test/scala/enumeratum/QuillEnumSpec.scala b/enumeratum-quill/src/test/scala/enumeratum/QuillEnumSpec.scala index 86f1bad5..96acb024 100644 --- a/enumeratum-quill/src/test/scala/enumeratum/QuillEnumSpec.scala +++ b/enumeratum-quill/src/test/scala/enumeratum/QuillEnumSpec.scala @@ -5,28 +5,28 @@ import org.scalatest.matchers.should.Matchers import scala.collection.immutable -class QuillEnumSpec extends AnyFunSpec with Matchers { +final class QuillEnumSpec extends AnyFunSpec with Matchers { describe("A QuillEnum") { // we only need to test whether it can compile because Quill will fail compilation if an Encoder is not found it("should encode to String") { """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillShirt].insert(_.size -> lift(QuillShirtSize.Small: QuillShirtSize))) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillShirt].insert(_.size -> lift(QuillShirtSize.Small: QuillShirtSize))) + """ should compile } // we only need to test whether it can compile because Quill will fail compilation if a Decoder is not found it("should decode from String") { """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillShirt]) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillShirt]) + """ should compile } } diff --git a/enumeratum-quill/src/test/scala/enumeratum/values/QuillValueEnumSpec.scala b/enumeratum-quill/src/test/scala/enumeratum/values/QuillValueEnumSpec.scala index dbd76a1c..dff74bae 100644 --- a/enumeratum-quill/src/test/scala/enumeratum/values/QuillValueEnumSpec.scala +++ b/enumeratum-quill/src/test/scala/enumeratum/values/QuillValueEnumSpec.scala @@ -12,21 +12,21 @@ class QuillValueEnumSpec extends AnyFunSpec with Matchers { it("should encode to Int") { // we only need to test whether it can compile because Quill will fail compilation if an Encoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillBorrowerToLibraryItem].insert(_.borrower -> "Foo", _.item -> lift(QuillLibraryItem.Book: QuillLibraryItem))) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillBorrowerToLibraryItem].insert(_.borrower -> "Foo", _.item -> lift(QuillLibraryItem.Book: QuillLibraryItem))) + """ should compile } it("should decode from Int") { // we only need to test whether it can compile because Quill will fail compilation if a Decoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillBorrowerToLibraryItem]) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillBorrowerToLibraryItem]) + """ should compile } } @@ -36,21 +36,21 @@ class QuillValueEnumSpec extends AnyFunSpec with Matchers { it("should encode to Long") { // we only need to test whether it can compile because Quill will fail compilation if an Encoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillContent].insert(_.`type` -> lift(QuillContentType.Image: QuillContentType))) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillContent].insert(_.`type` -> lift(QuillContentType.Image: QuillContentType))) + """ should compile } it("should decode from Long") { // we only need to test whether it can compile because Quill will fail compilation if a Decoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillContent]) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillContent]) + """ should compile } } @@ -60,21 +60,21 @@ class QuillValueEnumSpec extends AnyFunSpec with Matchers { it("should encode to Short") { // we only need to test whether it can compile because Quill will fail compilation if an Encoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillDrinkManufacturer].insert(_.name -> "Coca-Cola", _.drink -> lift(QuillDrink.Cola: QuillDrink))) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillDrinkManufacturer].insert(_.name -> "Coca-Cola", _.drink -> lift(QuillDrink.Cola: QuillDrink))) + """ should compile } it("should decode from Short") { // we only need to test whether it can compile because Quill will fail compilation if a Decoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillDrinkManufacturer]) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillDrinkManufacturer]) + """ should compile } } @@ -84,21 +84,21 @@ class QuillValueEnumSpec extends AnyFunSpec with Matchers { it("should encode to String") { // we only need to test whether it can compile because Quill will fail compilation if an Encoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillComputer].insert(_.operatingSystem -> lift(QuillOperatingSystem.Windows: QuillOperatingSystem))) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillComputer].insert(_.operatingSystem -> lift(QuillOperatingSystem.Windows: QuillOperatingSystem))) + """ should compile } it("should decode from String") { // we only need to test whether it can compile because Quill will fail compilation if a Decoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillComputer]) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillComputer]) + """ should compile } } @@ -108,21 +108,21 @@ class QuillValueEnumSpec extends AnyFunSpec with Matchers { it("should encode to Char") { // we only need to test whether it can compile because Quill will fail compilation if an Encoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillName].insert(_.name -> "Daniel", _.initials -> lift(QuillAlphabet.D: QuillAlphabet))) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillName].insert(_.name -> "Daniel", _.initials -> lift(QuillAlphabet.D: QuillAlphabet))) + """ should compile } it("should decode from Char") { // we only need to test whether it can compile because Quill will fail compilation if a Decoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillName]) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillName]) + """ should compile } } @@ -132,21 +132,21 @@ class QuillValueEnumSpec extends AnyFunSpec with Matchers { it("should encode to Byte") { // we only need to test whether it can compile because Quill will fail compilation if an Encoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillChar].insert(_.byte1 -> lift(QuillByte.ThreeByte: QuillByte), _.byte2 -> lift(QuillByte.TwoByte: QuillByte))) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillChar].insert(_.byte1 -> lift(QuillByte.ThreeByte: QuillByte), _.byte2 -> lift(QuillByte.TwoByte: QuillByte))) + """ should compile } it("should decode from Byte") { // we only need to test whether it can compile because Quill will fail compilation if a Decoder is not found """ - | import io.getquill._ - | val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) - | import ctx._ - | ctx.run(query[QuillChar]) - """.stripMargin should compile +import io.getquill._ +val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal) +import ctx._ +ctx.run(query[QuillChar]) + """ should compile } } From ba6803b03d1aee836e74014a4cd35e413fb14bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Thu, 8 Sep 2022 10:40:59 +0200 Subject: [PATCH 09/16] Fix --- build.sbt | 2 +- .../main/scala-3/enumeratum/FormsCompat.scala | 44 +++++++++++++++++++ .../enumeratum/values/FormsCompat.scala | 16 +++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 enumeratum-play/src/main/scala-3/enumeratum/FormsCompat.scala create mode 100644 enumeratum-play/src/main/scala-3/enumeratum/values/FormsCompat.scala diff --git a/build.sbt b/build.sbt index 2da308b0..122e3851 100644 --- a/build.sbt +++ b/build.sbt @@ -373,7 +373,7 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu version := Versions.Core.head, crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version, scala_3Version), libraryDependencies += { - val dep = "com.typesafe.play" %% "play" % "2.8.0" + val dep = ("com.typesafe.play" %% "play" % "2.8.0").exclude("org.scala-lang.modules", "*") if (scalaBinaryVersion.value == "3") { dep diff --git a/enumeratum-play/src/main/scala-3/enumeratum/FormsCompat.scala b/enumeratum-play/src/main/scala-3/enumeratum/FormsCompat.scala new file mode 100644 index 00000000..b9ce999f --- /dev/null +++ b/enumeratum-play/src/main/scala-3/enumeratum/FormsCompat.scala @@ -0,0 +1,44 @@ +package enumeratum + +import play.api.data.{Mapping, Forms => PlayForms} + +private[enumeratum] trait FormsCompat { _self: Forms.type => + + /** Returns an [[Enum]] mapping + * + * Example: + * + * {{{ + * scala> import enumeratum._ + * scala> import play.api.data.Form + * + * scala> sealed trait Greeting extends EnumEntry + * + * scala> object Greeting extends Enum[Greeting] { + * | val values = findValues + * | case object Hello extends Greeting + * | case object GoodBye extends Greeting + * | case object Hi extends Greeting + * | case object Bye extends Greeting + * | } + * + * scala> val form = Form("greeting" -> Forms.enum(Greeting)) + * scala> form.bind(Map("greeting" -> "Hello")).value + * res0: Option[Greeting] = Some(Hello) + * + * scala> val formInsensitive = Form("greeting" -> Forms.enum(Greeting, true)) + * scala> formInsensitive.bind(Map("greeting" -> "hElLo")).value + * res1: Option[Greeting] = Some(Hello) + * }}} + * + * @param e + * The enum + * @param insensitive + * bind in a case-insensitive way, defaults to false + */ + def enumMapping[A <: EnumEntry]( + e: Enum[A], + insensitive: Boolean = false + ): Mapping[A] = + PlayForms.of(format(e, insensitive)) +} diff --git a/enumeratum-play/src/main/scala-3/enumeratum/values/FormsCompat.scala b/enumeratum-play/src/main/scala-3/enumeratum/values/FormsCompat.scala new file mode 100644 index 00000000..0eef12a5 --- /dev/null +++ b/enumeratum-play/src/main/scala-3/enumeratum/values/FormsCompat.scala @@ -0,0 +1,16 @@ +package enumeratum.values + +import play.api.data.{Mapping, Forms => PlayForms} +import play.api.data.format.Formatter + +private[values] trait FormsCompat { _self: Forms.type => + + /** Returns a [[ValueEnum]] mapping for Play form fields + */ + def enumMapping[ValueType, EntryType <: ValueEnumEntry[ValueType], EnumType <: ValueEnum[ + ValueType, + EntryType + ]](baseFormatter: Formatter[ValueType])(e: EnumType): Mapping[EntryType] = + PlayForms.of(formatter(baseFormatter)(e)) + +} From 44d1fd334252a4d31ff00b12b97d17a144c19401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Thu, 8 Sep 2022 22:02:14 +0200 Subject: [PATCH 10/16] Disable Slick --- build.sbt | 119 +++++++++--------- .../enumeratum/SlickEnumColumnSupport.scala | 14 +-- .../enumeratum/SlickEnumPlainSqlSupport.scala | 43 ++++--- .../values/SlickValueEnumColumnSupport.scala | 6 +- .../SlickValueEnumPlainSqlSupport.scala | 101 +++++++++------ .../scala-2/enumeratum/FindValEnums.scala | 11 ++ .../scala-3/enumeratum/FindValEnums.scala | 9 ++ .../scala/enumeratum/CompilationSpec.scala | 8 -- project/plugins.sbt | 2 +- 9 files changed, 180 insertions(+), 133 deletions(-) create mode 100644 macros/src/test/scala-2/enumeratum/FindValEnums.scala create mode 100644 macros/src/test/scala-3/enumeratum/FindValEnums.scala diff --git a/build.sbt b/build.sbt index 122e3851..8067795f 100644 --- a/build.sbt +++ b/build.sbt @@ -13,27 +13,6 @@ lazy val theScalaVersion = scala_2_12Version lazy val scalaTestVersion = "3.2.9" -// Library versions -lazy val reactiveMongoVersion = "1.1.0-RC7-SNAPSHOT" -lazy val json4sVersion = "4.0.3" - -def theArgonautVersion(scalaVersion: String) = - CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor >= 11 => "6.2.5" - case Some(_) => "6.3.8" - - case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Argonaut") - } - -def theSlickVersion(scalaVersion: String) = - CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor)) if scalaMajor <= 11 => "3.3.3" - case Some(_) => "3.3.3" - case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for Slick") - } - def thePlayJsonVersion(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor <= 11 => "2.7.3" @@ -44,21 +23,6 @@ def thePlayJsonVersion(scalaVersion: String) = throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion for play-json") } -def theCirceVersion(scalaVersion: String) = - CrossVersion.partialVersion(scalaVersion) match { - case Some((3, _)) => "0.14.1" - case Some((2, scalaMajor)) if scalaMajor >= 12 => "0.14.1" - case Some((2, scalaMajor)) if scalaMajor >= 11 => "0.11.1" - case _ => - throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion") - } - -def theScalacheckVersion(scalaVersion: String) = - CrossVersion.partialVersion(scalaVersion) match { - case Some((2, 11)) => "1.15.2" - case _ => "1.15.4" - } - def scalaTestPlay(scalaVersion: String) = CrossVersion.partialVersion(scalaVersion) match { case Some((2, scalaMajor)) if scalaMajor >= 12 => "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test @@ -309,7 +273,7 @@ lazy val enumeratumReactiveMongoBson = version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies += { - "org.reactivemongo" %% "reactivemongo-bson-api" % reactiveMongoVersion % Provided + "org.reactivemongo" %% "reactivemongo-bson-api" % "1.1.0-RC7-SNAPSHOT" % Provided }, libraryDependencies += scalaXmlTest.value, libraryDependencies ++= { @@ -371,7 +335,7 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu .settings(testSettings) .settings( version := Versions.Core.head, - crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version, scala_3Version), + crossScalaVersions := scalaVersionsAll.filter(_ != scala_2_11Version), libraryDependencies += { val dep = ("com.typesafe.play" %% "play" % "2.8.0").exclude("org.scala-lang.modules", "*") @@ -419,10 +383,20 @@ lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) .settings( name := "enumeratum-circe", version := Versions.Core.head, - libraryDependencies ++= Seq( - "io.circe" %%% "circe-core" % theCirceVersion(scalaVersion.value), - scalaXmlTest.value - ), + libraryDependencies ++= { + val ver: String = { + if (scalaBinaryVersion.value == "2.11") { + "0.11.1" + } else { + "0.14.1" + } + } + + Seq( + "io.circe" %%% "circe-core" % ver, + scalaXmlTest.value + ) + }, libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -435,7 +409,7 @@ lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform) crossScalaVersions := scalaVersionsAll ) .jsSettings( - crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version) + crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version, scala_3Version) ) lazy val enumeratumCirceJs = enumeratumCirce.js @@ -458,10 +432,20 @@ lazy val enumeratumArgonaut = crossProject(JSPlatform, JVMPlatform) name := "enumeratum-argonaut", version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, - libraryDependencies ++= Seq( - "io.argonaut" %%% "argonaut" % theArgonautVersion(scalaVersion.value), - scalaXmlTest.value - ), + libraryDependencies ++= { + val ver: String = { + if (scalaBinaryVersion.value == "3") { + "6.3.8" + } else { + "6.2.5" + } + } + + Seq( + "io.argonaut" %%% "argonaut" % ver, + scalaXmlTest.value + ) + }, libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -485,11 +469,15 @@ lazy val enumeratumJson4s = .settings( version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, - libraryDependencies ++= Seq( - "org.json4s" %% "json4s-core" % json4sVersion, - "org.json4s" %% "json4s-native" % json4sVersion % Test, - scalaXmlTest.value - ), + libraryDependencies ++= { + val ver = "4.0.3" + + Seq( + "org.json4s" %% "json4s-core" % ver, + "org.json4s" %% "json4s-native" % ver % Test, + scalaXmlTest.value + ) + }, libraryDependencies ++= { if (useLocalVersion) { Seq.empty @@ -515,8 +503,16 @@ lazy val enumeratumScalacheck = crossProject(JSPlatform, JVMPlatform) version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies ++= { + val ver: String = { + if (scalaBinaryVersion.value == "2.11") { + "1.15.2" + } else { + "1.15.4" + } + } + Seq( - "org.scalacheck" %%% "scalacheck" % theScalacheckVersion(scalaVersion.value), + "org.scalacheck" %%% "scalacheck" % ver, "org.scalatestplus" %%% "scalacheck-1-14" % "3.1.1.1" % Test ).map( _.exclude("org.scala-lang.modules", "*") @@ -633,8 +629,8 @@ lazy val enumeratumSlick = version := Versions.Core.head, crossScalaVersions := scalaVersionsAll, libraryDependencies ++= Seq( - "com.typesafe.slick" %% "slick" % theSlickVersion(scalaVersion.value), - "com.h2database" % "h2" % "1.4.197" % Test + ("com.typesafe.slick" %% "slick" % "3.3.3").cross(CrossVersion.for3Use2_13), + "com.h2database" % "h2" % "1.4.197" % Test ), libraryDependencies ++= { if (useLocalVersion) { @@ -642,6 +638,17 @@ lazy val enumeratumSlick = } else { Seq("com.beachape" %% "enumeratum" % Versions.Core.stable) } + }, + doctestScalaTestVersion := Some(scalaTestVersion), + // TODO: Remove once Slick is published for Dotty + sourceDirectory := { + if (scalaBinaryVersion.value == "3") new java.io.File("/no/sources") + else sourceDirectory.value + }, + publishArtifact := (scalaBinaryVersion.value != "3"), + publishLocal := { + if (publishArtifact.value) ({}) + else publishLocal.value } ) .configure(configureWithLocal(coreJVM)) @@ -682,7 +689,7 @@ lazy val enumeratumCats = crossProject(JSPlatform, JVMPlatform) crossScalaVersions := scalaVersionsAll ) .jsSettings( - crossScalaVersions := Seq(scala_2_12Version, scala_2_13Version) + crossScalaVersions := scalaVersionsAll.filter(_ != scala_2_11Version) ) lazy val enumeratumCatsJs = enumeratumCats.js.configure(configureWithLocal(coreJS)) diff --git a/enumeratum-slick/src/main/scala/enumeratum/SlickEnumColumnSupport.scala b/enumeratum-slick/src/main/scala/enumeratum/SlickEnumColumnSupport.scala index 14bd4d39..a5941798 100644 --- a/enumeratum-slick/src/main/scala/enumeratum/SlickEnumColumnSupport.scala +++ b/enumeratum-slick/src/main/scala/enumeratum/SlickEnumColumnSupport.scala @@ -23,41 +23,41 @@ import scala.reflect.ClassTag */ trait SlickEnumColumnSupport { - val profile: slick.profile.RelationalProfile + val profile: slick.relational.RelationalProfile def mappedColumnTypeForEnum[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] )(implicit tag: ClassTag[E]): profile.BaseColumnType[E] = { /* This import has a purpose - it brings the proper implicit profile.BaseColumnType[String] into scope */ import profile.api._ profile.MappedColumnType.base[E, String]( { _.entryName }, - { enum.namesToValuesMap } + { e.namesToValuesMap } ) } def mappedColumnTypeForLowercaseEnum[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] )(implicit tag: ClassTag[E]): profile.BaseColumnType[E] = { /* This import has a purpose - it brings the proper implicit profile.BaseColumnType[String] into scope */ import profile.api._ profile.MappedColumnType.base[E, String]( { _.entryName.toLowerCase }, - { enum.lowerCaseNamesToValuesMap } + { e.lowerCaseNamesToValuesMap } ) } def mappedColumnTypeForUppercaseEnum[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] )(implicit tag: ClassTag[E]): profile.BaseColumnType[E] = { /* This import has a purpose - it brings the proper implicit profile.BaseColumnType[String] into scope */ import profile.api._ profile.MappedColumnType.base[E, String]( { _.entryName.toUpperCase }, - { enum.upperCaseNameValuesToMap } + { e.upperCaseNameValuesToMap } ) } } diff --git a/enumeratum-slick/src/main/scala/enumeratum/SlickEnumPlainSqlSupport.scala b/enumeratum-slick/src/main/scala/enumeratum/SlickEnumPlainSqlSupport.scala index db3d53c1..eb47d0c5 100644 --- a/enumeratum-slick/src/main/scala/enumeratum/SlickEnumPlainSqlSupport.scala +++ b/enumeratum-slick/src/main/scala/enumeratum/SlickEnumPlainSqlSupport.scala @@ -49,7 +49,7 @@ trait SlickEnumPlainSqlSupport { } def setParameterForEnum[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] ): SetParameter[E] = { /* Implementation note: the enum argument is not used directly, but is used for type inference - if it wasn't required the caller would have to pass a type @@ -58,22 +58,25 @@ trait SlickEnumPlainSqlSupport { */ _makeSetParameter(identity) } + def optionalSetParameterForEnum[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter(identity) def setParameterForEnumLowercase[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] ): SetParameter[E] = _makeSetParameter(_.toLowerCase) + def optionalSetParameterForEnumLowercase[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter(_.toLowerCase) def setParameterForEnumUppercase[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] ): SetParameter[E] = _makeSetParameter(_.toUpperCase) + def optionalSetParameterForEnumUppercase[E <: EnumEntry]( - enum: Enum[E] + @deprecatedName(Symbol("enum")) e: Enum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter(_.toUpperCase) private def _makeGetResult[E <: EnumEntry]( @@ -83,6 +86,7 @@ trait SlickEnumPlainSqlSupport { override def apply(pr: PositionedResult): E = find(pr.nextString) } } + private def _makeOptionalGetResult[E <: EnumEntry]( find: String => E ): GetResult[Option[E]] = { @@ -94,25 +98,28 @@ trait SlickEnumPlainSqlSupport { } def getResultForEnum[E <: EnumEntry]( - enum: Enum[E] - ): GetResult[E] = _makeGetResult[E](enum.withName(_)) + @deprecatedName(Symbol("enum")) e: Enum[E] + ): GetResult[E] = _makeGetResult[E](e.withName(_)) + def optionalGetResultForEnum[E <: EnumEntry]( - enum: Enum[E] - ): GetResult[Option[E]] = _makeOptionalGetResult(enum.withName(_)) + @deprecatedName(Symbol("enum")) e: Enum[E] + ): GetResult[Option[E]] = _makeOptionalGetResult(e.withName(_)) def getResultForEnumLowercase[E <: EnumEntry]( - enum: Enum[E] - ): GetResult[E] = _makeGetResult(enum.withNameLowercaseOnly(_)) + @deprecatedName(Symbol("enum")) e: Enum[E] + ): GetResult[E] = _makeGetResult(e.withNameLowercaseOnly(_)) + def optionalGetResultForEnumLowercase[E <: EnumEntry]( - enum: Enum[E] - ): GetResult[Option[E]] = _makeOptionalGetResult(enum.withNameLowercaseOnly(_)) + @deprecatedName(Symbol("enum")) e: Enum[E] + ): GetResult[Option[E]] = _makeOptionalGetResult(e.withNameLowercaseOnly(_)) def getResultForEnumUppercase[E <: EnumEntry]( - enum: Enum[E] - ): GetResult[E] = _makeGetResult(enum.withNameUppercaseOnly(_)) + @deprecatedName(Symbol("enum")) e: Enum[E] + ): GetResult[E] = _makeGetResult(e.withNameUppercaseOnly(_)) + def optionalGetResultForEnumUppercase[E <: EnumEntry]( - enum: Enum[E] - ): GetResult[Option[E]] = _makeOptionalGetResult(enum.withNameUppercaseOnly(_)) + @deprecatedName(Symbol("enum")) e: Enum[E] + ): GetResult[Option[E]] = _makeOptionalGetResult(e.withNameUppercaseOnly(_)) } diff --git a/enumeratum-slick/src/main/scala/enumeratum/values/SlickValueEnumColumnSupport.scala b/enumeratum-slick/src/main/scala/enumeratum/values/SlickValueEnumColumnSupport.scala index 6231ad57..9e7a099f 100644 --- a/enumeratum-slick/src/main/scala/enumeratum/values/SlickValueEnumColumnSupport.scala +++ b/enumeratum-slick/src/main/scala/enumeratum/values/SlickValueEnumColumnSupport.scala @@ -23,17 +23,17 @@ import scala.reflect.ClassTag */ trait SlickValueEnumColumnSupport { - val profile: slick.profile.RelationalProfile + val profile: slick.relational.RelationalProfile def mappedColumnTypeForValueEnum[V, E <: ValueEnumEntry[V]]( - enum: ValueEnum[V, E] + @deprecatedName(Symbol("enum")) e: ValueEnum[V, E] )(implicit tag: ClassTag[E], valueColumnType: profile.BaseColumnType[V] ): profile.BaseColumnType[E] = { profile.MappedColumnType.base[E, V]( { _.value }, - { enum.withValue(_) } + { e.withValue(_) } ) } diff --git a/enumeratum-slick/src/main/scala/enumeratum/values/SlickValueEnumPlainSqlSupport.scala b/enumeratum-slick/src/main/scala/enumeratum/values/SlickValueEnumPlainSqlSupport.scala index 2e7093c0..16fa1132 100644 --- a/enumeratum-slick/src/main/scala/enumeratum/values/SlickValueEnumPlainSqlSupport.scala +++ b/enumeratum-slick/src/main/scala/enumeratum/values/SlickValueEnumPlainSqlSupport.scala @@ -46,7 +46,7 @@ trait SlickValueEnumPlainSqlSupport { } def setParameterForIntEnum[E <: IntEnumEntry]( - enum: IntEnum[E] + @deprecatedName(Symbol("enum")) e: IntEnum[E] ): SetParameter[E] = { /* Implementation note: the enum argument is not used directly, but is used for type inference - if it wasn't required the caller would have to pass a type @@ -55,110 +55,131 @@ trait SlickValueEnumPlainSqlSupport { */ _makeSetParameter[Int, E](_.setInt(_)) } + def optionalSetParameterForIntEnum[E <: IntEnumEntry]( - enum: IntEnum[E] + @deprecatedName(Symbol("enum")) e: IntEnum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter[Int, E](_.setIntOption(_)) + def setParameterForLongEnum[E <: LongEnumEntry]( - enum: LongEnum[E] + @deprecatedName(Symbol("enum")) e: LongEnum[E] ): SetParameter[E] = _makeSetParameter[Long, E](_.setLong(_)) + def optionalSetParameterForLongEnum[E <: LongEnumEntry]( - enum: LongEnum[E] + @deprecatedName(Symbol("enum")) e: LongEnum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter[Long, E](_.setLongOption(_)) + def setParameterForShortEnum[E <: ShortEnumEntry]( - enum: ShortEnum[E] + @deprecatedName(Symbol("enum")) e: ShortEnum[E] ): SetParameter[E] = _makeSetParameter[Short, E](_.setShort(_)) + def optionalSetParameterForShortEnum[E <: ShortEnumEntry]( - enum: ShortEnum[E] + @deprecatedName(Symbol("enum")) e: ShortEnum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter[Short, E](_.setShortOption(_)) + def setParameterForStringEnum[E <: StringEnumEntry]( - enum: StringEnum[E] + @deprecatedName(Symbol("enum")) e: StringEnum[E] ): SetParameter[E] = _makeSetParameter[String, E](_.setString(_)) + def optionalSetParameterForStringEnum[E <: StringEnumEntry]( - enum: StringEnum[E] + @deprecatedName(Symbol("enum")) e: StringEnum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter[String, E](_.setStringOption(_)) + def setParameterForByteEnum[E <: ByteEnumEntry]( - enum: ByteEnum[E] + @deprecatedName(Symbol("enum")) e: ByteEnum[E] ): SetParameter[E] = _makeSetParameter[Byte, E](_.setByte(_)) + def optionalSetParameterForByteEnum[E <: ByteEnumEntry]( - enum: ByteEnum[E] + @deprecatedName(Symbol("enum")) e: ByteEnum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter[Byte, E](_.setByteOption(_)) + def setParameterForCharEnum[E <: CharEnumEntry]( - enum: CharEnum[E] + @deprecatedName(Symbol("enum")) e: CharEnum[E] ): SetParameter[E] = _makeSetParameter[Char, E]({ (pp, char) => pp.setString(char.toString) }) def optionalSetParameterForCharEnum[E <: CharEnumEntry]( - enum: CharEnum[E] + @deprecatedName(Symbol("enum")) e: CharEnum[E] ): SetParameter[Option[E]] = _makeOptionalSetParameter[Char, E]({ (pp, char) => pp.setStringOption(char.map(_.toString)) }) private def _makeGetResult[V, E <: ValueEnumEntry[V]]( - enum: ValueEnum[V, E], + @deprecatedName(Symbol("enum")) e: ValueEnum[V, E], get: PositionedResult => V ): GetResult[E] = { new GetResult[E] { - override def apply(pr: PositionedResult): E = enum.valuesToEntriesMap(get(pr)) + override def apply(pr: PositionedResult): E = e.valuesToEntriesMap(get(pr)) } } private def _makeOptionalGetResult[V, E <: ValueEnumEntry[V]]( - enum: ValueEnum[V, E], + @deprecatedName(Symbol("enum")) e: ValueEnum[V, E], get: PositionedResult => Option[V] ): GetResult[Option[E]] = { new GetResult[Option[E]] { override def apply(pr: PositionedResult): Option[E] = { - get(pr).map(v => enum.valuesToEntriesMap(v)) + get(pr).map(v => e.valuesToEntriesMap(v)) } } } def getResultForIntEnum[E <: IntEnumEntry]( - enum: IntEnum[E] - ): GetResult[E] = _makeGetResult[Int, E](enum, _.nextInt) + @deprecatedName(Symbol("enum")) e: IntEnum[E] + ): GetResult[E] = _makeGetResult[Int, E](e, _.nextInt) + def optionalGetResultForIntEnum[E <: IntEnumEntry]( - enum: IntEnum[E] - ): GetResult[Option[E]] = _makeOptionalGetResult[Int, E](enum, _.nextIntOption) + @deprecatedName(Symbol("enum")) e: IntEnum[E] + ): GetResult[Option[E]] = _makeOptionalGetResult[Int, E](e, _.nextIntOption) + def getResultForLongEnum[E <: LongEnumEntry]( - enum: LongEnum[E] - ): GetResult[E] = _makeGetResult[Long, E](enum, _.nextLong) + @deprecatedName(Symbol("enum")) e: LongEnum[E] + ): GetResult[E] = _makeGetResult[Long, E](e, _.nextLong) + def optionalGetResultForLongEnum[E <: LongEnumEntry]( - enum: LongEnum[E] - ): GetResult[Option[E]] = _makeOptionalGetResult[Long, E](enum, _.nextLongOption) + @deprecatedName(Symbol("enum")) e: LongEnum[E] + ): GetResult[Option[E]] = _makeOptionalGetResult[Long, E](e, _.nextLongOption) + def getResultForShortEnum[E <: ShortEnumEntry]( - enum: ShortEnum[E] - ): GetResult[E] = _makeGetResult[Short, E](enum, { _.nextShort }) + @deprecatedName(Symbol("enum")) e: ShortEnum[E] + ): GetResult[E] = _makeGetResult[Short, E](e, { _.nextShort }) + def optionalGetResultForShortEnum[E <: ShortEnumEntry]( - enum: ShortEnum[E] - ): GetResult[Option[E]] = _makeOptionalGetResult[Short, E](enum, { _.nextShortOption }) + @deprecatedName(Symbol("enum")) e: ShortEnum[E] + ): GetResult[Option[E]] = _makeOptionalGetResult[Short, E](e, { _.nextShortOption }) + def getResultForStringEnum[E <: StringEnumEntry]( - enum: StringEnum[E] - ): GetResult[E] = _makeGetResult[String, E](enum, { _.nextString }) + @deprecatedName(Symbol("enum")) e: StringEnum[E] + ): GetResult[E] = _makeGetResult[String, E](e, { _.nextString }) + def optionalGetResultForStringEnum[E <: StringEnumEntry]( - enum: StringEnum[E] - ): GetResult[Option[E]] = _makeOptionalGetResult[String, E](enum, { _.nextStringOption }) + @deprecatedName(Symbol("enum")) e: StringEnum[E] + ): GetResult[Option[E]] = _makeOptionalGetResult[String, E](e, { _.nextStringOption }) + def getResultForByteEnum[E <: ByteEnumEntry]( - enum: ByteEnum[E] - ): GetResult[E] = _makeGetResult[Byte, E](enum, { _.nextByte }) + @deprecatedName(Symbol("enum")) e: ByteEnum[E] + ): GetResult[E] = _makeGetResult[Byte, E](e, { _.nextByte }) + def optionalGetResultForByteEnum[E <: ByteEnumEntry]( - enum: ByteEnum[E] - ): GetResult[Option[E]] = _makeOptionalGetResult[Byte, E](enum, { _.nextByteOption }) + @deprecatedName(Symbol("enum")) e: ByteEnum[E] + ): GetResult[Option[E]] = _makeOptionalGetResult[Byte, E](e, { _.nextByteOption }) + def getResultForCharEnum[E <: CharEnumEntry]( - enum: CharEnum[E] + @deprecatedName(Symbol("enum")) e: CharEnum[E] ): GetResult[E] = _makeGetResult[Char, E]( - enum, + e, { pr => pr.nextString.head } ) + def optionalGetResultForCharEnum[E <: CharEnumEntry]( - enum: CharEnum[E] + @deprecatedName(Symbol("enum")) e: CharEnum[E] ): GetResult[Option[E]] = _makeOptionalGetResult[Char, E]( - enum, + e, { pr => pr.nextStringOption.map(_.head) } diff --git a/macros/src/test/scala-2/enumeratum/FindValEnums.scala b/macros/src/test/scala-2/enumeratum/FindValEnums.scala new file mode 100644 index 00000000..1a80d94e --- /dev/null +++ b/macros/src/test/scala-2/enumeratum/FindValEnums.scala @@ -0,0 +1,11 @@ +package enumeratum + +import scala.language.experimental.macros + +/** Created by Lloyd on 1/4/17. + * + * Copyright 2017 + */ +object FindValEnums { + def apply[A]: IndexedSeq[A] = macro ValueEnumMacros.findIntValueEntriesImpl[A] +} diff --git a/macros/src/test/scala-3/enumeratum/FindValEnums.scala b/macros/src/test/scala-3/enumeratum/FindValEnums.scala new file mode 100644 index 00000000..33bdaef0 --- /dev/null +++ b/macros/src/test/scala-3/enumeratum/FindValEnums.scala @@ -0,0 +1,9 @@ +package enumeratum + +/** Created by Lloyd on 1/4/17. + * + * Copyright 2017 + */ +object FindValEnums { + inline def apply[A]: IndexedSeq[A] = ${ ValueEnumMacros.findIntValueEntriesImpl[A] } +} diff --git a/macros/src/test/scala/enumeratum/CompilationSpec.scala b/macros/src/test/scala/enumeratum/CompilationSpec.scala index 07d013a6..8fa943b0 100644 --- a/macros/src/test/scala/enumeratum/CompilationSpec.scala +++ b/macros/src/test/scala/enumeratum/CompilationSpec.scala @@ -19,14 +19,6 @@ class CompilationSpec extends AnyFunSpec with Matchers { } -/** Created by Lloyd on 1/4/17. - * - * Copyright 2017 - */ -object FindValEnums { - def apply[A]: IndexedSeq[A] = macro ValueEnumMacros.findIntValueEntriesImpl[A] -} - sealed abstract class A private (val value: Int) { val text: String } diff --git a/project/plugins.sbt b/project/plugins.sbt index 3c8a6920..e0733499 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,7 +3,7 @@ resolvers += Classpaths.sbtPluginReleases addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") -addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.9.9") +addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.10.0") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") From c907f6c359783f6e05498170e8e2e3ff49327a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Thu, 8 Sep 2022 22:32:12 +0200 Subject: [PATCH 11/16] Disable JSON4S --- build.sbt | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/build.sbt b/build.sbt index 8067795f..fc9ab85c 100644 --- a/build.sbt +++ b/build.sbt @@ -486,6 +486,10 @@ lazy val enumeratumJson4s = } } ) + .settings( + // TODO: Remove once JSON4S is fixed for Scala3; + // https://github.com/json4s/json4s/issues/1035 + disabledSettings) .configure(configureWithLocal(coreJVM)) // ScalaCheck @@ -638,19 +642,10 @@ lazy val enumeratumSlick = } else { Seq("com.beachape" %% "enumeratum" % Versions.Core.stable) } - }, - doctestScalaTestVersion := Some(scalaTestVersion), - // TODO: Remove once Slick is published for Dotty - sourceDirectory := { - if (scalaBinaryVersion.value == "3") new java.io.File("/no/sources") - else sourceDirectory.value - }, - publishArtifact := (scalaBinaryVersion.value != "3"), - publishLocal := { - if (publishArtifact.value) ({}) - else publishLocal.value } ) + .settings( // TODO: Remove once Slick is published for Dotty + disabledSettings) .configure(configureWithLocal(coreJVM)) // Cats @@ -848,12 +843,23 @@ val testSettings = { ) } -val jsTestSettings = { - Seq( - coverageEnabled := false, // Disable until Scala.js 1.0 support is there https://github.com/scoverage/scalac-scoverage-plugin/pull/287 - doctestGenTests := Seq.empty - ) -} +val jsTestSettings = Seq( + coverageEnabled := false, // Disable until Scala.js 1.0 support is there https://github.com/scoverage/scalac-scoverage-plugin/pull/287 + doctestGenTests := Seq.empty +) + +lazy val disabledSettings = Seq( + doctestScalaTestVersion := Some(scalaTestVersion), + sourceDirectory := { + if (scalaBinaryVersion.value == "3") new java.io.File("/no/sources") + else sourceDirectory.value + }, + publishArtifact := (scalaBinaryVersion.value != "3"), + publishLocal := { + if (publishArtifact.value) ({}) + else publishLocal.value + } +) lazy val benchmarking = Project(id = "benchmarking", base = file("benchmarking")) From bb5e42e2fdc2d802cfb4d0f323ab1e12ecfa26f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Thu, 8 Sep 2022 22:51:38 +0200 Subject: [PATCH 12/16] Fix test dependencies --- build.sbt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index fc9ab85c..365f0362 100644 --- a/build.sbt +++ b/build.sbt @@ -166,7 +166,8 @@ lazy val macros = crossProject(JSPlatform, JVMPlatform) } else { "org.scala-lang" % "scala-reflect" % scalaVersion.value } - } + }, + libraryDependencies += scalaXmlTest.value ) lazy val macrosJS = macros.js @@ -192,7 +193,8 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) } else { Seq("com.beachape" %% "enumeratum-macros" % Versions.Macros.stable) } - } + }, + libraryDependencies += scalaXmlTest.value ) def configureWithLocal( @@ -251,6 +253,7 @@ lazy val coreJVMTests = Project(id = "coreJVMTests", base = file("enumeratum-cor "org.scala-lang" % "scala-compiler" % scalaVersion.value % Test } }, + libraryDependencies += scalaXmlTest.value, publishArtifact := false, publishLocal := {} ) @@ -489,7 +492,8 @@ lazy val enumeratumJson4s = .settings( // TODO: Remove once JSON4S is fixed for Scala3; // https://github.com/json4s/json4s/issues/1035 - disabledSettings) + disabledSettings + ) .configure(configureWithLocal(coreJVM)) // ScalaCheck @@ -645,7 +649,8 @@ lazy val enumeratumSlick = } ) .settings( // TODO: Remove once Slick is published for Dotty - disabledSettings) + disabledSettings + ) .configure(configureWithLocal(coreJVM)) // Cats From 79aeb2aa1e170f45c77a7f69bb6bc87ffd71cc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Mon, 31 Oct 2022 15:23:31 +0100 Subject: [PATCH 13/16] Review --- .../src/test/scala-2/enumeratum/EnumSpecCompat.scala | 2 +- .../src/test/scala-3/enumeratum/EnumSpecCompat.scala | 11 ++++++++++- .../src/test/scala/enumeratum/EnumSpec.scala | 2 ++ .../src/main/scala-2/enumeratum/FormsCompat.scala | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/enumeratum-core/src/test/scala-2/enumeratum/EnumSpecCompat.scala b/enumeratum-core/src/test/scala-2/enumeratum/EnumSpecCompat.scala index 081bef59..759f54bd 100644 --- a/enumeratum-core/src/test/scala-2/enumeratum/EnumSpecCompat.scala +++ b/enumeratum-core/src/test/scala-2/enumeratum/EnumSpecCompat.scala @@ -1,7 +1,7 @@ package enumeratum private[enumeratum] trait EnumSpecCompat { _: EnumSpec => - describe("Scala2 in") { + def scalaCompat = describe("Scala2 in") { it( "should fail to compile if either enum in the parameter list is not instance of the same enum type as the checked one" ) { diff --git a/enumeratum-core/src/test/scala-3/enumeratum/EnumSpecCompat.scala b/enumeratum-core/src/test/scala-3/enumeratum/EnumSpecCompat.scala index 2daa5d72..11577eb4 100644 --- a/enumeratum-core/src/test/scala-3/enumeratum/EnumSpecCompat.scala +++ b/enumeratum-core/src/test/scala-3/enumeratum/EnumSpecCompat.scala @@ -1,3 +1,12 @@ package enumeratum -private[enumeratum] trait EnumSpecCompat { spec: EnumSpec => } +private[enumeratum] trait EnumSpecCompat { spec: EnumSpec => + def scalaCompat = describe("Scala3 in") { + it( + "should compile if either enum in the parameter list is not instance of the same enum type as the checked one" + ) { + val myEnum: DummyEnum = DummyEnum.Hi + myEnum.in(DummyEnum.Hello, SnakeEnum.ShoutGoodBye) shouldBe false + } + } +} diff --git a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala index df678785..e35ec19a 100644 --- a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala +++ b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala @@ -582,4 +582,6 @@ class EnumSpec extends AnyFunSpec with Matchers with EnumSpecCompat { Result.withNameInsensitive("ok") shouldBe Result.Good } } + + scalaCompat } diff --git a/enumeratum-play/src/main/scala-2/enumeratum/FormsCompat.scala b/enumeratum-play/src/main/scala-2/enumeratum/FormsCompat.scala index 915c3762..a47363d5 100644 --- a/enumeratum-play/src/main/scala-2/enumeratum/FormsCompat.scala +++ b/enumeratum-play/src/main/scala-2/enumeratum/FormsCompat.scala @@ -37,7 +37,7 @@ private[enumeratum] trait FormsCompat { _: Forms.type => * res1: Option[Greeting] = Some(Hello) * }}} * - * @param enum + * @param e * The enum * @param insensitive * bind in a case-insensitive way, defaults to false From ee55e8528008e2157b1485e9d287c98978eb05dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Chantepie?= Date: Sat, 26 Nov 2022 21:40:54 +0100 Subject: [PATCH 14/16] Review & rebase --- .github/workflows/ci.yml | 10 ++--- build.sbt | 6 +-- .../src/test/scala/enumeratum/EnumSpec.scala | 6 +++ .../src/test/scala/enumeratum/Models.scala | 13 +++++++ .../main/scala-3/enumeratum/EnumMacros.scala | 37 +++++++++---------- project/build.properties | 2 +- project/plugins.sbt | 2 +- 7 files changed, 46 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9c8781e..20e2bc56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,11 +13,11 @@ jobs: - java: 11 scala: 2.11.12 - java: 11 - scala: 2.12.16 + scala: 2.12.17 - java: 11 - scala: 2.13.8 + scala: 2.13.10 - java: 11 - scala: 3.2.1-RC1 + scala: 3.2.1 runs-on: ubuntu-latest env: SCALAJS_TEST_OPT: full @@ -42,7 +42,7 @@ jobs: sbt -v ++${{ matrix.scala }} scalafmtCheck scalafmtSbtCheck scala_2_11/test:compile scala_2_11/test:doc sbt -v ++${{ matrix.scala }} scala_2_11/test ;; - 2.12.16) + 2.12.17) sbt -v ++${{ matrix.scala }} test:compile test:doc sbt -v ++${{ matrix.scala }} coverage test coverageReport sbt -v ++${{ matrix.scala }} coverageAggregate @@ -58,7 +58,7 @@ jobs: find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true find $HOME/.sbt -name "*.lock" -delete || true - name: Upload coverage to Codecov - if: ${{ matrix.scala == '2.12.16' }} + if: ${{ matrix.scala == '2.12.17' }} uses: codecov/codecov-action@v2 with: fail_ci_if_error: true diff --git a/build.sbt b/build.sbt index 365f0362..bfa3858f 100644 --- a/build.sbt +++ b/build.sbt @@ -3,9 +3,9 @@ import sbtbuildinfo.BuildInfoPlugin.autoImport._ 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" +lazy val scala_2_12Version = "2.12.17" +lazy val scala_2_13Version = "2.13.10" +lazy val scala_3Version = "3.2.1" lazy val scalaVersionsAll = Seq(scala_2_11Version, scala_2_12Version, scala_2_13Version, scala_3Version) diff --git a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala index e35ec19a..05ac0c6c 100644 --- a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala +++ b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala @@ -37,6 +37,12 @@ class EnumSpec extends AnyFunSpec with Matchers with EnumSpecCompat { ) ) } + + it("should contain instance of subclass") { + import Inheritance._ + + Word.values should be(IndexedSeq(Word.Hello, Word.Hi)) + } } describe("#withName") { diff --git a/enumeratum-core/src/test/scala/enumeratum/Models.scala b/enumeratum-core/src/test/scala/enumeratum/Models.scala index c575cd9c..abc6beb8 100644 --- a/enumeratum-core/src/test/scala/enumeratum/Models.scala +++ b/enumeratum-core/src/test/scala/enumeratum/Models.scala @@ -305,3 +305,16 @@ object InTheWoods { } } + +object Inheritance { + sealed trait Word extends EnumEntry + + object Word extends Enum[Word] { + sealed class Greeting extends Word + + lazy val values = findValues + + case object Hello extends Greeting + case object Hi extends Greeting + } +} diff --git a/macros/src/main/scala-3/enumeratum/EnumMacros.scala b/macros/src/main/scala-3/enumeratum/EnumMacros.scala index 91568b1b..465feb14 100644 --- a/macros/src/main/scala-3/enumeratum/EnumMacros.scala +++ b/macros/src/main/scala-3/enumeratum/EnumMacros.scala @@ -7,6 +7,16 @@ object EnumMacros: def findValuesImpl[A](using tpe: Type[A], q: Quotes): Expr[IndexedSeq[A]] = { import q.reflect.* + // println(s"${child.show} :: ${child.typeSymbol.methodMember("findValues")}") + val definingTpeSym = Symbol.spliceOwner.maybeOwner.maybeOwner + + if (!definingTpeSym.flags.is(Flags.Module)) { + report.errorAndAbort( + // Root must be a module to have same behaviour + s"The enum (i.e. the class containing the case objects and the call to `findValues`) must be an object: ${definingTpeSym.fullName}" + ) + } + val repr = validateType[A] val subclasses = enclosedSubClasses[A](q)(repr) @@ -88,25 +98,11 @@ object EnumMacros: )(using tpe: Type[T]): List[q.reflect.TypeRepr] = { import q.reflect.* - // TODO: Use SumOf? given quotes: q.type = q @annotation.tailrec def isObject(sym: Symbol, ok: Boolean = false): Boolean = { if (!sym.flags.is(Flags.Module)) { - val owner = sym.maybeOwner - - if ( - owner == defn.RootClass || - owner.flags.is(Flags.Package) || - owner.isAnonymousFunction - ) { - report.errorAndAbort( - // Root must be a module to have same behaviour - "The enum (i.e. the class containing the case objects and the call to `findValues`) must be an object" - ) - } - false } else { val owner = sym.maybeOwner @@ -139,7 +135,7 @@ object EnumMacros: children: List[Tree], out: List[TypeRepr] ): List[TypeRepr] = { - val childTpr = children.headOption.collect { + val childTpr: Option[TypeRepr] = children.headOption.collect { case tpd: Typed => tpd.tpt.tpe @@ -147,19 +143,21 @@ object EnumMacros: vd.tpt.tpe case cd: ClassDef => - cd.constructor.returnTpt.tpe + cd.symbol.typeRef match { + case TypeRef(prefix, _) => + prefix.select(cd.symbol) + } } childTpr match { case Some(child) => { child.asType match { - case '[IsEntry[t]] => { + case ct @ '[IsEntry[t]] => { val tpeSym = child.typeSymbol - // TODO: Check is subtype (same in Scala2?) if (!isObject(tpeSym)) { - subclasses(children.tail, out) + subclasses(tpeSym.children.map(_.tree) ::: children.tail, out) } else { subclasses(children.tail, child :: out) } @@ -177,7 +175,6 @@ object EnumMacros: tpr.classSymbol .flatMap { cls => - // TODO: cls.typeMembers val types = subclasses(cls.children.map(_.tree), Nil) if (types.isEmpty) None else Some(types) diff --git a/project/build.properties b/project/build.properties index d738b858..9a19778c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.7.1 +sbt.version = 1.8.0 diff --git a/project/plugins.sbt b/project/plugins.sbt index e0733499..2f1457ee 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -10,6 +10,6 @@ addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") -addSbtPlugin(("org.scoverage" % "sbt-scoverage" % "2.0.2").exclude("org.scala-lang.modules", "*")) +addSbtPlugin(("org.scoverage" % "sbt-scoverage" % "2.0.5").exclude("org.scala-lang.modules", "*")) addSbtPlugin(("org.scoverage" % "sbt-coveralls" % "1.3.2").exclude("org.scala-lang.modules", "*")) From 3ae11c469e29a1b4a1662bccb9be9772fbf9cc37 Mon Sep 17 00:00:00 2001 From: lloydmeta Date: Sat, 3 Dec 2022 22:20:22 +0900 Subject: [PATCH 15/16] * Use match instead of straight cast Signed-off-by: lloydmeta --- macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala b/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala index e6bc2639..eab04a0b 100644 --- a/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala +++ b/macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala @@ -117,7 +117,10 @@ object ValueEnumMacros { import q.reflect.* - val ctx = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val ctx = q match { + case quotesImpl: scala.quoted.runtime.impl.QuotesImpl => quotesImpl.ctx + case other => report.errorAndAbort(s"[${other}] was not the expected class") + } val yRetainTrees = ctx.settings.YretainTrees.valueIn(ctx.settingsState) From f826d238144f75aae17d9d46e2904e3f87117a50 Mon Sep 17 00:00:00 2001 From: Lloyd Date: Sat, 3 Dec 2022 23:12:25 +0900 Subject: [PATCH 16/16] Add Scala3 mention in Readme Signed-off-by: Lloyd --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de0ff3db..c5835873 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,11 @@ Enumeratum has the following niceties: - All magic happens at compile-time so you know right away when things go awry - Comprehensive automated testing to make sure everything is in tip-top shape -Enumeratum is published for Scala 2.11.x, 2.12.x, 2.13.x as well as ScalaJS. +Enumeratum is published for Scala 2.11.x, 2.12.x, 2.13.x, 3.x as well as ScalaJS. + +Note that there are a couple of limitations for 3.x: +* All "immediate" parent types of enum entries need to be `sealed` (reasoning [here](https://github.com/lloydmeta/enumeratum/pull/349#discussion_r1034715979)) +* The `-Yretain-trees` Scalac option must be set when using ValueEnums Integrations are available for: