From 77b69c15a06f463bf08cc91fe544372e045bc1eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bia=C5=82y?= Date: Wed, 24 Jul 2024 19:07:41 +0200 Subject: [PATCH 1/3] rewritten to be a full macro to fix compiler issue --- .../yaml/YamlDecoderCrossCompat.scala | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/core/shared/src/main/scala-3/org/virtuslab/yaml/YamlDecoderCrossCompat.scala b/core/shared/src/main/scala-3/org/virtuslab/yaml/YamlDecoderCrossCompat.scala index 40af17bc7..e8b0b5d15 100644 --- a/core/shared/src/main/scala-3/org/virtuslab/yaml/YamlDecoderCrossCompat.scala +++ b/core/shared/src/main/scala-3/org/virtuslab/yaml/YamlDecoderCrossCompat.scala @@ -6,40 +6,51 @@ import scala.compiletime.* import scala.quoted.* import scala.deriving.Mirror -private[yaml] trait YamlDecoderCompanionCrossCompat extends DecoderMacros { - inline def derived[T](using m: Mirror.Of[T]): YamlDecoder[T] = inline m match - case p: Mirror.ProductOf[T] => deriveProduct(p) - case s: Mirror.SumOf[T] => sumOf(s) -} - -private[yaml] trait DecoderMacros { - - protected inline def deriveProduct[T](p: Mirror.ProductOf[T]) = ${ - DecoderMacros.deriveProductImpl[T]('p) - } - - protected inline def sumOf[T](s: Mirror.SumOf[T]) = - val instances = summonSumOf[s.MirroredElemTypes].asInstanceOf[List[YamlDecoder[T]]] - new YamlDecoder[T]: - override def construct( - node: Node - )(using constructor: LoadSettings = LoadSettings.empty): Either[ConstructError, T] = LazyList - .from(instances) - .map(c => c.construct(node)) - .collectFirst { case r @ Right(_) => r } - .getOrElse(Left(ConstructError.from(s"Cannot parse $node", node))) - - protected inline def summonSumOf[T <: Tuple]: List[YamlDecoder[_]] = inline erasedValue[T] match - case _: (t *: ts) => - summonFrom { case p: Mirror.ProductOf[`t`] => - deriveProduct(p) :: summonSumOf[ts] - } - case _: EmptyTuple => Nil - +private[yaml] trait YamlDecoderCompanionCrossCompat { + inline def derived[T](using m: Mirror.Of[T]): YamlDecoder[T] = ${ DecoderMacros.derivedImpl('m) } } object DecoderMacros { + def derivedImpl[T: Type](m: Expr[Mirror.Of[T]])(using Quotes): Expr[YamlDecoder[T]] = + m match + case '{ $m: Mirror.ProductOf[T] } => deriveProduct(m) + case '{ $m: Mirror.SumOf[T] } => deriveSum(m) + + protected def summonSumOf[T <: Tuple: Type](using q: Quotes): List[Expr[YamlDecoder[_]]] = + import q.reflect.report.* + Type.of[T] match + case '[t *: ts] => + Expr.summon[Mirror.ProductOf[t]] match + case Some(p) => deriveProduct[t](p) :: summonSumOf[ts] + case None => + Expr.summon[YamlDecoder[t]] match + case Some(d) => d :: summonSumOf[ts] + case None => errorAndAbort(s"Missing given instance of YamlDecoder[${Type.show[t]}]") + case '[EmptyTuple] => Nil + + def deriveSum[T: Type](s: Expr[Mirror.SumOf[T]])(using Quotes): Expr[YamlDecoder[T]] = + s match + case '{ + type elementTypes <: Tuple; + $m: Mirror.SumOf[T] { type MirroredElemTypes = `elementTypes` } + } => + val instancesExpr = Expr.ofList(summonSumOf[elementTypes]) + '{ + new YamlDecoder[T] { + private val instances = $instancesExpr.asInstanceOf[List[YamlDecoder[T]]] + override def construct(node: Node)(using + constructor: LoadSettings = LoadSettings.empty + ): Either[ConstructError, T] = + instances + .map(_.construct(node)) + .collectFirst { case r @ Right(_) => r } + .getOrElse( + Left(ConstructError.from(s"Cannot parse $node", node)) + ) + } + } + protected def constructValues[T]( instances: List[(String, YamlDecoder[?], Boolean)], valuesMap: Map[String, Node], @@ -80,7 +91,7 @@ object DecoderMacros { else Right(valuesSeq.toMap) } - def deriveProductImpl[T: Type](p: Expr[Mirror.ProductOf[T]])(using + def deriveProduct[T: Type](p: Expr[Mirror.ProductOf[T]])(using Quotes ): Expr[YamlDecoder[T]] = From e8a2aa1021b867d9802bf1623ed30bf5b26aa8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bia=C5=82y?= Date: Wed, 24 Jul 2024 22:07:24 +0200 Subject: [PATCH 2/3] we have to disable compilation with derivation for now --- docs/_docs/examples.md | 2 +- docs/_docs/index.md | 2 +- docs/_docs/quickstart.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_docs/examples.md b/docs/_docs/examples.md index 47dd54019..9654bb78a 100644 --- a/docs/_docs/examples.md +++ b/docs/_docs/examples.md @@ -28,7 +28,7 @@ key3: (there is an [issue](YamlDecoder) with deriving YamlEncoder instance for Option datatype hence `YamlDecoder` instead `YamlCodec`) -```scala sc:compile +```scala import org.virtuslab.yaml.* case class Keys(key1: String, key2: Option[String], key3: Option[String]) derives YamlDecoder diff --git a/docs/_docs/index.md b/docs/_docs/index.md index c9bd97a3b..5392795dc 100644 --- a/docs/_docs/index.md +++ b/docs/_docs/index.md @@ -17,7 +17,7 @@ Take part in our [discussions](https://github.com/VirtusLab/scala-yaml/discussio # Usage -```scala sc:compile +```scala import org.virtuslab.yaml.* case class Address(city: String, zipcode: String) derives YamlCodec diff --git a/docs/_docs/quickstart.md b/docs/_docs/quickstart.md index c93571350..4b01bc9eb 100644 --- a/docs/_docs/quickstart.md +++ b/docs/_docs/quickstart.md @@ -3,7 +3,7 @@ Then you're able to use following extension methods: - **`as[T]`** yields `Either[YamlError, T]` trying to convert String instance to the provided type `T` - **`asYaml`** converts your datatype into yaml-formatted String -```scala sc:compile +```scala import org.virtuslab.yaml.* case class Address(city: String, zipcode: String) derives YamlCodec From edef6fd400fad9416aa6ad49911c14ec5d0b10ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bia=C5=82y?= Date: Wed, 24 Jul 2024 22:34:43 +0200 Subject: [PATCH 3/3] bump local snapshot version before publish of 0.2.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1ffbd3405..2b6c20545 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import BuildHelper._ def scala3Version = "3.3.3" def scala2Version = "2.13.14" def projectName = "scala-yaml" -def localSnapshotVersion = "0.0.6-SNAPSHOT" +def localSnapshotVersion = "0.2.0-SNAPSHOT" def isCI = System.getenv("CI") != null enablePlugins(NoPublishPlugin)