Skip to content

Commit df708af

Browse files
allow value constructor param to be any constructor parameter, rather than only the first (#398)
* don't check for the correct tree shape for empty tree * fix constructor param fallback value * keep error messages per PR comments * use Option.flatMap rather than sentinel * fmt
1 parent 1fbbd04 commit df708af

File tree

3 files changed

+41
-54
lines changed

3 files changed

+41
-54
lines changed

enumeratum-core/src/test/scala/enumeratum/values/ValueEnumSpec.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,17 @@ class ValueEnumSpec extends AnyFunSpec with Matchers with ValueEnumHelpers {
118118
""" shouldNot compile
119119
}
120120

121+
it("should compile when the value constructor parameter is not first") {
122+
"""
123+
sealed abstract class MyStatus(final val idx: Int, final val value: String) extends StringEnumEntry
124+
125+
object MyStatus extends StringEnum[MyStatus] {
126+
case object PENDING extends MyStatus(1, "PENDING")
127+
val values = findValues
128+
}
129+
""" should compile
130+
}
131+
121132
it("should compile even when values are repeated if AllowAlias is extended") {
122133
"""
123134
sealed abstract class ContentTypeRepeated(val value: Long, name: String) extends LongEnumEntry with AllowAlias

macros/src/main/scala-3/enumeratum/ValueEnumMacros.scala

Lines changed: 24 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -133,51 +133,28 @@ In SBT settings:
133133
""")
134134
}
135135

136-
val repr = TypeRepr.of[A](using tpe)
136+
val repr = TypeRepr.of[A]
137137
val tpeSym = repr.typeSymbol
138138

139139
val valueRepr = TypeRepr.of[ValueType]
140140

141-
val ctorParams = tpeSym.primaryConstructor.paramSymss.flatten
142-
143-
val enumFields = repr.typeSymbol.fieldMembers.flatMap { field =>
144-
ctorParams.zipWithIndex.find { case (p, i) =>
145-
p.name == field.name && (p.tree match {
146-
case term: Term =>
147-
term.tpe <:< valueRepr
148-
149-
case _ =>
150-
false
151-
})
141+
val valueParamIndex = tpeSym.primaryConstructor.paramSymss
142+
.filterNot(_.exists(_.isType))
143+
.flatten
144+
.zipWithIndex
145+
.collectFirst {
146+
case (p, i) if p.name == "value" => i
152147
}
153-
}.toSeq
154-
155-
val (valueField, valueParamIndex): (Symbol, Int) = {
156-
if (enumFields.size == 1) {
157-
enumFields.headOption
158-
} else {
159-
enumFields.find(_._1.name == "value")
160-
}
161-
}.getOrElse {
162-
Symbol.newVal(tpeSym, "value", valueRepr, Flags.Abstract, Symbol.noSymbol) -> 0
163-
}
164148

165149
type IsValue[T <: ValueType] = T
166150

167151
object ConstVal {
168152
@annotation.tailrec
169153
def unapply(tree: Tree): Option[Constant] = tree match {
170-
case NamedArg(nme, v) if (nme == valueField.name) =>
171-
unapply(v)
172-
173-
case ValDef(nme, _, Some(v)) if (nme == valueField.name) =>
174-
unapply(v)
175-
176-
case lit @ Literal(const) if (lit.tpe <:< valueRepr) =>
177-
Some(const)
178-
179-
case _ =>
180-
None
154+
case NamedArg("value", v) => unapply(v)
155+
case ValDef("value", _, Some(v)) => unapply(v)
156+
case lit @ Literal(const) if (lit.tpe <:< valueRepr) => Some(const)
157+
case _ => None
181158
}
182159
}
183160

@@ -193,24 +170,18 @@ In SBT settings:
193170
(for {
194171
vof <- Expr.summon[ValueOf[h]]
195172
constValue <- htpr.typeSymbol.tree match {
196-
case ClassDef(_, _, spr, _, rhs) => {
197-
val fromCtor = spr
198-
.collectFirst {
199-
case Apply(Select(New(id), _), args) if id.tpe <:< repr => args
200-
case Apply(TypeApply(Select(New(id), _), _), args) if id.tpe <:< repr => args
201-
}
202-
.flatMap(_.lift(valueParamIndex).collect { case ConstVal(const) =>
203-
const
204-
})
205-
206-
fromCtor
207-
.orElse(rhs.collectFirst { case ConstVal(v) => v })
208-
.flatMap { const =>
209-
cls.unapply(const.value)
210-
}
211-
173+
case ClassDef(_, _, parents, _, statements) => {
174+
val fromCtor = valueParamIndex.flatMap { (ix: Int) =>
175+
parents
176+
.collectFirst {
177+
case Apply(Select(New(id), _), args) if id.tpe <:< repr => args
178+
case Apply(TypeApply(Select(New(id), _), _), args) if id.tpe <:< repr => args
179+
}
180+
.flatMap(_.lift(ix).collect { case ConstVal(const) => const })
181+
}
182+
def fromBody = statements.collectFirst { case ConstVal(v) => v }
183+
fromCtor.orElse(fromBody).flatMap { const => cls.unapply(const.value) }
212184
}
213-
214185
case _ =>
215186
Option.empty[ValueType]
216187
}
@@ -230,8 +201,7 @@ In SBT settings:
230201
case Some(sum) =>
231202
sum.asTerm.tpe.asType match {
232203
case '[SumOf[a, t]] => collect[Tuple.Concat[t, tail]](instances, values)
233-
234-
case _ => Left(s"Invalid `Mirror.SumOf[${TypeRepr.of[h].show}]")
204+
case _ => Left(s"Invalid `Mirror.SumOf[${TypeRepr.of[h].show}]")
235205
}
236206

237207
case None =>
@@ -248,7 +218,7 @@ In SBT settings:
248218
}
249219
.mkString(", ")
250220

251-
Left(s"Values for ${valueField.name} are not discriminated subtypes: ${details}")
221+
Left(s"Values value are not discriminated subtypes: ${details}")
252222
} else {
253223
Right(Expr ofList instances.reverse)
254224
}

macros/src/test/scala/enumeratum/CompilationSpec.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,9 @@ object F {
136136
case object F4 extends F(value = 4, "mike")
137137

138138
}
139+
140+
sealed abstract class G(val name: String, val value: Int)
141+
object G {
142+
val values = FindValEnums[G]
143+
case object G1 extends G("gerald", 1)
144+
}

0 commit comments

Comments
 (0)