trait for companion object of enums, and create a given for it. #18917
Replies: 3 comments
-
I was eventually able to get a macro working (well, it compiles anyway) that looks something like: private def derivedMacro[T](using typ: Type[T], quotes: Quotes): Expr[Reader[T]] = {
import quotes.reflect.*
import scala.compiletime.error
val companionSymbol = TypeRepr.of[T].typeSymbol.companionModule
val fromOrdinalMeth = companionSymbol.declaredMethod("fromOrdinal") match {
case List(meth) => meth
case _ => error(s"${companionSymbol} must have exactly one fromOrdinal method")
}
val i = Ident(TermRef(TypeRepr.of[Int], "i"))
val parsed = Ref(companionSymbol).select(fromOrdinalMeth).appliedToArgs(List(i)).asExprOf[T]
'{
new Reader[T] {
def read(input: Input) = summon[Reader[Int]].read(input).flatMap { i =>
try
Some(${parsed})
catch case _: NoSuchElementException => None
}
}
}
}
inline def derived[T <: scala.reflect.Enum]: Reader[T] = ${ derivedMacro[T] }
It took me a while to figure it out though (partly due to lack of more documentation and examples for macros). And it is kind of surprising there isn't an easier way to do this. |
Beta Was this translation helpful? Give feedback.
-
I was just about to open a feature request to suggest having methods like This is indeed very useful for parsing - and this is actually needed a lot in certain application with database-codecs, json-codecs, etc. So it would be very useful to handle this generically. |
Beta Was this translation helpful? Give feedback.
-
i'd find a trait useful, too. i solved the issue with a (slightly more general) macro: import scala.reflect.Enum
import scala.quoted.*
object EnumCompanion {
inline given of[T<:Enum]:EnumCompanion[T] = ${ ofImpl[T] }
def ofImpl[T<:Enum](using quotes:Quotes, tpe:Type[T]):Expr[EnumCompanion[T]] = {
import quotes.reflect.*
val companion = Ref(TypeTree.of[T].symbol.companionModule)
'{
new EnumCompanion[T] {
def values:Array[T] = ${ Select.unique(companion, "values").asExprOf[Array[T]] }
def fromOrdinal(ordinal:Int):T = ${ Select.unique(companion, "fromOrdinal").appliedTo('{ordinal}.asTerm).asExprOf[T] }
def ordinal(element:T):Int = element.ordinal
def valueOf(name:String):T = ${ Select.unique(companion, "valueOf").appliedTo('{name}.asTerm).asExprOf[T] }
def enumLabel(element:T):String = element.productPrefix
}
}
}
}
trait EnumCompanion[T] {
def values:Array[T]
def fromOrdinal(ordinal:Int):T
def ordinal(element:T):Int
def valueOf(name:String):T
def enumLabel(element:T):String
}
|
Beta Was this translation helpful? Give feedback.
-
I'll start by saying what I want to do.
I have a typeclass for deserializing from an input to a target type. I'd like to be able to define an instance of it that works for any enumeration that only has values (not an ADT). I'd like to do something like this:
As far as I can tell there isn't really a good way to do that currently. It is possible using a macro, but that is pretty involved.
My proposal is that a trait like the following is added:
Obviously, the names could be something else, and the last two methods could potential be a separate feature request.
Then if an
enum
E
is defined in which all cases are a single value (that is, it is not an ADT), and the compiler would generate the values, valueOf, and fromOrdinal methods on the companion object. Then the companion object will also automatically extendEnumCompanion[E]
and the following given alias will be defined:Beta Was this translation helpful? Give feedback.
All reactions