From 2dcbfb614f2cd732680ebba5f7aaa4d70a89c6a9 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 4 Jul 2024 22:19:34 +0800 Subject: [PATCH] . --- .../src-3/upickle/implicits/Readers.scala | 17 +++- .../src-3/upickle/implicits/Writers.scala | 2 + .../src-3/upickle/implicits/macros.scala | 42 ++++++++++ .../test/src-3/upickle/DerivationTests.scala | 80 +++++++++---------- upickle/test/src/upickle/LegacyTests.scala | 1 - 5 files changed, 98 insertions(+), 44 deletions(-) diff --git a/upickle/implicits/src-3/upickle/implicits/Readers.scala b/upickle/implicits/src-3/upickle/implicits/Readers.scala index d749e2a0f..a87057227 100644 --- a/upickle/implicits/src-3/upickle/implicits/Readers.scala +++ b/upickle/implicits/src-3/upickle/implicits/Readers.scala @@ -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] => @@ -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 diff --git a/upickle/implicits/src-3/upickle/implicits/Writers.scala b/upickle/implicits/src-3/upickle/implicits/Writers.scala index fb9d066d2..40e512320 100644 --- a/upickle/implicits/src-3/upickle/implicits/Writers.scala +++ b/upickle/implicits/src-3/upickle/implicits/Writers.scala @@ -48,6 +48,7 @@ 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 @@ -55,6 +56,7 @@ trait WritersVersionSpecific writer, macros.tagKey[T], macros.tagName[T], + macros.shortTagName[T], Annotator.Checker.Cls(implicitly[ClassTag[T]].runtimeClass), ) else writer diff --git a/upickle/implicits/src-3/upickle/implicits/macros.scala b/upickle/implicits/src-3/upickle/implicits/macros.scala index 814eb4274..af38e6bff 100644 --- a/upickle/implicits/src-3/upickle/implicits/macros.scala +++ b/upickle/implicits/src-3/upickle/implicits/macros.scala @@ -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 `` 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] = diff --git a/upickle/test/src-3/upickle/DerivationTests.scala b/upickle/test/src-3/upickle/DerivationTests.scala index ccd71ea6e..3bb05a0bf 100644 --- a/upickle/test/src-3/upickle/DerivationTests.scala +++ b/upickle/test/src-3/upickle/DerivationTests.scala @@ -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 } } @@ -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"}""" ) } @@ -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"){ diff --git a/upickle/test/src/upickle/LegacyTests.scala b/upickle/test/src/upickle/LegacyTests.scala index 0dc2ad06c..e12376e85 100644 --- a/upickle/test/src/upickle/LegacyTests.scala +++ b/upickle/test/src/upickle/LegacyTests.scala @@ -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