Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
lihaoyi committed Jul 4, 2024
1 parent fb43360 commit 2dcbfb6
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 44 deletions.
17 changes: 14 additions & 3 deletions upickle/implicits/src-3/upickle/implicits/Readers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,19 @@ trait ReadersVersionSpecific
}

inline if macros.isSingleton[T] then
annotate[T](SingletonReader[T](macros.getSingleton[T]), macros.tagKey[T], macros.tagName[T])
annotate[T](
SingletonReader[T](macros.getSingleton[T]),
macros.tagKey[T],
macros.tagName[T],
macros.shortTagName[T]
)
else if macros.isMemberOfSealedHierarchy[T] then
annotate[T](reader, macros.tagKey[T], macros.tagName[T])
annotate[T](
reader,
macros.tagKey[T],
macros.tagName[T],
macros.shortTagName[T],
)
else reader

case m: Mirror.SumOf[T] =>
Expand All @@ -101,7 +111,8 @@ trait ReadersVersionSpecific
val actual = implicitly[Reader[V]].asInstanceOf[TaggedReader[T]]
val tagKey = macros.tagKey[T]
val tagName = macros.tagName[T]
new TaggedReader.Leaf(tagKey, tagName, actual.findReader(tagName))
val shortTagName = macros.shortTagName[T]
new TaggedReader.Leaf(tagKey, tagName, shortTagName, actual.findReader(tagName))
}

// see comment in MacroImplicits as to why Dotty's extension methods aren't used here
Expand Down
2 changes: 2 additions & 0 deletions upickle/implicits/src-3/upickle/implicits/Writers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ trait WritersVersionSpecific
SingletonWriter[T](null.asInstanceOf[T]),
macros.tagKey[T],
macros.tagName[T],
macros.shortTagName[T],
Annotator.Checker.Val(macros.getSingleton[T]),
)
else if macros.isMemberOfSealedHierarchy[T] then
annotate[T](
writer,
macros.tagKey[T],
macros.tagName[T],
macros.shortTagName[T],
Annotator.Checker.Cls(implicitly[ClassTag[T]].runtimeClass),
)
else writer
Expand Down
42 changes: 42 additions & 0 deletions upickle/implicits/src-3/upickle/implicits/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,48 @@ def tagNameImpl[T](using Quotes, Type[T]): Expr[String] =
TypeTree.of[T].tpe.typeSymbol.fullName.filter(_ != '$')
}
)
inline def shortTagName[T]: String = ${ shortTagNameImpl[T] }
def shortTagNameImpl[T](using Quotes, Type[T]): Expr[String] =
import quotes.reflect._
val sym = TypeTree.of[T].symbol
val segments = TypeRepr.of[T].baseClasses
.filter(_.flags.is(Flags.Sealed))
.flatMap(_.children)
.filter(_.flags.is(Flags.Case))
.map(_.fullName.split('.'))

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

Expr(
extractKey(sym) match
case Some(name) => name
case None =>
// In Scala 3 enums, we use the short name of each case as the tag, rather
// than the fully-qualified name. We can do this because we know that all
// enum cases are in the same `enum Foo` namespace with distinct short names,
// whereas sealed trait instances could be all over the place with identical
// short names only distinguishable by their prefix.
//
// Harmonizing these two cases further is TBD
if (TypeRepr.of[T] <:< TypeRepr.of[scala.reflect.Enum]) {
// Sometimes .symbol/.typeSymbol gives the wrong thing:
//
// - `.symbol.name` returns `<none>` for `LinkedList.Node[T]`
// - `.typeSymbol` returns `LinkedList` for `LinkedList.End`
//
// so we just mangle `.show` even though it's super gross
TypeRepr.of[T] match{
case TermRef(prefix, value) => value
case TypeRef(prefix, value) => value
case AppliedType(TermRef(prefix, value), _) => value
case AppliedType(TypeRef(prefix, value), _) => value
}
} else {
TypeTree.of[T].tpe.typeSymbol.fullName.filter(_ != '$').split('.').drop(identicalSegmentCount).mkString(".")
}
)

