Skip to content

Commit

Permalink
Merge pull request #328 from VirtusLab/issue-215
Browse files Browse the repository at this point in the history
rewritten to be a full macro to fix compiler issue
  • Loading branch information
tgodzik authored Jul 26, 2024
2 parents 5bf61d1 + edef6fd commit fca6e33
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 35 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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]] =

Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit fca6e33

Please sign in to comment.