amnaredo / test

0 stars 0 forks source link

Enumeratum + MacroImplicits: Failing test. #272

Open amnaredo opened 2 years ago

amnaredo commented 2 years ago

I'm looking to add a mixin for enumeratum enums like https://github.com/lloydmeta/enumeratum#playenum.

trait UPickleEnum[T <: EnumEntry] extends Enum[T] {

  implicit def rw[C <: T]: upickle.default.ReadWriter[C] = {
    upickle.default.readwriter[String]
      .bimap[C](
        _.entryName,
        str => values.find(_.entryName == str).get match {
          case child: C => child
          case _ => throw new Exception(s"wrong child")
        }
      )
  }
}

sealed abstract class Fruit(override val entryName: String) extends EnumEntry

object Fruit extends UPickleEnum[Fruit] {
  case object Peach extends Fruit("peach")
  case object Strawberry extends Fruit("strawberry")

  override val values = Seq(Peach, Strawberry)
}

object EnumeratumTests extends TestSuite {

  val tests = Tests {
    test("write") {
      test("Fruit") {
        // Passes, but requires explicit cast.
        upickle.default.write(Fruit.Peach: Fruit) ==> "\"peach\""
      }
      test("Peach") {
        // Can we remove the cast?
        // Fails:
        upickle.default.write(Fruit.Peach) ==> "\"peach\"" // {"$type":"upickle.Fruit.Peach"}
      }
    }
  }
}

The problem is with the sealed hierarchy implicits at https://github.com/lihaoyi/upickle/blob/master/implicits/src/upickle/implicits/MacroImplicits.scala#L41-L43 come in at higher precedence if I import upickle.default._.

If I avoid that wildcard import, I get ambiguous implicits:

Error:(60, 30) ambiguous implicit values:
 both method rw in trait UPickleEnum of type [C <: upickle.Fruit]=> upickle.default.ReadWriter[C]
 and macro method macroSingletonRW in trait MacroImplicits of type [T <: Singleton]=> upickle.default.ReadWriter[T]
 match expected type upickle.default.Writer[upickle.Fruit.Peach.type]
        upickle.default.write(Fruit.Peach) ==> "\"peach\"" // {"$type":"upickle.Fruit.Peach"}

I can think of a couple workarounds:

  1. import Fruit.rw in a tighter scope than import upickle.default._
  2. upcast anytime I call write(Fruit.Peach: Fruit).

Both of these workarounds are easy to forget. In the worst cases, the code compiles, but produces the wrong result at runtime.

Is there a way to have write(Fruit.Peach) ==> "\"peach\"" work while relying only on implicit scope (i.e. not lexical scope)?

Failing tests and commit: https://github.com/htmldoug/upickle/commit/5c012480c6a1ea0410af6d127785bee8ed345e02 ID: 314 Original Author: htmldoug