Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,51 @@ object DecoderMacros {
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[_]]] =
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]
Expr.summon[YamlDecoder[t]] match
case Some(decoder) =>
'{
new YamlDecoder[Any] {
override def construct(node: Node)(using settings: LoadSettings): Either[ConstructError, Any] =
$decoder.construct(node)
}
} :: 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
Expr.summon[Mirror.ProductOf[t]] match
case Some(p) =>
val derived = deriveProduct[t](p)
'{
new YamlDecoder[Any] {
override def construct(node: Node)(using settings: LoadSettings): Either[ConstructError, Any] =
$derived.construct(node)
}
} :: 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;
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]]]
private val instances = $instancesExpr.asInstanceOf[List[YamlDecoder[Any]]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to change it here?

override def construct(node: Node)(using
constructor: LoadSettings = LoadSettings.empty
): Either[ConstructError, T] =
instances
.map(_.construct(node))
.collectFirst { case r @ Right(_) => r }
.collectFirst { case Right(value) => Right(value.asInstanceOf[T]) }
.getOrElse(
Left(ConstructError.from(s"Cannot parse $node", node))
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,3 +577,39 @@ class DecoderSuite extends munit.FunSuite:
case Right(value) =>
fail(s"Should fail, but got $value")
}

test("Recursive case class".only) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test("Recursive case class".only) {
test("Recursive case class") {

this fails on CI

sealed trait Tree derives YamlDecoder
object Tree:
final case class Branch(left: Tree, right: Tree) extends Tree
final case class Leaf(value: Int) extends Tree

val yaml = """type: Branch
|left:
| type: Leaf
| value: 1
|right:
| type: Branch
| left:
| type: Leaf
| value: 2
| right:
| type: Leaf
| value: 3""".stripMargin

val node = yaml.asNode

yaml.as[Tree] match
case Left(error: YamlError) =>
fail(s"failed with YamlError: $error")
case Right(foo) =>
val expected =
Tree.Branch(
left = Tree.Leaf(1),
right = Tree.Branch(
left = Tree.Leaf(2),
right = Tree.Leaf(3)
)
)
assertEquals(foo, expected)
}
Loading