From c3aad0f1c9dde5513f0fa45b03e7e88c9d288b7e Mon Sep 17 00:00:00 2001 From: Michal Sitko Date: Sun, 14 Mar 2021 17:53:48 +0100 Subject: [PATCH] Don't invoke magnolia for built-in customs instances (e.g. encodeList) See https://github.com/propensive/magnolia/issues/107#issuecomment-589289260 --- .../pl/msitko/dhallj/generic/Exported.scala | 17 +++++++++++++ .../msitko/dhallj/generic/decoder/auto.scala | 24 +++++++++++++++---- .../msitko/dhallj/generic/encoder/auto.scala | 24 +++++++++++++++---- .../decoder/AutoDeriveDecoderSpec.scala | 13 ++++++++++ .../encoder/AutoDeriveEncoderSpec.scala | 22 +++++++++++++---- 5 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 src/main/scala/pl/msitko/dhallj/generic/Exported.scala diff --git a/src/main/scala/pl/msitko/dhallj/generic/Exported.scala b/src/main/scala/pl/msitko/dhallj/generic/Exported.scala new file mode 100644 index 0000000..cf46dbf --- /dev/null +++ b/src/main/scala/pl/msitko/dhallj/generic/Exported.scala @@ -0,0 +1,17 @@ +package pl.msitko.dhallj.generic + +// Workaround for issues with magnolia trying to derive instances having custom encoder/decoders +// Read more about that workaround here: https://github.com/propensive/magnolia/issues/107#issuecomment-589289260 +case class Exported[A](instance: A) extends AnyVal + +//import scala.language.higherKinds +import magnolia.Magnolia +import scala.reflect.macros.whitebox + +object ExportedMagnolia { + + def exportedMagnolia[TC[_], A: c.WeakTypeTag](c: whitebox.Context): c.Expr[Exported[TC[A]]] = { + val magnoliaTree = c.Expr[TC[A]](Magnolia.gen[A](c)) + c.universe.reify(Exported(magnoliaTree.splice)) + } +} diff --git a/src/main/scala/pl/msitko/dhallj/generic/decoder/auto.scala b/src/main/scala/pl/msitko/dhallj/generic/decoder/auto.scala index e4249c9..e931645 100644 --- a/src/main/scala/pl/msitko/dhallj/generic/decoder/auto.scala +++ b/src/main/scala/pl/msitko/dhallj/generic/decoder/auto.scala @@ -1,10 +1,10 @@ package pl.msitko.dhallj.generic.decoder -import magnolia.{CaseClass, Magnolia, SealedTrait} +import magnolia.{CaseClass, SealedTrait} import org.dhallj.codec.Decoder -import pl.msitko.dhallj.generic.GenericDecoder +import pl.msitko.dhallj.generic.{Exported, ExportedMagnolia, GenericDecoder} -object auto { +object auto extends ExportedDecoder { type Typeclass[T] = Decoder[T] def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = @@ -13,5 +13,21 @@ object auto { def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = GenericDecoder.dispatch(sealedTrait) - implicit def deriveDecoder[T]: Typeclass[T] = macro Magnolia.gen[T] + implicit def deriveDecoder[T]: Exported[Decoder[T]] = macro ExportedMagnolia.exportedMagnolia[Decoder, T] + + // Those are definedin an object in dhallj and therefore cannot be brought by inheritance + // TODO: Open a PR in dhallj + implicit val decodeLong: Decoder[Long] = Decoder.decodeLong + implicit val decodeInt: Decoder[Int] = Decoder.decodeInt + implicit val decodeBigInt: Decoder[BigInt] = Decoder.decodeBigInt + implicit val decodeDouble: Decoder[Double] = Decoder.decodeDouble + implicit val decodeString: Decoder[String] = Decoder.decodeString + implicit val decodeBoolean: Decoder[Boolean] = Decoder.decodeBoolean + implicit def decodeOption[A: Decoder]: Decoder[Option[A]] = Decoder.decodeOption + implicit def decodeVector[A: Decoder]: Decoder[Vector[A]] = Decoder.decodeVector + implicit def decodeList[A: Decoder]: Decoder[List[A]] = Decoder.decodeList +} + +trait ExportedDecoder { + implicit def exportedDecoder[A](implicit exported: Exported[Decoder[A]]): Decoder[A] = exported.instance } diff --git a/src/main/scala/pl/msitko/dhallj/generic/encoder/auto.scala b/src/main/scala/pl/msitko/dhallj/generic/encoder/auto.scala index ed206e6..c0ee297 100644 --- a/src/main/scala/pl/msitko/dhallj/generic/encoder/auto.scala +++ b/src/main/scala/pl/msitko/dhallj/generic/encoder/auto.scala @@ -1,10 +1,10 @@ package pl.msitko.dhallj.generic.encoder -import magnolia.{CaseClass, Magnolia, SealedTrait} +import magnolia.{CaseClass, SealedTrait} import org.dhallj.codec.Encoder -import pl.msitko.dhallj.generic.GenericEncoder +import pl.msitko.dhallj.generic.{Exported, ExportedMagnolia, GenericEncoder} -object auto { +object auto extends ExportedEncoder { type Typeclass[T] = Encoder[T] def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = @@ -13,5 +13,21 @@ object auto { def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = GenericEncoder.dispatch(sealedTrait) - implicit def deriveEncoder[T]: Typeclass[T] = macro Magnolia.gen[T] + implicit def deriveEncoder[T]: Exported[Encoder[T]] = macro ExportedMagnolia.exportedMagnolia[Encoder, T] + + // Those are definedin an object in dhallj and therefore cannot be brought by inheritance + // TODO: Open a PR in dhallj + implicit val encodeLong: Encoder[Long] = Encoder.encodeLong + implicit val encodeInt: Encoder[Int] = Encoder.encodeInt + implicit val encodeBigInt: Encoder[BigInt] = Encoder.encodeBigInt + implicit val encodeDouble: Encoder[Double] = Encoder.encodeDouble + implicit val encodeString: Encoder[String] = Encoder.encodeString + implicit val encodeBoolean: Encoder[Boolean] = Encoder.encodeBoolean + implicit def encodeOption[A: Encoder]: Encoder[Option[A]] = Encoder.encodeOption + implicit def encodeVector[A: Encoder]: Encoder[Vector[A]] = Encoder.encodeVector + implicit def encodeList[A: Encoder]: Encoder[List[A]] = Encoder.encodeList +} + +trait ExportedEncoder { + implicit def exportedEncoder[A](implicit exported: Exported[Encoder[A]]): Encoder[A] = exported.instance } diff --git a/src/test/scala/pl/msitko/dhallj/generic/decoder/AutoDeriveDecoderSpec.scala b/src/test/scala/pl/msitko/dhallj/generic/decoder/AutoDeriveDecoderSpec.scala index 3b0fbc7..373b6d1 100644 --- a/src/test/scala/pl/msitko/dhallj/generic/decoder/AutoDeriveDecoderSpec.scala +++ b/src/test/scala/pl/msitko/dhallj/generic/decoder/AutoDeriveDecoderSpec.scala @@ -109,6 +109,19 @@ class AutoDeriveDecoderSpec extends munit.FunSuite with Fixtures { assertEquals(decoded, OnOrOff2.Off()) } + // It wasn't working without ExportedMagnolia trick (see https://github.com/propensive/magnolia/issues/107#issuecomment-589289260) + // The point is that it should use custom decoder defined in dhallj and not try to invoke magnolia derivation + test("Work if the top level type has custom encoder") { + val decoded = + """ + |[ + | (.Error1) {msg = "abc"}, + | (.Error2) {code = 123, code2 = 456} + |]""".stripMargin.decode[List[Error]] + + assertEquals(decoded, List(Error1("abc"), Error2(code = 123, code2 = 456))) + } + test("Decoding error should be comprehensible for deeply nested case classes".ignore) { val input = """ diff --git a/src/test/scala/pl/msitko/dhallj/generic/encoder/AutoDeriveEncoderSpec.scala b/src/test/scala/pl/msitko/dhallj/generic/encoder/AutoDeriveEncoderSpec.scala index e89f009..5de22a5 100644 --- a/src/test/scala/pl/msitko/dhallj/generic/encoder/AutoDeriveEncoderSpec.scala +++ b/src/test/scala/pl/msitko/dhallj/generic/encoder/AutoDeriveEncoderSpec.scala @@ -1,5 +1,6 @@ package pl.msitko.dhallj.generic.encoder +import org.dhallj.ast.{NaturalLiteral, RecordLiteral} import org.dhallj.codec.Encoder import org.dhallj.codec.syntax._ import org.dhallj.core.Expr @@ -43,6 +44,21 @@ class AutoDeriveEncoderSpec extends munit.FunSuite with Fixtures { """{errors = [(.Error1) {msg = "abc"}, (.Error2) {code = 123, code2 = 456}]}""") } + test("Do not invoke magnolia and use custom instance if one is provided manually") { + implicit val errorEncoder: Encoder[StatusCode] = new Encoder[StatusCode] { + override def encode(value: StatusCode, target: Option[Expr]): Expr = + RecordLiteral(Map("myStatusCode" -> NaturalLiteral(value.code).get)) + override def dhallType(value: Option[StatusCode], target: Option[Expr]): Expr = ??? + } + + val res = encode(StatusCode(201)) + + assertEquals( + res, + """{myStatusCode = 201}""" + ) + } + test("Generate dhall type for sealed traits") { val typeExpr = dhallType[Error] @@ -52,11 +68,9 @@ class AutoDeriveEncoderSpec extends munit.FunSuite with Fixtures { ) } + // It wasn't working without ExportedMagnolia trick (see https://github.com/propensive/magnolia/issues/107#issuecomment-589289260) + // The point is that it should use custom decoder defined in dhallj and not try to invoke magnolia derivation test("Work if the top level type has custom encoder") { - // TODO: doesn't work without that import - import Encoder._ - - // BTW it wouldn't work if we don't `import org.dhallj.codec.Encoder._` val typeExpr = dhallType[List[Error]] assertEquals(