Skip to content

Commit ad3ac98

Browse files
committed
Release v1.21.0
1 parent eda9510 commit ad3ac98

3 files changed

Lines changed: 68 additions & 32 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ The focus of this project is minimalism and flexibility. To that end, the featur
4646
### Setup
4747

4848
For SBT simply include:
49-
`libraryDependencies += "org.typelevel" %%% "fabric-core" % "1.20.1"`
49+
`libraryDependencies += "org.typelevel" %%% "fabric-core" % "1.21.0"`
5050

5151
For parsing support include:
52-
`libraryDependencies += "org.typelevel" %%% "fabric-io" % "1.20.1"`
52+
`libraryDependencies += "org.typelevel" %%% "fabric-io" % "1.21.0"`
5353

5454
### Create
5555

core/shared/src/main/scala-2/fabric/rw/RWMacros.scala

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,33 @@ object RWMacros {
4040
case m: MethodSymbol if m.isPrimaryConstructor => m.paramLists.head
4141
} match {
4242
case Some(fields) =>
43-
val transientNames = fields.filter(_.annotations.exists(_.tree.tpe =:= typeOf[notSerialized])).map(_.asTerm.name.decodedName.toString).toSet
44-
val fieldDefs = fields.zipWithIndex.filterNot { case (field, _) =>
45-
transientNames.contains(field.asTerm.name.decodedName.toString)
46-
}.map { case (field, index) =>
47-
val name = field.asTerm.name
48-
val key = name.decodedName.toString
49-
val returnType = tpe.decl(name).typeSignature.asSeenFrom(tpe, tpe.typeSymbol.asClass)
50-
val descAnn = field.annotations.find(_.tree.tpe =:= typeOf[description])
51-
val baseDef =
52-
if (defaults.contains(index)) {
53-
q"implicitly[RW[$returnType]].definition.opt"
54-
} else {
55-
q"implicitly[RW[$returnType]].definition"
56-
}
57-
descAnn match {
58-
case Some(ann) => ann.tree.children.tail.head match {
59-
case l: LiteralApi =>
60-
val text = l.value.value.toString
61-
q"$key -> $baseDef.describe($text)"
62-
case _ => q"$key -> $baseDef"
43+
val transientNames = fields
44+
.filter(_.annotations.exists(_.tree.tpe =:= typeOf[notSerialized]))
45+
.map(_.asTerm.name.decodedName.toString)
46+
.toSet
47+
val fieldDefs = fields.zipWithIndex
48+
.filterNot { case (field, _) => transientNames.contains(field.asTerm.name.decodedName.toString) }
49+
.map { case (field, index) =>
50+
val name = field.asTerm.name
51+
val key = name.decodedName.toString
52+
val returnType = tpe.decl(name).typeSignature.asSeenFrom(tpe, tpe.typeSymbol.asClass)
53+
val descAnn = field.annotations.find(_.tree.tpe =:= typeOf[description])
54+
val baseDef =
55+
if (defaults.contains(index)) {
56+
q"implicitly[RW[$returnType]].definition.opt"
57+
} else {
58+
q"implicitly[RW[$returnType]].definition"
6359
}
64-
case None => q"$key -> $baseDef"
60+
descAnn match {
61+
case Some(ann) => ann.tree.children.tail.head match {
62+
case l: LiteralApi =>
63+
val text = l.value.value.toString
64+
q"$key -> $baseDef.describe($text)"
65+
case _ => q"$key -> $baseDef"
66+
}
67+
case None => q"$key -> $baseDef"
68+
}
6569
}
66-
}
6770
val serializedDefs = serializedMembers(context)(tpe).map { info =>
6871
val key = info.jsonKey
6972
val returnType = info.returnType.asInstanceOf[Type]
@@ -107,14 +110,19 @@ object RWMacros {
107110
case m: MethodSymbol if m.isPrimaryConstructor => m.paramLists.head
108111
} match {
109112
case Some(fields) =>
110-
val transientNames = fields.filter(_.annotations.exists(_.tree.tpe =:= typeOf[notSerialized])).map(_.asTerm.name.decodedName.toString).toSet
111-
val toMap: List[context.universe.Tree] = fields.filterNot { field =>
112-
transientNames.contains(field.asTerm.name.decodedName.toString)
113-
}.map { field =>
114-
val name = field.asTerm.name
115-
val key = name.decodedName.toString
116-
q"$key -> t.$name.json"
117-
}
113+
val transientNames = fields
114+
.filter(_.annotations.exists(_.tree.tpe =:= typeOf[notSerialized]))
115+
.map(_.asTerm.name.decodedName.toString)
116+
.toSet
117+
val toMap: List[context.universe.Tree] = fields
118+
.filterNot { field =>
119+
transientNames.contains(field.asTerm.name.decodedName.toString)
120+
}
121+
.map { field =>
122+
val name = field.asTerm.name
123+
val key = name.decodedName.toString
124+
q"$key -> t.$name.json"
125+
}
118126
val extraMap: List[context.universe.Tree] = serializedMembers(context)(tpe).map { info =>
119127
val key = info.jsonKey
120128
val memberName = TermName(info.memberName)

core/shared/src/test/scala-3/spec/Scala3Spec.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,27 @@ class Scala3Spec extends AnyWordSpec with Matchers {
210210
val back = str("xyz-789").as[UserId]
211211
back should be(UserId("xyz-789"))
212212
}
213+
"handle recursive/self-referencing types" in {
214+
import RecursiveTest._
215+
val tree = TreeNode("root", List(
216+
TreeNode("child1", Nil),
217+
TreeNode("child2", List(TreeNode("grandchild", Nil)))
218+
))
219+
val json = tree.json
220+
json("value").asString should be("root")
221+
json("children").asArr.value should have size 2
222+
json("children").asArr.value(1)("children").asArr.value(0)("value").asString should be("grandchild")
223+
224+
val back = json.as[TreeNode]
225+
back should be(tree)
226+
227+
// Option-based recursion
228+
val linked = LinkedNode(1, Some(LinkedNode(2, Some(LinkedNode(3, None)))))
229+
val linkedJson = linked.json
230+
linkedJson("value").asInt should be(1)
231+
val linkedBack = linkedJson.as[LinkedNode]
232+
linkedBack should be(linked)
233+
}
213234
"extract @description annotations into DefType definitions" in {
214235
import DescriptionTest._
215236
val defn = implicitly[RW[Documented]].definition
@@ -292,6 +313,13 @@ object AnyValTest {
292313
}
293314
}
294315

316+
object RecursiveTest {
317+
case class TreeNode(value: String, children: List[TreeNode]) derives RW
318+
case class LinkedNode(value: Int, next: Option[LinkedNode]) derives RW
319+
case class Foo(name: String, bar: Bar) derives RW
320+
case class Bar(value: Int, foo: Option[Foo]) derives RW
321+
}
322+
295323
object UnionTest {
296324
case class Cat(name: String, age: Int) derives RW
297325
case class Dog(name: String, breed: String) derives RW

0 commit comments

Comments
 (0)