如何在Scala 3中访问未知枚举的valueOf方法

tquggr8v  于 2023-03-02  发布在  Scala
关注(0)|答案(1)|浏览(120)

我正在尝试创建内联def来为Scala 3中的任何枚举生成Json编解码器。为此,我需要访问枚举的父代的valueOf方法。
inline def gen[T](using JsonCodec[String], T <:< reflect.Enum): JsonCodec[T] = ???
如何才能做到这一点?
阅读评论后,现在我改变了我的代码:

val decoder: JsonDecoder[T] = JsonDecoder[String].mapOrFail(v => Try(${getEnum[T]}(v)).fold(e => Left(e.getMessage), v => Right(v)))
    val encoder: JsonEncoder[T] = JsonEncoder[String].contramap(_.toString)
    JsonCodec.apply(encoder, decoder)

  def getEnum[T: Type](using Quotes): Expr[String => T] =
    import quotes.reflect.*
    val companion = Ref(TypeTree.of[T].symbol.companionModule)
    Select.unique(companion, "valueOf").asExprOf[String => T]

编译器抱怨:

Malformed macro.

Expected the splice ${...} to be at the top of the RHS:
  inline def foo(inline x: X, ..., y: Y): Int = ${ impl('x, ... 'y) }

 * The contents of the splice must call a static method
 * All arguments must be quoted
kknvjkwl

kknvjkwl1#

作为json库,您似乎使用https://zio.github.io/zio-json
宏格式不正确。
预期拼接${...}位于RHS顶部
我想这个错误是可以理解的。你不应该像那样直接使用宏实现。
您有一个宏(内联方法)getEnum及其实现getEnumImpl(返回Expr),并且使用getEnum(而不是getEnumImpl

import zio.json.{JsonDecoder, JsonEncoder, JsonCodec}
import scala.quoted.*

inline def gen[T]: JsonCodec[T] =
  val decoder: JsonDecoder[T] = JsonDecoder[String].mapOrFail(v => util.Try(getEnum[T](v)).fold(e => Left(e.getMessage), v => Right(v)))
  val encoder: JsonEncoder[T] = JsonEncoder[String].contramap(_.toString)
  JsonCodec.apply(encoder, decoder)

inline def getEnum[T]: String => T = ${ getEnumImpl[T] }

def getEnumImpl[T: Type](using Quotes): Expr[String => T] =
  import quotes.reflect.*

  val companion = Ref(TypeTree.of[T].symbol.companionModule)
  Select.unique(companion, "valueOf").asExprOf[String => T]

getEnum本身是宏实现,您可以在另一个宏实现中使用它

import zio.json.{JsonDecoder, JsonEncoder, JsonCodec}
import scala.quoted.*

inline def gen[T]: JsonCodec[T] = ${genImpl[T]}

def genImpl[T: Type](using Quotes): Expr[JsonCodec[T]] =
  '{
    val decoder: JsonDecoder[T] = JsonDecoder[String].mapOrFail(v => util.Try(${ getEnum[T] }(v)).fold(e => Left(e.getMessage), v => Right(v)) )
    val encoder: JsonEncoder[T] = JsonEncoder[String].contramap(_.toString)
    JsonCodec.apply(encoder, decoder)
  }

def getEnum[T: Type](using Quotes): Expr[String => T] =
  import quotes.reflect.*

  val companion = Ref(TypeTree.of[T].symbol.companionModule)
  Select.unique(companion, "valueOf").asExprOf[String => T]

现在的错误是

enum Color:
  case Red, Green, Blue

gen[Color].encodeJson(Color.Red, None)

// Exception occurred while executing macro expansion.
// java.lang.Exception: Expected an expression.
// This is a partially applied Term. Try eta-expanding the term first.

可以用Apply Package Select.unique(并用方法替换函数String => T)。

inline def gen[T <: reflect.Enum]: JsonCodec[T] =
  val decoder: JsonDecoder[T] = JsonDecoder[String].mapOrFail(v => util.Try(getEnum[T](v)).fold(e => Left(e.getMessage), v => Right(v)))
  val encoder: JsonEncoder[T] = JsonEncoder[String].contramap(_.toString)
  JsonCodec.apply(encoder, decoder)

inline def getEnum[T](v: String): T = ${ getEnumImpl[T]('v) }

def getEnumImpl[T: Type](v: Expr[String])(using Quotes): Expr[T] =
  import quotes.reflect.*

  val companion = Ref(TypeTree.of[T].symbol.companionModule)
  Apply(Select.unique(companion, "valueOf"), List(v.asTerm)).asExprOf[T]

inline def gen[T <: reflect.Enum]: JsonCodec[T] = ${genImpl[T]}

def genImpl[T: Type](using Quotes): Expr[JsonCodec[T]] =
  '{
    val decoder: JsonDecoder[T] = JsonDecoder[String].mapOrFail(v => util.Try(${ getEnum[T]('v) }).fold(e => Left(e.getMessage), v => Right(v)) )
    val encoder: JsonEncoder[T] = JsonEncoder[String].contramap(_.toString)
    JsonCodec.apply(encoder, decoder)
  }

def getEnum[T: Type](v: Expr[String])(using Quotes): Expr[T] =
  import quotes.reflect.*

  val companion = Ref(TypeTree.of[T].symbol.companionModule)
  Apply(Select.unique(companion, "valueOf"), List(v.asTerm)).asExprOf[T]

此外,为了派生枚举的JsonCodec,可以使用Mirror

import scala.compiletime.{constValue, erasedValue, summonInline}
import scala.deriving.Mirror

inline given [T <: reflect.Enum with Singleton](using
  m: Mirror.ProductOf[T]
): JsonCodec[T] =
  val decoder: JsonDecoder[T] =
    val label = constValue[m.MirroredLabel]
    JsonDecoder[String].mapOrFail(v => Either.cond(label == v, m.fromProduct(EmptyTuple), s"$label != $v"))
  val encoder: JsonEncoder[T] = JsonEncoder[String].contramap(_.toString)
  JsonCodec.apply(encoder, decoder)

inline def mkSumDecoder[T, Tup <: Tuple]: JsonDecoder[T] = inline erasedValue[Tup] match
  case _: (h *: EmptyTuple) => summonInline[JsonDecoder[h & T]].widen[T]
  case _: (h *: t) => summonInline[JsonDecoder[h & T]].widen[T] <> mkSumDecoder[T, t]

inline given [T <: reflect.Enum](using
  m: Mirror.SumOf[T]
): JsonCodec[T] =
  val decoder: JsonDecoder[T] = mkSumDecoder[T, m.MirroredElemTypes]
  val encoder: JsonEncoder[T] = JsonEncoder[String].contramap(_.toString)
  JsonCodec.apply(encoder, decoder)

测试:

import zio.json.given

(Color.Blue: Color.Blue.type).toJson // "Blue"
(Color.Blue: Color).toJson           // "Blue"
""" "Blue" """.fromJson[Color.Blue.type] // Right(Blue)
""" "Blue" """.fromJson[Color]           // Right(Blue)

相关问题