Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Break up Types.scala #460

Merged
merged 8 commits into from
Mar 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ object implicits extends Module {
* Auto-generated picklers and unpicklers, used for creating the 22
* versions of tuple-picklers and case-class picklers
*/
trait Generated extends upickle.core.Types{
trait Generated extends TupleReadWriters{
${tuples.mkString("\n")}
}
""")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package upickle.core

object Util {
object ParseUtils {

val hexes = Array[String](
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
Expand Down
233 changes: 32 additions & 201 deletions core/src/upickle/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -146,184 +146,6 @@ trait Types{ types =>
}
}

class TupleNWriter[V](val writers: Array[Writer[_]], val f: V => Array[Any]) extends Writer[V]{
def write0[R](out: Visitor[_, R], v: V): R = {
if (v == null) out.visitNull(-1)
else{
val ctx = out.visitArray(writers.length, -1)
val vs = f(v)
var i = 0
while(i < writers.length){
ctx.visitValue(
writers(i).asInstanceOf[Writer[Any]].write(
ctx.subVisitor.asInstanceOf[Visitor[Any, Nothing]],
vs(i)
),
-1
)
i += 1
}
ctx.visitEnd(-1)
}
}
}

class TupleNReader[V](val readers: Array[Reader[_]], val f: Array[Any] => V) extends SimpleReader[V]{

override def expectedMsg = "expected sequence"
override def visitArray(length: Int, index: Int) = new ArrVisitor[Any, V] {
val b = new Array[Any](readers.length)
var facadesIndex = 0

var start = facadesIndex
def visitValue(v: Any, index: Int): Unit = {
b(facadesIndex % readers.length) = v
facadesIndex = facadesIndex + 1
}

def visitEnd(index: Int) = {
val lengthSoFar = facadesIndex - start
if (lengthSoFar != readers.length) {
throw new Abort(
"expected " + readers.length + " items in sequence, found " + lengthSoFar
)
}
start = facadesIndex

f(b)

}

def subVisitor: Visitor[_, _] = {
readers(facadesIndex % readers.length)
}
}
}

abstract class CaseR[V] extends SimpleReader[V]{
override def expectedMsg = "expected dictionary"
override def visitString(s: CharSequence, index: Int) = visitObject(0, true, index).visitEnd(index)

abstract class CaseObjectContext(fieldCount: Int) extends ObjVisitor[Any, V] with BaseCaseObjectContext{
var found = 0L

def visitValue(v: Any, index: Int): Unit = {
if (currentIndex != -1 && ((found & (1L << currentIndex)) == 0)) {
storeAggregatedValue(currentIndex, v)
found |= (1L << currentIndex)
}
}

def storeValueIfNotFound(i: Int, v: Any) = {
if ((found & (1L << i)) == 0) {
found |= (1L << i)
storeAggregatedValue(i, v)
}
}
protected def errorMissingKeys(rawArgsLength: Int, mappedArgs: Array[String]) = {
val keys = for{
i <- 0 until rawArgsLength
if (found & (1L << i)) == 0
} yield mappedArgs(i)
throw new _root_.upickle.core.Abort(
"missing keys in dictionary: " + keys.mkString(", ")
)
}
protected def checkErrorMissingKeys(rawArgsBitset: Long) = {
found != rawArgsBitset
}
}
abstract class HugeCaseObjectContext(fieldCount: Int) extends ObjVisitor[Any, V] with BaseCaseObjectContext{
var found = new Array[Long](fieldCount / 64 + 1)

def visitValue(v: Any, index: Int): Unit = {
if (currentIndex != -1 && ((found(currentIndex / 64) & (1L << currentIndex)) == 0)) {
storeAggregatedValue(currentIndex, v)
found(currentIndex / 64) |= (1L << currentIndex)
}
}

def storeValueIfNotFound(i: Int, v: Any) = {
if ((found(i / 64) & (1L << i)) == 0) {
found(i / 64) |= (1L << i)
storeAggregatedValue(i, v)
}
}
protected def errorMissingKeys(rawArgsLength: Int, mappedArgs: Array[String]) = {
val keys = for{
i <- 0 until rawArgsLength
if (found(i / 64) & (1L << i)) == 0
} yield mappedArgs(i)
throw new _root_.upickle.core.Abort(
"missing keys in dictionary: " + keys.mkString(", ")
)
}
protected def checkErrorMissingKeys(rawArgsLength: Long) = {
var bits = 0
for(v <- found) bits += java.lang.Long.bitCount(v)
bits != rawArgsLength
}
}
}
trait CaseW[V] extends Writer[V]{
def length(v: V): Int
def writeToObject[R](ctx: ObjVisitor[_, R], v: V): Unit
def write0[R](out: Visitor[_, R], v: V): R = {
if (v == null) out.visitNull(-1)
else{
val ctx = out.visitObject(length(v), true, -1)
writeToObject(ctx, v)
ctx.visitEnd(-1)
}
}
def writeSnippet[R, V](objectAttributeKeyWriteMap: CharSequence => CharSequence,
ctx: _root_.upickle.core.ObjVisitor[_, R],
mappedArgsI: String,
w: Any,
value: Any) = {
val keyVisitor = ctx.visitKey(-1)
ctx.visitKeyValue(
keyVisitor.visitString(objectAttributeKeyWriteMap(mappedArgsI), -1)
)
ctx.narrow.visitValue(w.asInstanceOf[Writer[Any]].write(ctx.subVisitor, value), -1)
}
}
class SingletonR[T](t: T) extends CaseR[T]{
override def expectedMsg = "expected string or dictionary"

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

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

def visitKey(index: Int) = NoOpVisitor
def visitKeyValue(s: Any) = ()

def visitValue(v: Any, index: Int): Unit = ()

def visitEnd(index: Int) = t
}
}
class SingletonW[T](f: T) extends CaseW[T] {
def length(v: T) = 0
def writeToObject[R](ctx: ObjVisitor[_, R], v: T): Unit = () // do nothing
}


def taggedExpectedMsg: String
def taggedArrayContext[T](taggedReader: TaggedReader[T], index: Int): ArrVisitor[Any, T] = throw new Abort(taggedExpectedMsg)
def taggedObjectContext[T](taggedReader: TaggedReader[T], index: Int): ObjVisitor[Any, T] = throw new Abort(taggedExpectedMsg)
def taggedWrite[T, R](w: CaseW[T], tag: String, out: Visitor[_, R], v: T): R

private[this] def scanChildren[T, V](xs: Seq[T])(f: T => V) = {
var x: V = null.asInstanceOf[V]
val i = xs.iterator
while(x == null && i.hasNext){
val t = f(i.next())
if(t != null) x = t
}
x
}
trait TaggedReader[T] extends SimpleReader[T]{
def findReader(s: String): Reader[T]

Expand All @@ -347,15 +169,15 @@ trait Types{ types =>
}

trait TaggedWriter[T] extends Writer[T]{
def findWriter(v: Any): (String, CaseW[T])
def findWriter(v: Any): (String, ObjectWriter[T])
def write0[R](out: Visitor[_, R], v: T): R = {
val (tag, w) = findWriter(v)
taggedWrite(w, tag, out, v)

}
}
object TaggedWriter{
class Leaf[T](checker: Annotator.Checker, tag: String, r: CaseW[T]) extends TaggedWriter[T]{
class Leaf[T](checker: Annotator.Checker, tag: String, r: ObjectWriter[T]) extends TaggedWriter[T]{
def findWriter(v: Any) = {
checker match{
case Annotator.Checker.Cls(c) if c.isInstance(v) => tag -> r
Expand All @@ -365,7 +187,7 @@ trait Types{ types =>
}
}
class Node[T](rs: TaggedWriter[_ <: T]*) extends TaggedWriter[T]{
def findWriter(v: Any) = scanChildren(rs)(_.findWriter(v)).asInstanceOf[(String, CaseW[T])]
def findWriter(v: Any) = scanChildren(rs)(_.findWriter(v)).asInstanceOf[(String, ObjectWriter[T])]
}
}

Expand All @@ -375,7 +197,7 @@ trait Types{ types =>

}
object TaggedReadWriter{
class Leaf[T](c: ClassTag[_], tag: String, r: CaseW[T] with Reader[T]) extends TaggedReadWriter[T]{
class Leaf[T](c: ClassTag[_], tag: String, r: ObjectWriter[T] with Reader[T]) extends TaggedReadWriter[T]{
def findReader(s: String) = if (s == tag) r else null
def findWriter(v: Any) = {
if (c.runtimeClass.isInstance(v)) (tag -> r)
Expand All @@ -384,22 +206,45 @@ trait Types{ types =>
}
class Node[T](rs: TaggedReadWriter[_ <: T]*) extends TaggedReadWriter[T]{
def findReader(s: String) = scanChildren(rs)(_.findReader(s)).asInstanceOf[Reader[T]]
def findWriter(v: Any) = scanChildren(rs)(_.findWriter(v)).asInstanceOf[(String, CaseW[T])]
def findWriter(v: Any) = scanChildren(rs)(_.findWriter(v)).asInstanceOf[(String, ObjectWriter[T])]
}
}

def taggedExpectedMsg: String

def taggedArrayContext[T](taggedReader: TaggedReader[T], index: Int): ArrVisitor[Any, T] = throw new Abort(taggedExpectedMsg)

def taggedObjectContext[T](taggedReader: TaggedReader[T], index: Int): ObjVisitor[Any, T] = throw new Abort(taggedExpectedMsg)

def taggedWrite[T, R](w: ObjectWriter[T], tag: String, out: Visitor[_, R], v: T): R

private[this] def scanChildren[T, V](xs: Seq[T])(f: T => V) = {
var x: V = null.asInstanceOf[V]
val i = xs.iterator
while (x == null && i.hasNext) {
val t = f(i.next())
if (t != null) x = t
}
x
}

trait ObjectWriter[T] extends Writer[T]{
def length(v: T): Int
def writeToObject[R](ctx: ObjVisitor[_, R], v: T): Unit
}
}

/**
* Wrap a CaseR or CaseW reader/writer to handle $type tags during reading and writing.
* Wrap a CaseClassReader or CaseClassWriter reader/writer to handle $type tags during reading and writing.
*
* Note that Scala 3 singleton `enum` values do not have proper `ClassTag[V]`s
* like Scala 2 `case object`s do, so we instead use a `Checker.Val` to check
* for `.equals` equality during writes to determine which tag to use.
*/
trait Annotator { this: Types =>
def annotate[V](rw: CaseR[V], n: String): TaggedReader[V]
def annotate[V](rw: CaseW[V], n: String, checker: Annotator.Checker): TaggedWriter[V]
def annotate[V](rw: CaseW[V], n: String)(implicit ct: ClassTag[V]): TaggedWriter[V] =
def annotate[V](rw: Reader[V], n: String): TaggedReader[V]
def annotate[V](rw: ObjectWriter[V], n: String, checker: Annotator.Checker): TaggedWriter[V]
def annotate[V](rw: ObjectWriter[V], n: String)(implicit ct: ClassTag[V]): TaggedWriter[V] =
annotate(rw, n, Annotator.Checker.Cls(ct.runtimeClass))
}
object Annotator{
Expand All @@ -409,17 +254,3 @@ object Annotator{
case class Val(v: Any) extends Checker
}
}

trait BaseCaseObjectContext {
def storeAggregatedValue(currentIndex: Int, v: Any): Unit

def visitKey(index: Int) = _root_.upickle.core.StringVisitor

var currentIndex = -1

def storeValueIfNotFound(i: Int, v: Any): Unit

protected def errorMissingKeys(rawArgsLength: Int, mappedArgs: Array[String]): Unit

protected def checkErrorMissingKeys(rawArgsBitset: Long): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import utest._

import scala.util.Try

object UtilTests extends TestSuite {
object ParseUtilTests extends TestSuite {

val tests = Tests {

test("parseLong - valid") {
test("0")(Util.parseLong("0", 0, 1) ==> 0L)
test("42")(Util.parseLong("42", 0, 2) ==> 42L)
test("x0")(Util.parseLong("x0", 1, 2) ==> 0L)
test("x0x")(Util.parseLong("x0x", 1, 2) ==> 0L)
test("x-1x")(Util.parseLong("x-1x", 1, 3) ==> -1L)
test("0")(ParseUtils.parseLong("0", 0, 1) ==> 0L)
test("42")(ParseUtils.parseLong("42", 0, 2) ==> 42L)
test("x0")(ParseUtils.parseLong("x0", 1, 2) ==> 0L)
test("x0x")(ParseUtils.parseLong("x0x", 1, 2) ==> 0L)
test("x-1x")(ParseUtils.parseLong("x-1x", 1, 3) ==> -1L)
}

test("parseLong - invalid") {
def invalid(input: String)(start: Int = 0, end: Int = input.length) = {
val e = intercept[NumberFormatException] {
Util.parseLong(input, start, end)
ParseUtils.parseLong(input, start, end)
}
e.getMessage ==> input.substring(start, end)
}
Expand All @@ -42,9 +42,9 @@ object UtilTests extends TestSuite {
// Roundabout way to avoid scala.js differences: https://github.com/scala-js/scala-js/issues/3546
val isValidRange = 0 <= start && start <= end && end <= s.length
if (isValidRange) {
Util.parseLong(s, start, end)
ParseUtils.parseLong(s, start, end)
} else {
intercept[java.lang.IndexOutOfBoundsException](Util.parseLong(s, start, end))
intercept[java.lang.IndexOutOfBoundsException](ParseUtils.parseLong(s, start, end))
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions implicits/src-2/upickle/implicits/internal/Macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ object Macros {
abstract class Reading[M[_]] extends DeriveDefaults[M] {
val c: scala.reflect.macros.blackbox.Context
import c.universe._
def wrapObject(t: c.Tree) = q"new ${c.prefix}.SingletonR($t)"
def wrapObject(t: c.Tree) = q"new ${c.prefix}.SingletonReader($t)"

def wrapCaseN(companion: c.Tree,
rawArgs: Seq[String],
Expand All @@ -250,8 +250,8 @@ object Macros {
for (i <- rawArgs.indices)
yield q"private[this] lazy val ${localReaders(i)} = implicitly[${c.prefix}.Reader[${argTypes(i)}]]"
}
new ${c.prefix}.CaseR[$targetType]{
override def visitObject(length: Int, jsonableKeys: Boolean, index: Int) = new ${if (rawArgs.size <= 64) tq"CaseObjectContext" else tq"HugeCaseObjectContext"}(${rawArgs.size}){
new ${c.prefix}.CaseClassReader[$targetType]{
override def visitObject(length: Int, jsonableKeys: Boolean, index: Int) = new ${if (rawArgs.size <= 64) tq"_root_.upickle.implicits.CaseObjectContext[$targetType]" else tq"_root_.upickle.implicits.HugeCaseObjectContext[$targetType]"}(${rawArgs.size}){
..${
for (i <- rawArgs.indices)
yield q"private[this] var ${aggregates(i)}: ${argTypes(i)} = _"
Expand Down Expand Up @@ -318,7 +318,7 @@ object Macros {
abstract class Writing[M[_]] extends DeriveDefaults[M] {
val c: scala.reflect.macros.blackbox.Context
import c.universe._
def wrapObject(obj: c.Tree) = q"new ${c.prefix}.SingletonW($obj)"
def wrapObject(obj: c.Tree) = q"new ${c.prefix}.SingletonWriter($obj)"
def findUnapply(tpe: Type) = {
val (companion, paramTypes, argSyms, hasDefaults) = getArgSyms(tpe).fold(
errMsg => c.abort(c.enclosingPosition, errMsg),
Expand Down Expand Up @@ -357,7 +357,7 @@ object Macros {
else q"""if ($serDfltVals || v.${TermName(rawArgs(i))} != ${defaults(i)}) $snippet"""
}
q"""
new ${c.prefix}.CaseW[$targetType]{
new ${c.prefix}.CaseClassWriter[$targetType]{
def length(v: $targetType) = {
${
Range(0, rawArgs.length)
Expand Down
Loading