Skip to content

Commit

Permalink
Forwards-compatibility for uPickle 2.0.0 (#385)
Browse files Browse the repository at this point in the history
This PR makes uPickle master able to read the dictionary-based `Map`s introduced by https://github.com/com-lihaoyi/upickle/pull/382/files, and string-based case objects introduced by https://github.com/com-lihaoyi/upickle/pull/382/files. We also pull in the strictness improvements for the test suites, to ensure that we are still *writing* the old format, and the new format is only accepted during reads.

This should provide a smooth migration path for people who need to migrate their system piecemeal to uPickle 2.0.0
  • Loading branch information
lihaoyi committed Apr 11, 2022
1 parent 693af26 commit a4247cd
Show file tree
Hide file tree
Showing 16 changed files with 309 additions and 113 deletions.
8 changes: 6 additions & 2 deletions core/src/upickle/core/Types.scala
Expand Up @@ -186,7 +186,7 @@ trait Types{ types =>

abstract class CaseR[V] extends SimpleReader[V]{
override def expectedMsg = "expected dictionary"

override def visitString(s: CharSequence, index: Int) = visitObject(0, index).visitEnd(index)
abstract class CaseObjectContext(fieldCount: Int) extends ObjVisitor[Any, V]{
def storeAggregatedValue(currentIndex: Int, v: Any): Unit
var found = 0L
Expand Down Expand Up @@ -274,7 +274,10 @@ trait Types{ types =>
}
}
class SingletonR[T](t: T) extends CaseR[T]{
override def expectedMsg = "expected dictionary"
override def expectedMsg = "expected string or dictionary"

override def visitString(s: CharSequence, index: Int) = t

override def visitObject(length: Int, index: Int) = new ObjVisitor[Any, T] {
def subVisitor = NoOpVisitor

Expand Down Expand Up @@ -312,6 +315,7 @@ trait Types{ types =>
override def expectedMsg = taggedExpectedMsg
override def visitArray(length: Int, index: Int) = taggedArrayContext(this, index)
override def visitObject(length: Int, index: Int) = taggedObjectContext(this, index)
override def visitString(s: CharSequence, index: Int) = findReader(s.toString).visitString(s, index)
}
object TaggedReader{
class Leaf[T](tag: String, r: Reader[T]) extends TaggedReader[T]{
Expand Down
6 changes: 3 additions & 3 deletions implicits/src-2/upickle/implicits/MacroImplicits.scala
Expand Up @@ -38,9 +38,9 @@ object MacroImplicits{

}
trait MacroImplicits extends MacrosCommon { this: upickle.core.Types =>
implicit def macroSingletonR[T <: Singleton]: Reader[T] = macro MacroImplicits.applyR[T]
implicit def macroSingletonW[T <: Singleton]: Writer[T] = macro MacroImplicits.applyW[T]
implicit def macroSingletonRW[T <: Singleton]: ReadWriter[T] = macro MacroImplicits.applyRW[T]
// def macroSingletonR[T <: Singleton]: Reader[T] = macro MacroImplicits.applyR[T]
// def macroSingletonW[T <: Singleton]: Writer[T] = macro MacroImplicits.applyW[T]
// def macroSingletonRW[T <: Singleton]: ReadWriter[T] = macro MacroImplicits.applyRW[T]
def macroR[T]: Reader[T] = macro MacroImplicits.applyR[T]
def macroW[T]: Writer[T] = macro MacroImplicits.applyW[T]
def macroRW[T]: ReadWriter[T] = macro MacroImplicits.applyRW[ReadWriter[T]]
Expand Down
113 changes: 76 additions & 37 deletions implicits/src/upickle/implicits/Readers.scala
Expand Up @@ -26,11 +26,16 @@ trait Readers extends upickle.core.Types

def visitKeyValue(v: Any): Unit = ()
}

override def visitNull(index: Int): Unit = ()
}

implicit val BooleanReader: Reader[Boolean] = new SimpleReader[Boolean] {
override def expectedMsg = "expected boolean"
override def visitTrue(index: Int) = true
override def visitFalse(index: Int) = false

override def visitString(s: CharSequence, index: Int) = s.toString.toBoolean
}

protected trait NumericReader[T] extends SimpleReader[T] {
Expand All @@ -49,7 +54,7 @@ trait Readers extends upickle.core.Types

implicit val DoubleReader: Reader[Double] = new NumericReader[Double] {
override def expectedMsg = "expected number"
override def visitString(s: CharSequence, index: Int) = s.toString.toDouble
override def visitString(s: CharSequence, index: Int) = visitFloat64String(s.toString, index)
override def visitInt32(d: Int, index: Int) = d
override def visitInt64(d: Long, index: Int) = d
override def visitUInt64(d: Long, index: Int) = d
Expand All @@ -58,9 +63,11 @@ trait Readers extends upickle.core.Types
override def visitFloat64StringParts(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = {
s.toString.toDouble
}

}
implicit val IntReader: Reader[Int] = new NumericReader[Int] {
override def expectedMsg = "expected number"
override def visitString(s: CharSequence, index: Int) = visitFloat64String(s.toString, index)
override def visitInt32(d: Int, index: Int) = d
override def visitInt64(d: Long, index: Int) = d.toInt
override def visitUInt64(d: Long, index: Int) = d.toInt
Expand All @@ -69,11 +76,13 @@ trait Readers extends upickle.core.Types
override def visitFloat64StringParts(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = {
Util.parseIntegralNum(s, decIndex, expIndex, index).toInt
}

}

implicit val FloatReader: Reader[Float] = new NumericReader[Float] {
override def expectedMsg = "expected number"

override def visitString(s: CharSequence, index: Int) = s.toString.toFloat
override def visitString(s: CharSequence, index: Int) = visitFloat64String(s.toString, index)
override def visitInt32(d: Int, index: Int) = d.toFloat
override def visitInt64(d: Long, index: Int) = d.toFloat
override def visitUInt64(d: Long, index: Int) = d.toFloat
Expand All @@ -82,9 +91,12 @@ trait Readers extends upickle.core.Types
override def visitFloat64StringParts(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = {
s.toString.toFloat
}

}
implicit val ShortReader: Reader[Short] = new NumericReader[Short] {

implicit val ShortReader: Reader[Short] = new NumericReader[Short]{
override def expectedMsg = "expected number"
override def visitString(s: CharSequence, index: Int) = visitFloat64String(s.toString, index)
override def visitInt32(d: Int, index: Int) = d.toShort
override def visitInt64(d: Long, index: Int) = d.toShort
override def visitUInt64(d: Long, index: Int) = d.toShort
Expand All @@ -93,9 +105,12 @@ trait Readers extends upickle.core.Types
override def visitFloat64StringParts(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = {
Util.parseIntegralNum(s, decIndex, expIndex, index).toShort
}

}

implicit val ByteReader: Reader[Byte] = new NumericReader[Byte] {
override def expectedMsg = "expected number"
override def visitString(s: CharSequence, index: Int) = visitFloat64String(s.toString, index)
override def visitInt32(d: Int, index: Int) = d.toByte
override def visitInt64(d: Long, index: Int) = d.toByte
override def visitUInt64(d: Long, index: Int) = d.toByte
Expand All @@ -104,15 +119,20 @@ trait Readers extends upickle.core.Types
override def visitFloat64StringParts(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = {
Util.parseIntegralNum(s, decIndex, expIndex, index).toByte
}

}

implicit val StringReader: Reader[String] = new SimpleReader[String] {
override def expectedMsg = "expected string"
override def visitString(s: CharSequence, index: Int) = s.toString

}
class MapStringReader[T](f: CharSequence => T) extends SimpleReader[T] {


trait SimpleStringReader[T] extends SimpleReader[T] {
override def expectedMsg = "expected string"
override def visitString(s: CharSequence, index: Int) = f(s)
override def visitString(s: CharSequence, index: Int) = readString(s)
def readString(s: CharSequence): T
}

implicit val CharReader: Reader[Char] = new NumericReader[Char] {
Expand All @@ -127,8 +147,13 @@ trait Readers extends upickle.core.Types
override def visitFloat64StringParts(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = {
Util.parseIntegralNum(s, decIndex, expIndex, index).toChar
}

}
implicit val UUIDReader: Reader[UUID] = new MapStringReader(s => UUID.fromString(s.toString))

implicit val UUIDReader: Reader[UUID] = new SimpleStringReader[UUID]{
def readString(s: CharSequence) = UUID.fromString(s.toString)
}

implicit val LongReader: Reader[Long] = new NumericReader[Long] {
override def expectedMsg = "expected number"
override def visitString(d: CharSequence, index: Int) = upickle.core.Util.parseLong(d, 0, d.length())
Expand All @@ -140,34 +165,46 @@ trait Readers extends upickle.core.Types
override def visitFloat64StringParts(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = {
Util.parseIntegralNum(s, decIndex, expIndex, index).toLong
}

}

implicit val BigIntReader: Reader[BigInt] = new SimpleStringReader[BigInt]{
def readString(s: CharSequence) = BigInt(s.toString)
}
implicit val BigDecimalReader: Reader[BigDecimal] = new SimpleStringReader[BigDecimal]{
def readString(s: CharSequence) = BigDecimal(s.toString)
}
implicit val SymbolReader: Reader[Symbol] = new SimpleStringReader[Symbol]{
def readString(s: CharSequence) = Symbol(s.toString)
}
implicit val BigIntReader: Reader[BigInt] = new MapStringReader(s => BigInt(s.toString))
implicit val BigDecimalReader: Reader[BigDecimal] = new MapStringReader(s => BigDecimal(s.toString))
implicit val SymbolReader: Reader[Symbol] = new MapStringReader(s => Symbol(s.toString))



def MapReader0[M[A, B] <: collection.Map[A, B], K, V]
(make: Iterable[(K, V)] => M[K, V])
(implicit k: Reader[K], v: Reader[V]): Reader[M[K, V]] = {
if (k ne StringReader) SeqLikeReader[Array, (K, V)].map(x => make(x))
else new SimpleReader[M[K, V]]{
override def visitObject(length: Int, index: Int) = new ObjVisitor[Any, M[K, V]] {
val strings = mutable.Buffer.empty[K]
new SimpleReader[M[K, V]]{
override def visitObject(length: Int,index: Int) = new ObjVisitor[Any, M[K, V]] {
val keys = mutable.Buffer.empty[K]
val values = mutable.Buffer.empty[V]
def subVisitor = v

def visitKey(index: Int) = StringReader
def visitKey(index: Int) = k

def visitKeyValue(s: Any): Unit = {
strings.append(s.toString.asInstanceOf[K])
}
def visitKeyValue(s: Any): Unit = keys.append(s.asInstanceOf[K])

def visitValue(v: Any, index: Int): Unit = values.append(v.asInstanceOf[V])

def visitEnd(index: Int) = make(strings.zip(values))
def visitEnd(index: Int) = make(keys.zip(values))
}

override def visitArray(length: Int, index: Int) = {
SeqLikeReader[Array, (K, V)](Tuple2Reader(k, v), implicitly)
.map(x => make(x))
.visitArray(length, index)
}

def expectedMsg = "expected map"
def expectedMsg = "expected map or sequence"
}
}

Expand Down Expand Up @@ -255,27 +292,29 @@ trait Readers extends upickle.core.Types
}
}

implicit val DurationReader: Reader[Duration] = new MapStringReader( s =>
if (s.charAt(0) == 'i' &&
implicit val DurationReader: Reader[Duration] = new SimpleStringReader[Duration]{
override def readString(s: CharSequence) =
if (s.charAt(0) == 'i' &&
s.charAt(1) == 'n' &&
s.charAt(2) == 'f'
&& s.length() == 3){
Duration.Inf
} else if (s.charAt(0) == '-' &&
s.charAt(1) == 'i' &&
s.charAt(2) == 'n' &&
s.charAt(3) == 'f' &&
s.length() == 4){
Duration.MinusInf
} else if (s.charAt(0) == 'u' &&
s.charAt(1) == 'n' &&
s.charAt(2) == 'd' &&
s.charAt(3) == 'e' &&
s.charAt(4) == 'f' &&
s.length() == 5){
Duration.Undefined
}else Duration(upickle.core.Util.parseLong(s, 0, s.length()), TimeUnit.NANOSECONDS)
)
Duration.Inf
} else if (s.charAt(0) == '-' &&
s.charAt(1) == 'i' &&
s.charAt(2) == 'n' &&
s.charAt(3) == 'f' &&
s.length() == 4){
Duration.MinusInf
} else if (s.charAt(0) == 'u' &&
s.charAt(1) == 'n' &&
s.charAt(2) == 'd' &&
s.charAt(3) == 'e' &&
s.charAt(4) == 'f' &&
s.length() == 5){
Duration.Undefined
}else Duration(upickle.core.Util.parseLong(s, 0, s.length()), TimeUnit.NANOSECONDS)

}

implicit val InfiniteDurationReader: Reader[Duration.Infinite] = DurationReader.narrow[Duration.Infinite]
implicit val FiniteDurationReader: Reader[FiniteDuration] = DurationReader.narrow[FiniteDuration]
Expand Down
7 changes: 6 additions & 1 deletion ujson/src/ujson/JsVisitor.scala
Expand Up @@ -14,7 +14,12 @@ trait JsVisitor[-T, +J] extends Visitor[T, J]{

}

def visitFloat32(d: Float, index: Int): J = visitFloat64(d, index)
def visitFloat32(d: Float, index: Int): J = {
val i = d.toLong
if(i == d) visitFloat64StringParts(i.toString, -1, -1, index)
else visitFloat64String(d.toString, index)
}

def visitInt32(i: Int, index: Int): J = visitFloat64(i, index)
def visitInt64(i: Long, index: Int): J = {
if (math.abs(i) > math.pow(2, 53) || i == -9223372036854775808L) visitString(i.toString, index)
Expand Down
14 changes: 14 additions & 0 deletions ujson/templates/BaseElemRenderer.scala
Expand Up @@ -142,6 +142,20 @@ class BaseElemRenderer[T <: upickle.core.ElemOps.Output]
out
}

override def visitFloat32(d: Float, index: Int) = {
d match{
case Float.PositiveInfinity => visitNonNullString("Infinity", -1)
case Float.NegativeInfinity => visitNonNullString("-Infinity", -1)
case d if java.lang.Float.isNaN(d) => visitNonNullString("NaN", -1)
case d =>
val i = d.toInt
if (d == i) visitFloat64StringParts(i.toString, -1, -1, index)
else super.visitFloat32(d, index)
flushBuffer()
}
flushElemBuilder()
out
}

def visitString(s: CharSequence, index: Int) = {

Expand Down
6 changes: 5 additions & 1 deletion upickle/test/src-2.12-2.13/upickle/LegacyTests.scala
Expand Up @@ -58,7 +58,7 @@ object LegacyTests extends TestSuite {
implicit def Brw: RW[B] = upickle.legacy.macroRW
implicit def Crw: RW[C] = upickle.legacy.macroRW
implicit def Arw: RW[A] = upickle.legacy.ReadWriter.merge(Crw, Brw)

implicit def AnZrw: RW[AnZ.type] = upickle.legacy.macroRW
implicit def Zrw: RW[Z] = upickle.legacy.macroRW
test("shallow"){
test - rw(B(1), """["upickle.Hierarchy.B",{"i":1}]""")
Expand Down Expand Up @@ -101,6 +101,8 @@ object LegacyTests extends TestSuite {
import Singletons._

implicit def AArw: RW[AA] = legacy.macroRW
implicit def BBrw: RW[BB.type] = legacy.macroRW
implicit def CCrw: RW[CC.type] = legacy.macroRW
rw(BB, """["upickle.Singletons.BB",{}]""")
rw(CC, """["upickle.Singletons.CC",{}]""")
rw(BB: AA, """["upickle.Singletons.BB",{}]""")
Expand Down Expand Up @@ -190,6 +192,7 @@ object LegacyTests extends TestSuite {


implicit def LLrw: RW[LL] = upickle.legacy.macroRW
implicit def Endrw: RW[End.type] = upickle.legacy.macroRW
rw(
IntTree(123, List(IntTree(456, Nil), IntTree(789, Nil))),
"""{"value":123,"children":[{"value":456,"children":[]},{"value":789,"children":[]}]}"""
Expand Down Expand Up @@ -301,6 +304,7 @@ object LegacyTests extends TestSuite {
rw(new CaseClassWithJson(ujson.Arr(ujson.Num(7), ujson.Str("lol"))), """{"json":[7,"lol"]}""")
}
test("traitFromOtherPackage"){
implicit val BaseChildRW: RW[subpackage.Base.Child.type] = upickle.legacy.macroRW
implicit val BaseRW: RW[subpackage.Base] = upickle.legacy.macroRW
implicit val WrapperRW: RW[subpackage.Wrapper] = upickle.legacy.macroRW
upickle.legacy.write(subpackage.Wrapper(subpackage.Base.Child))
Expand Down
34 changes: 29 additions & 5 deletions upickle/test/src-2/upickle/AdvancedTests.scala
Expand Up @@ -211,14 +211,28 @@ object AdvancedTests extends TestSuite {
]
}"""
)
rw(End: LL, """{"$type":"upickle.Recursive.End"}""")
rw(Node(3, End): LL,
rw(
End: LL,
"""{"$type":"upickle.Recursive.End"}""",
""" "upickle.Recursive.End" """
)

rw(
Node(3, End): LL,
"""{
"$type": "upickle.Recursive.Node",
"c": 3,
"next": {"$type":"upickle.Recursive.End"}
}""")
rw(Node(6, Node(3, End)),
}""",
"""{
"$type": "upickle.Recursive.Node",
"c": 3,
"next": "upickle.Recursive.End"
}"""
)

rw(
Node(6, Node(3, End)),
"""{
"$type": "upickle.Recursive.Node",
"c": 6,
Expand All @@ -227,7 +241,17 @@ object AdvancedTests extends TestSuite {
"c":3,
"next":{"$type":"upickle.Recursive.End"}
}
}""")
}""",
"""{
"$type": "upickle.Recursive.Node",
"c": 6,
"next": {
"$type": "upickle.Recursive.Node",
"c":3,
"next": "upickle.Recursive.End"
}
}"""
)

}
test("gadt"){
Expand Down

0 comments on commit a4247cd

Please sign in to comment.