Skip to content

Commit

Permalink
Feature/2.10 valueenums (#68)
Browse files Browse the repository at this point in the history
Backport ValueEnums to 2.10.x

- Ported macros by adding more shims to ContextUtils

- Move integration lib ValueEnum code into main src from compat directories
  • Loading branch information
lloydmeta authored Aug 29, 2016
1 parent d08e3a3 commit 9d9b443
Show file tree
Hide file tree
Showing 44 changed files with 48 additions and 25 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ script:
- sbt clean +test:compile
- sbt +test
# Manually select the JVM projects to run coverage on until Scoverage is compatible with ScalaJS
- sbt coverage coreJVM/test enumeratum-play/test enumeratum-play-json/test enumeratumCirceJVM/test enumeratumUPickleJVM/test enumeratum-reactivemongo-bson/test
- sbt coverage coreJVM/test enumeratum-play/test enumeratum-play-json/test enumeratumCirceJVM/test enumeratumUPickleJVM/test enumeratum-reactivemongo-bson/test coverageReport && sbt coverageAggregate
after_success:
- sbt coverageReport coverageAggregate coveralls
- sbt coveralls
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && sbt coreJVM/updateImpactSubmit || true'
cache:
directories:
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,6 @@ LibraryItem.withValue(10) // => java.util.NoSuchElementException:

**Restrictions**
- `ValueEnum`s must have their value members implemented as literal values.
- `ValueEnum`s are not available in Scala 2.10.x because work needs to be done to bridge all Macro API differences (e.g. `isConstructor`)


## ScalaJS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ case object MovieGenre extends IntEnum[MovieGenre] {

val values = findValues

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ class ValueEnumSpec extends FunSpec with Matchers with ValueEnumHelpers {

describe("trying to use with improper types") {

it("should fail to compile when value types do not match with the kind of value enum") {
"""
|sealed abstract class IceCream(val value: String) extends IntEnumEntry
|
|case object IceCream extends IntEnum[IceCream] {
| val value = findValues
|
| case object Sandwich extends IceCream("sandwich")
|}
""" shouldNot compile
}

it("should fail to compile for unsealed traits") {
"""
trait NotSealed extends IntEnumEntry
Expand Down
10 changes: 10 additions & 0 deletions macros/compat/src/main/scala-2.10/enumeratum/ContextUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ object ContextUtils {

type Context = scala.reflect.macros.Context

// In 2.10, the constants have Java boxed types at compile time for some reason
type CTLong = java.lang.Long
type CTInt = java.lang.Integer

/**
* Returns a TermName
*/
Expand All @@ -16,4 +20,10 @@ object ContextUtils {
*/
def companion(c: Context)(sym: c.Symbol): c.universe.Symbol = sym.companionSymbol

/**
* Returns a PartialFunction for turning symbols into names
*/
def constructorsToParamNamesPF(c: Context): PartialFunction[c.universe.Symbol, List[c.universe.Name]] = {
case m if m.isMethod && m.asMethod.isConstructor => m.asMethod.paramss.flatten.map(_.asTerm.name)
}
}
10 changes: 10 additions & 0 deletions macros/compat/src/main/scala-2.11/enumeratum/ContextUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ object ContextUtils {

type Context = scala.reflect.macros.blackbox.Context

// Constant types
type CTLong = Long
type CTInt = Int

/**
* Returns a TermName
*/
Expand All @@ -16,4 +20,10 @@ object ContextUtils {
*/
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)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package enumeratum

import enumeratum.ContextUtils.Context

import scala.reflect.ClassTag
import ContextUtils.Context

object ValueEnumMacros {

Expand All @@ -11,7 +12,7 @@ object ValueEnumMacros {
* Note, requires the ValueEntryType to have a 'value' member that has a literal value
*/
def findIntValueEntriesImpl[ValueEntryType: c.WeakTypeTag](c: Context): c.Expr[IndexedSeq[ValueEntryType]] = {
findValueEntriesImpl[ValueEntryType, Int, Int](c)(identity)
findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Int](c)(identity)
}

/**
Expand All @@ -20,7 +21,7 @@ object ValueEnumMacros {
* Note, requires the ValueEntryType to have a 'value' member that has a literal value
*/
def findLongValueEntriesImpl[ValueEntryType: c.WeakTypeTag](c: Context): c.Expr[IndexedSeq[ValueEntryType]] = {
findValueEntriesImpl[ValueEntryType, Long, Long](c)(identity)
findValueEntriesImpl[ValueEntryType, ContextUtils.CTLong, Long](c)(identity)
}

/**
Expand All @@ -32,7 +33,7 @@ object ValueEnumMacros {
* - the Short value should be a literal Int (do no need to cast .toShort).
*/
def findShortValueEntriesImpl[ValueEntryType: c.WeakTypeTag](c: Context): c.Expr[IndexedSeq[ValueEntryType]] = {
findValueEntriesImpl[ValueEntryType, Int, Short](c)(_.toShort) // do a transform because there is no such thing as Short literals
findValueEntriesImpl[ValueEntryType, ContextUtils.CTInt, Short](c)(_.toShort) // do a transform because there is no such thing as Short literals
}

/**
Expand Down Expand Up @@ -71,7 +72,7 @@ object ValueEnumMacros {
*
* Will abort compilation if not all the trees provided have a literal value member/constructor argument
*/
private[this] def findValuesForSubclassTrees[ValueType: ClassTag, ProcessedValueType](c: Context)(valueEntryCTorsParams: List[List[c.universe.TermName]], memberTrees: Seq[c.universe.Tree], processFoundValues: ValueType => ProcessedValueType): Seq[TreeWithVal[c.universe.Tree, ProcessedValueType]] = {
private[this] def findValuesForSubclassTrees[ValueType: ClassTag, ProcessedValueType](c: Context)(valueEntryCTorsParams: List[List[c.universe.Name]], memberTrees: Seq[c.universe.Tree], processFoundValues: ValueType => ProcessedValueType): Seq[TreeWithVal[c.universe.Tree, ProcessedValueType]] = {
val treeWithValues = toTreeWithMaybeVals[ValueType, ProcessedValueType](c)(valueEntryCTorsParams, memberTrees, processFoundValues)
val (hasValueMember, lacksValueMember) = treeWithValues.partition(_.maybeValue.isDefined)
if (lacksValueMember.nonEmpty) {
Expand Down Expand Up @@ -100,7 +101,7 @@ object ValueEnumMacros {
*
* 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.TermName]], memberTrees: Seq[c.universe.Tree], processFoundValues: ValueType => ProcessedValueType): Seq[TreeWithMaybeVal[c.universe.Tree, ProcessedValueType]] = {
private[this] def toTreeWithMaybeVals[ValueType: ClassTag, ProcessedValueType](c: Context)(valueEntryCTorsParams: List[List[c.universe.Name]], memberTrees: Seq[c.universe.Tree], processFoundValues: ValueType => ProcessedValueType): Seq[TreeWithMaybeVal[c.universe.Tree, ProcessedValueType]] = {
import c.universe._
val classTag = implicitly[ClassTag[ValueType]]
val valueTerm = ContextUtils.termName(c)("value")
Expand All @@ -122,14 +123,12 @@ object ValueEnumMacros {
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
*
* Note: Can't match without using Ident(ContextUtils.termName(c)(" ")) extractor ??!
*/
case (_, AssignOrNamedArg(Ident(TermName("value")), Literal(Constant(i: ValueType)))) => i
case (_, AssignOrNamedArg(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 (_, AssignOrNamedArg(Ident(TermName("value")), 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 (_, AssignOrNamedArg(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}")
}
}
}
Expand All @@ -145,12 +144,12 @@ object ValueEnumMacros {
/**
* Given a type, finds the constructor params lists for it
*/
private[this] def findConstructorParamsLists[ValueEntryType: c.WeakTypeTag](c: Context): List[List[c.universe.TermName]] = {
private[this] def findConstructorParamsLists[ValueEntryType: c.WeakTypeTag](c: Context): List[List[c.universe.Name]] = {
val valueEntryTypeTpe = implicitly[c.WeakTypeTag[ValueEntryType]].tpe
val valueEntryTypeTpeMembers = valueEntryTypeTpe.members
valueEntryTypeTpeMembers.collect {
case m if m.isMethod && m.isConstructor => m.asMethod.paramLists.flatten.map(_.asTerm.name)
}.toList
valueEntryTypeTpeMembers
.collect(ContextUtils.constructorsToParamNamesPF(c))
.toList
}

/**
Expand Down
7 changes: 0 additions & 7 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ object Enumeratum extends Build {
.settings(
name := "enumeratum"
)
.settings(withCompatUnmanagedSources(jsJvmCrossProject = true, include_210Dir = false, includeTestSrcs = true):_*)
.settings(testSettings:_*)
.settings(commonWithPublishSettings:_*)
.dependsOn(macros)
Expand Down Expand Up @@ -89,7 +88,6 @@ object Enumeratum extends Build {
"org.reactivemongo" %% "reactivemongo" % reactiveMongoVersion
)
)
.settings(withCompatUnmanagedSources(jsJvmCrossProject = false, include_210Dir = false, includeTestSrcs = true):_*)
.settings(testSettings:_*)
.dependsOn(coreJvm % "test->test;compile->compile")

Expand All @@ -99,7 +97,6 @@ object Enumeratum extends Build {
"com.typesafe.play" %% "play-json" % thePlayVersion(scalaVersion.value)
)
)
.settings(withCompatUnmanagedSources(jsJvmCrossProject = false, include_210Dir = false, includeTestSrcs = true):_*)
.settings(testSettings:_*)
.dependsOn(coreJvm % "test->test;compile->compile")

Expand All @@ -109,7 +106,6 @@ object Enumeratum extends Build {
"com.typesafe.play" %% "play" % thePlayVersion(scalaVersion.value)
)
)
.settings(withCompatUnmanagedSources(jsJvmCrossProject = false, include_210Dir = false, includeTestSrcs = true):_*)
.settings(testSettings:_*)
.dependsOn(coreJvm, enumeratumPlayJson % "test->test;compile->compile")

Expand Down Expand Up @@ -139,7 +135,6 @@ object Enumeratum extends Build {
additionalMacroDeps
}
)
.settings(withCompatUnmanagedSources(jsJvmCrossProject = true, include_210Dir = false, includeTestSrcs = true):_*)
.settings(testSettings:_*)
.dependsOn(core % "test->test;compile->compile")
lazy val enumeratumUPickleJs = enumeratumUPickle.js
Expand All @@ -160,7 +155,6 @@ object Enumeratum extends Build {
Seq(impl.ScalaJSGroupID.withCross("io.circe", "circe-core", cross) % "0.4.1")
}
)
.settings(withCompatUnmanagedSources(jsJvmCrossProject = true, include_210Dir = false, includeTestSrcs = true):_*)
.settings(testSettings:_*)
.dependsOn(core % "test->test;compile->compile")
lazy val enumeratumCirceJs = enumeratumCirce.js
Expand Down Expand Up @@ -267,7 +261,6 @@ object Enumeratum extends Build {
publishArtifact := false,
publishLocal := {}
)
.settings(withCompatUnmanagedSources(jsJvmCrossProject = false, include_210Dir = false, includeTestSrcs = false):_*)
.dependsOn(macrosJs, macrosJvm, coreJs, coreJvm, coreJVMTests, enumeratumPlay, enumeratumPlayJson, enumeratumUPickleJs, enumeratumUPickleJvm, enumeratumCirceJs, enumeratumCirceJvm, enumeratumReactiveMongoBson)
.enablePlugins(JmhPlugin)
.settings(libraryDependencies += "org.slf4j" % "slf4j-simple" % "1.7.21")
Expand Down

0 comments on commit 9d9b443

Please sign in to comment.