inline def isSingleton[T]: Boolean = ${ isSingletonImpl[T] }
def isSingletonImpl[T](using Quotes, Type[T]): Expr[Boolean] =
Expand Down
80 changes: 40 additions & 40 deletions upickle/test/src-3/upickle/DerivationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ object DerivationTests extends TestSuite {
}
test("animal"){
upickle.default.write(Person("Peter", "Ave 10")) ==>
"""{"$type":"upickle.Person","name":"Peter","address":"Ave 10"}"""
"""{"$type":"Person","name":"Peter","address":"Ave 10"}"""

upickle.default.read[Animal]("""{"$type":"upickle.Person","name":"Peter","address":"Ave 10"}""") ==>
upickle.default.read[Animal]("""{"$type":"Person","name":"Peter","address":"Ave 10"}""") ==>
Person("Peter", "Ave 10")

upickle.default.write(Cthulu) ==> "\"upickle.Cthulu\""
upickle.default.read[Animal]("\"upickle.Cthulu\"") ==> Cthulu
upickle.default.write(Cthulu) ==> "\"Cthulu\""
upickle.default.read[Animal]("\"Cthulu\"") ==> Cthulu
}
}

Expand All @@ -76,36 +76,36 @@ object DerivationTests extends TestSuite {
test("caseClassTagged") - {
rw[Person](
Person("Peter", "Avenue 10 Zurich", 20),
"""{"$type":"upickle.Person","name":"Peter","address":"Avenue 10 Zurich"}"""
"""{"$type":"Person","name":"Peter","address":"Avenue 10 Zurich"}"""
)
}

test("trait") - {
rw[Animal](
Person("Peter", "Avenue 10 Zurich" ,20),
"""{"$type":"upickle.Person","name":"Peter","address":"Avenue 10 Zurich"}"""
"""{"$type":"Person","name":"Peter","address":"Avenue 10 Zurich"}"""
)
rw[Animal](
Person("Peter", "Avenue 10 Zurich"),
"""{"$type":"upickle.Person","name":"Peter","address":"Avenue 10 Zurich"}"""
"""{"$type":"Person","name":"Peter","address":"Avenue 10 Zurich"}"""
)
}

test("caseObjectWriter") - {
rw[Animal](Cthulu, """"upickle.Cthulu"""", """{"$type":"upickle.Cthulu"}""")
rw[Cthulu.type](Cthulu, """"upickle.Cthulu"""", """{"$type":"upickle.Cthulu"}""")
rw[Animal](Cthulu, """"Cthulu"""", """{"$type":"Cthulu"}""")
rw[Cthulu.type](Cthulu, """"Cthulu"""", """{"$type":"Cthulu"}""")
}

test("caseObjectWriterImplicit") - {
rw[AnimalImplicit](
CthuluImplicit,
""""upickle.CthuluImplicit"""",
"""{"$type":"upickle.CthuluImplicit"}"""
""""CthuluImplicit"""",
"""{"$type":"CthuluImplicit"}"""
)
rw[CthuluImplicit.type](
CthuluImplicit,
""""upickle.CthuluImplicit"""",
"""{"$type":"upickle.CthuluImplicit"}"""
""""CthuluImplicit"""",
"""{"$type":"CthuluImplicit"}"""
)
}

Expand All @@ -115,53 +115,53 @@ object DerivationTests extends TestSuite {
rw(Recur(Some(Recur(None))), """{"recur":[{"recur": []}]}""")
}
test("multilevel"){
rw(Level1Cls(1), """{"$type": "upickle.Level1Cls", "i": 1}""")
rw(Level1Cls(1): Level1, """{"$type": "upickle.Level1Cls", "i": 1}""")
rw(Level1Cls(1), """{"$type": "Level1Cls", "i": 1}""")
rw(Level1Cls(1): Level1, """{"$type": "Level1Cls", "i": 1}""")

rw(Level1Obj, """"upickle.Level1Obj"""")
rw(Level1Obj: Level1, """"upickle.Level1Obj"""")
rw(Level1Obj, """"Level1Obj"""")
rw(Level1Obj: Level1, """"Level1Obj"""")

rw(Level2Cls("str"), """{"$type": "upickle.Level2Cls", "s": "str"}""")
rw(Level2Cls("str"): Level2, """{"$type": "upickle.Level2Cls", "s": "str"}""")
rw(Level2Cls("str"): Level1, """{"$type": "upickle.Level2Cls", "s": "str"}""")
rw(Level2Cls("str"), """{"$type": "Level2Cls", "s": "str"}""")
rw(Level2Cls("str"): Level2, """{"$type": "Level2Cls", "s": "str"}""")
rw(Level2Cls("str"): Level1, """{"$type": "Level2Cls", "s": "str"}""")

rw(Level2Obj, """"upickle.Level2Obj"""")
rw(Level2Obj: Level2, """"upickle.Level2Obj"""")
rw(Level2Obj: Level1, """"upickle.Level2Obj"""")
rw(Level2Obj, """"Level2Obj"""")
rw(Level2Obj: Level2, """"Level2Obj"""")
rw(Level2Obj: Level1, """"Level2Obj"""")

rw(Level3Cls(true), """{"$type": "upickle.Level3Cls", "b": true}""")
rw(Level3Cls(true): Level3, """{"$type": "upickle.Level3Cls", "b": true}""")
rw(Level3Cls(true): Level2, """{"$type": "upickle.Level3Cls", "b": true}""")
rw(Level3Cls(true): Level1, """{"$type": "upickle.Level3Cls", "b": true}""")
rw(Level3Cls(true), """{"$type": "Level3Cls", "b": true}""")
rw(Level3Cls(true): Level3, """{"$type": "Level3Cls", "b": true}""")
rw(Level3Cls(true): Level2, """{"$type": "Level3Cls", "b": true}""")
rw(Level3Cls(true): Level1, """{"$type": "Level3Cls", "b": true}""")

rw(Level3Obj, """"upickle.Level3Obj"""")
rw(Level3Obj: Level3, """"upickle.Level3Obj"""")
rw(Level3Obj: Level2, """"upickle.Level3Obj"""")
rw(Level3Obj: Level1, """"upickle.Level3Obj"""")
rw(Level3Obj, """"Level3Obj"""")
rw(Level3Obj: Level3, """"Level3Obj"""")
rw(Level3Obj: Level2, """"Level3Obj"""")
rw(Level3Obj: Level1, """"Level3Obj"""")
}

test("abstractClass"){
rw(UnknownShirtSize, """ "upickle.UnknownShirtSize" """)
rw(UnknownShirtSize: ShirtSize, """ "upickle.UnknownShirtSize" """)
rw(XL, """ "upickle.XL" """)
rw(XL: ShirtSize, """ "upickle.XL" """)
rw(XL: KnownShirtSize, """ "upickle.XL" """)
rw(UnknownShirtSize, """ "UnknownShirtSize" """)
rw(UnknownShirtSize: ShirtSize, """ "UnknownShirtSize" """)
rw(XL, """ "XL" """)
rw(XL: ShirtSize, """ "XL" """)
rw(XL: KnownShirtSize, """ "XL" """)
}
test("failures"){
test("caseClassTaggedWrong") - {
val e = intercept[upickle.core.AbortException] {
upickle.default.read[Person](
"""{"$type":"upickle.Cat","name":"Peter","owner":{"$type":"upickle.Person","name": "bob", "address": "Avenue 10 Zurich"}}"""
"""{"$type":"Cat","name":"Peter","owner":{"$type":"Person","name": "bob", "address": "Avenue 10 Zurich"}}"""
)
}
assert(e.getMessage == "invalid tag for tagged object: upickle.Cat at index 9")
assert(e.getMessage == "invalid tag for tagged object: Cat at index 9")
}

test("multilevelTaggedWrong") - {
val e = intercept[upickle.core.AbortException] {
upickle.default.read[Level2]("""{"$type": "upickle.Level1Cls", "i": 1}""")
upickle.default.read[Level2]("""{"$type": "Level1Cls", "i": 1}""")
}
assert(e.getMessage == "invalid tag for tagged object: upickle.Level1Cls at index 10")
assert(e.getMessage == "invalid tag for tagged object: Level1Cls at index 10")
}
}
test("issue468"){
Expand Down
1 change: 0 additions & 1 deletion upickle/test/src/upickle/LegacyTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import LegacyTestUtil.rw

import upickle.legacy.{ReadWriter => RW, Reader => R, Writer => W}
object LegacyTests extends TestSuite {

val tests = Tests {
test("simpleAdt"){
implicit def ADT0rw: RW[ADTs.ADT0] = upickle.legacy.macroRW
Expand Down

0 comments on commit 2dcbfb6

Please sign in to comment.