Skip to content

Commit 004ed7e

Browse files
authored
Fix macro crash when handling sealed case classes (#634)
Fixes #628 Sealed classes can be instantiated directly. Therefore, when generating the typeTag, I included the sealed class itself. This ensures that the macro correctly handles sealed classes without subclasses. To check whether it’s a sealed class and not a trait or abstract class, I used the following condition: ``` sealedParents.find(_ == tpe.typeSymbol) ``` Since `trait` and `abstract class` cannot be instantiated, I believe this approach works. However, I could also explicitly check that the symbol is not a trait or an abstract class, if needed. What do you think?
1 parent c7a77d9 commit 004ed7e

File tree

3 files changed

+18
-4
lines changed

3 files changed

+18
-4
lines changed

upickle/implicits/src-2/upickle/implicits/internal/Macros.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,14 @@ object Macros {
219219
fail(tpe, _),
220220
)
221221

222-
222+
val sealedClassSymbol: Option[Symbol] = sealedParents.find(_ == tpe.typeSymbol)
223223
val segments =
224-
sealedParents
224+
sealedClassSymbol.toList.map(_.fullName.split('.')) ++
225+
sealedParents
225226
.flatMap(_.asClass.knownDirectSubclasses)
226227
.map(_.fullName.split('.'))
227228

229+
228230
// -1 because even if there is only one subclass, and so no name segments
229231
// are needed to differentiate between them, we want to keep at least
230232
// the rightmost name segment

upickle/implicits/src-3/upickle/implicits/macros.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,12 +309,15 @@ def tagNameImpl0[T](transform: String => String)(using Quotes, Type[T]): Expr[St
309309
inline def shortTagName[T]: String = ${ shortTagNameImpl[T] }
310310
def shortTagNameImpl[T](using Quotes, Type[T]): Expr[String] =
311311
import quotes.reflect._
312-
val sym = TypeTree.of[T].symbol
312+
val sealedClassSymbol = if (TypeRepr.of[T].baseClasses.contains(TypeRepr.of[T].typeSymbol))
313+
Some(TypeRepr.of[T].typeSymbol.fullName.split('.'))
314+
else None
313315
val segments = TypeRepr.of[T].baseClasses
314316
.filter(_.flags.is(Flags.Sealed))
315317
.flatMap(_.children)
316318
.filter(_.flags.is(Flags.Case))
317-
.map(_.fullName.split('.'))
319+
.map(_.fullName.split('.')) ++
320+
sealedClassSymbol.toList
318321

319322
val identicalSegmentCount = Range(0, segments.map(_.length).max - 1)
320323
.takeWhile(i => segments.map(_.lift(i)).distinct.size == 1)

upickle/test/src/upickle/MacroTests.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import upickle.default.{read, write, ReadWriter => RW}
66

77
case class Trivial(a: Int = 1)
88

9+
sealed case class SealedClass(i: Int, s: String)
10+
object SealedClass {
11+
implicit val rw: RW[SealedClass] = upickle.default.macroRW
12+
}
13+
914
case class KeyedPerson(
1015
@upickle.implicits.key("first_name") firstName: String = "N/A",
1116
@upickle.implicits.key("last_name") lastName: String)
@@ -872,5 +877,9 @@ object MacroTests extends TestSuite {
872877
)
873878

874879
}
880+
881+
test("sealedClass"){
882+
assert(write(SealedClass(3, "Hello")) == """{"$type":"SealedClass","i":3,"s":"Hello"}""")
883+
}
875884
}
876885
}

0 commit comments

Comments
 (0)