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

Support customizing $type tag with key annotation #579

Merged
merged 7 commits into from
May 17, 2024
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
117 changes: 98 additions & 19 deletions upickle/core/src/upickle/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ trait Types{ types =>
abstract class Delegate[T](other: Visitor[Any, T])
extends Visitor.Delegate[Any, T](other) with ReadWriter[T]

def merge[T](rws: ReadWriter[_ <: T]*): TaggedReadWriter[T] = {
new TaggedReadWriter.Node(rws.asInstanceOf[Seq[TaggedReadWriter[T]]]:_*)
def merge[T](tagKey: String, rws: ReadWriter[_ <: T]*): TaggedReadWriter[T] = {
new TaggedReadWriter.Node(tagKey, rws.asInstanceOf[Seq[TaggedReadWriter[T]]]:_*)
}

def merge[T](rws: ReadWriter[_ <: T]*): TaggedReadWriter[T] = merge(Annotator.defaultTagKey, rws:_*)

implicit def join[T](implicit r0: Reader[T], w0: Writer[T]): ReadWriter[T] = (r0, w0) match{
// Make sure we preserve the tagged-ness of the Readers/Writers being
// pulled in; we need to do this because the macros that generate tagged
Expand All @@ -45,9 +47,12 @@ trait Types{ types =>

case (r1: TaggedReader[T], w1: TaggedWriter[T]) =>
new TaggedReadWriter[T] {
private[upickle] override def tagKey = r1.tagKey
override def isJsonDictKey = w0.isJsonDictKey
def findReader(s: String) = r1.findReader(s)
@deprecated("Not used, left for binary compatibility")
def findWriter(v: Any) = w1.findWriter(v)
override def findWriterWithKey(v: Any) = w1.findWriterWithKey(v)
}

case _ =>
Expand Down Expand Up @@ -104,9 +109,11 @@ trait Types{ types =>

override def visitArray(length: Int, index: Int) = super.visitArray(length, index).asInstanceOf[ArrVisitor[Any, Z]]
}
def merge[T](readers0: Reader[_ <: T]*) = {
new TaggedReader.Node(readers0.asInstanceOf[Seq[TaggedReader[T]]]:_*)
def merge[T](tagKey: String, readers0: Reader[_ <: T]*): TaggedReader.Node[T] = {
new TaggedReader.Node(tagKey, readers0.asInstanceOf[Seq[TaggedReader[T]]]:_*)
}

def merge[T](readers0: Reader[_ <: T]*): TaggedReader.Node[T] = merge(Annotator.defaultTagKey, readers0:_*)
}

/**
Expand Down Expand Up @@ -147,6 +154,8 @@ trait Types{ types =>
}

trait TaggedReader[T] extends SimpleReader[T]{
private[upickle] def tagKey: String = Annotator.defaultTagKey

def findReader(s: String): Reader[T]

override def expectedMsg = taggedExpectedMsg
Expand All @@ -160,34 +169,65 @@ trait Types{ types =>
}
}
object TaggedReader{
class Leaf[T](tag: String, r: Reader[T]) extends TaggedReader[T]{
def findReader(s: String) = if (s == tag) r else null
class Leaf[T](private[upickle] override val tagKey: String, tagValue: String, r: Reader[T]) extends TaggedReader[T]{
@deprecated("Not used, left for binary compatibility")
def this(tag: String, r: Reader[T]) = this(Annotator.defaultTagKey, tag, r)

def findReader(s: String) = if (s == tagValue) r else null
}
class Node[T](rs: TaggedReader[_ <: T]*) extends TaggedReader[T]{
class Node[T](private[upickle] override val tagKey: String, rs: TaggedReader[_ <: T]*) extends TaggedReader[T]{
@deprecated("Not used, left for binary compatibility")
def this(rs: TaggedReader[_ <: T]*) = this(Annotator.defaultTagKey, rs:_*)

def findReader(s: String) = scanChildren(rs)(_.findReader(s)).asInstanceOf[Reader[T]]
}
}

trait TaggedWriter[T] extends Writer[T]{
@deprecated("Not used, left for binary compatibility")
def findWriter(v: Any): (String, ObjectWriter[T])
def write0[R](out: Visitor[_, R], v: T): R = {

// Calling deprecated method to maintain binary compatibility
@annotation.nowarn("msg=deprecated")
def findWriterWithKey(v: Any): (String, String, ObjectWriter[T]) = {
val (tag, w) = findWriter(v)
taggedWrite(w, tag, out, v)
(Annotator.defaultTagKey, tag, w)
}

def write0[R](out: Visitor[_, R], v: T): R = {
val (tagKey, tagValue, w) = findWriterWithKey(v)
taggedWrite(w, tagKey, tagValue, out, v)

}
}
object TaggedWriter{
class Leaf[T](checker: Annotator.Checker, tag: String, r: ObjectWriter[T]) extends TaggedWriter[T]{
class Leaf[T](checker: Annotator.Checker, tagKey: String, tagValue: String, r: ObjectWriter[T]) extends TaggedWriter[T]{
@deprecated("Not used, left for binary compatibility")
def this(checker: Annotator.Checker, tag: String, r: ObjectWriter[T]) =
this(checker, Annotator.defaultTagKey, tag, r)

@deprecated("Not used, left for binary compatibility")
def findWriter(v: Any) = {
checker match{
case Annotator.Checker.Cls(c) if c.isInstance(v) => tag -> r
case Annotator.Checker.Val(v0) if v0 == v => tag -> r
case Annotator.Checker.Cls(c) if c.isInstance(v) => tagValue -> r
case Annotator.Checker.Val(v0) if v0 == v => tagValue -> r
case _ => null
}
}

override def findWriterWithKey(v: Any) = {
checker match{
case Annotator.Checker.Cls(c) if c.isInstance(v) => (tagKey, tagValue, r)
case Annotator.Checker.Val(v0) if v0 == v => (tagKey, tagValue, r)
case _ => null
}
}
}
class Node[T](rs: TaggedWriter[_ <: T]*) extends TaggedWriter[T]{
@deprecated("Not used, left for binary compatibility")
def findWriter(v: Any) = scanChildren(rs)(_.findWriter(v)).asInstanceOf[(String, ObjectWriter[T])]
override def findWriterWithKey(v: Any) =
scanChildren(rs)(_.findWriterWithKey(v)).asInstanceOf[(String, String, ObjectWriter[T])]
}
}

Expand All @@ -197,16 +237,30 @@ trait Types{ types =>

}
object TaggedReadWriter{
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
class Leaf[T](c: ClassTag[_], private[upickle] override val tagKey: String, tagValue: String, r: ObjectWriter[T] with Reader[T]) extends TaggedReadWriter[T]{
@deprecated("Not used, left for binary compatibility")
def this(c: ClassTag[_], tag: String, r: ObjectWriter[T] with Reader[T]) = this(c, Annotator.defaultTagKey, tag, r)

def findReader(s: String) = if (s == tagValue) r else null
@deprecated("Not used, left for binary compatibility")
def findWriter(v: Any) = {
if (c.runtimeClass.isInstance(v)) (tag -> r)
if (c.runtimeClass.isInstance(v)) tagValue -> r
else null
}
override def findWriterWithKey(v: Any) = {
if (c.runtimeClass.isInstance(v)) (tagKey, tagValue, r)
else null
}
}
class Node[T](rs: TaggedReadWriter[_ <: T]*) extends TaggedReadWriter[T]{
class Node[T](private[upickle] override val tagKey: String, rs: TaggedReadWriter[_ <: T]*) extends TaggedReadWriter[T]{
@deprecated("Not used, left for binary compatibility")
def this(rs: TaggedReadWriter[_ <: T]*) = this(Annotator.defaultTagKey, rs:_*)

def findReader(s: String) = scanChildren(rs)(_.findReader(s)).asInstanceOf[Reader[T]]
@deprecated("Not used, left for binary compatibility")
def findWriter(v: Any) = scanChildren(rs)(_.findWriter(v)).asInstanceOf[(String, ObjectWriter[T])]
override def findWriterWithKey(v: Any) =
scanChildren(rs)(_.findWriterWithKey(v)).asInstanceOf[(String, String, ObjectWriter[T])]
}
}

Expand All @@ -216,7 +270,13 @@ trait Types{ types =>

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
@deprecated("Not used, left for binary compatibility")
def taggedWrite[T, R](w: ObjectWriter[T], tag: String, out: Visitor[_, R], v: T): R

// Calling deprecated method to maintain binary compatibility
@annotation.nowarn("msg=deprecated")
def taggedWrite[T, R](w: ObjectWriter[T], tagKey: String, tagValue: String, out: Visitor[_, R], v: T): R =
taggedWrite(w, tagValue, out, v)

private[this] def scanChildren[T, V](xs: Seq[T])(f: T => V) = {
var x: V = null.asInstanceOf[V]
Expand Down Expand Up @@ -249,12 +309,31 @@ class CurrentlyDeriving[T]
* for `.equals` equality during writes to determine which tag to use.
*/
trait Annotator { this: Types =>
@deprecated("Not used, left for binary compatibility")
def annotate[V](rw: Reader[V], n: String): TaggedReader[V]

// Calling deprecated method to maintain binary compatibility
@annotation.nowarn("msg=deprecated")
def annotate[V](rw: Reader[V], key: String, value: String): TaggedReader[V] = annotate(rw, value)

@deprecated("Not used, left for binary compatibility")
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))

// Calling deprecated method to maintain binary compatibility
@annotation.nowarn("msg=deprecated")
def annotate[V](rw: ObjectWriter[V], key: String, value: String, checker: Annotator.Checker): TaggedWriter[V] =
annotate(rw, value, checker)

def annotate[V](rw: ObjectWriter[V], key: String, value: String)(implicit ct: ClassTag[V]): TaggedWriter[V] =
annotate(rw, key, value, Annotator.Checker.Cls(ct.runtimeClass))

@deprecated("Not used, left for binary compatibility")
final def annotate[V](rw: ObjectWriter[V], n: String)(implicit ct: ClassTag[V]): TaggedWriter[V] =
annotate(rw, Annotator.defaultTagKey, n, Annotator.Checker.Cls(ct.runtimeClass))
}
object Annotator{
def defaultTagKey = "$type"

sealed trait Checker
object Checker{
case class Cls(c: Class[_]) extends Checker
Expand Down
49 changes: 38 additions & 11 deletions upickle/implicits/src-2/upickle/implicits/internal/Macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import scala.annotation.{nowarn, StaticAnnotation}
import scala.language.experimental.macros
import compat._
import acyclic.file
import upickle.implicits.key
import upickle.core.Annotator
import upickle.implicits.{MacrosCommon, key}

import language.higherKinds
import language.existentials
Expand Down Expand Up @@ -102,8 +103,15 @@ object Macros {
annotate(tpe)(wrapObject(mod2))

}

@deprecated("Not used, left for binary compatibility")
def mergeTrait(subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree

// Calling deprecated method to maintain binary compatibility
@annotation.nowarn("msg=deprecated")
def mergeTrait(tagKey: String, subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree =
mergeTrait(subtrees, subtypes, targetType)

def derive(tpe: c.Type) = {
if (tpe.typeSymbol.asClass.isTrait || (tpe.typeSymbol.asClass.isAbstractClass && !tpe.typeSymbol.isJava)) {
val derived = deriveTrait(tpe)
Expand All @@ -125,11 +133,12 @@ object Macros {
"https://com-lihaoyi.github.io/upickle/#ManualSealedTraitPicklers"
fail(tpe, msg)
}else{
val tagKey = customKey(clsSymbol).getOrElse(Annotator.defaultTagKey)
val subTypes = fleshedOutSubtypes(tpe).toSeq.sortBy(_.typeSymbol.fullName)
// println("deriveTrait")
val subDerives = subTypes.map(subCls => q"implicitly[${typeclassFor(subCls)}]")
// println(Console.GREEN + "subDerives " + Console.RESET + subDrivess)
val merged = mergeTrait(subDerives, subTypes, tpe)
val merged = mergeTrait(tagKey, subDerives, subTypes, tpe)
merged
}
}
Expand Down Expand Up @@ -203,12 +212,20 @@ object Macros {
* representation with a class label.
*/
def annotate(tpe: c.Type)(derived: c.universe.Tree) = {
val sealedParent = tpe.baseClasses.find(_.asClass.isSealed)
sealedParent.fold(derived) { parent =>

val index = customKey(tpe.typeSymbol).getOrElse(TypeName(tpe.typeSymbol.fullName).decodedName.toString)

q"${c.prefix}.annotate($derived, $index)"
val sealedParents = tpe.baseClasses.filter(_.asClass.isSealed)

if (sealedParents.isEmpty) derived
else {
val tagKey = MacrosCommon.tagKeyFromParents(
tpe.typeSymbol.name.toString,
sealedParents,
customKey,
(_: c.Symbol).name.toString,
fail(tpe, _),
)
val tagValue = customKey(tpe.typeSymbol).getOrElse(TypeName(tpe.typeSymbol.fullName).decodedName.toString)

q"${c.prefix}.annotate($derived, $tagKey, $tagValue)"
}
}

Expand Down Expand Up @@ -329,8 +346,13 @@ object Macros {
}
"""
}
def mergeTrait(subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree = {
q"${c.prefix}.Reader.merge[$targetType](..$subtrees)"

@deprecated("Not used, left for binary compatibility")
def mergeTrait(subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree =
mergeTrait(Annotator.defaultTagKey, subtrees, subtypes, targetType)

override def mergeTrait(tagKey: String, subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree = {
q"${c.prefix}.Reader.merge[$targetType]($tagKey, ..$subtrees)"
}
}

Expand Down Expand Up @@ -401,7 +423,12 @@ object Macros {
}
"""
}
def mergeTrait(subtree: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree = {

@deprecated("Not used, left for binary compatibility")
def mergeTrait(subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree =
mergeTrait(Annotator.defaultTagKey, subtrees, subtypes, targetType)

override def mergeTrait(tagKey: String, subtree: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree = {
q"${c.prefix}.Writer.merge[$targetType](..$subtree)"
}
}
Expand Down
9 changes: 5 additions & 4 deletions upickle/implicits/src-3/upickle/implicits/Readers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ trait ReadersVersionSpecific
}

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

case m: Mirror.SumOf[T] =>
Expand All @@ -87,7 +87,7 @@ trait ReadersVersionSpecific
.toList
.asInstanceOf[List[Reader[_ <: T]]]

Reader.merge[T](readers: _*)
Reader.merge[T](macros.tagKey[T], readers: _*)
}

inline def macroRAll[T](using m: Mirror.Of[T]): Reader[T] = inline m match {
Expand All @@ -99,8 +99,9 @@ trait ReadersVersionSpecific
inline given superTypeReader[T: Mirror.ProductOf, V >: T : Reader : Mirror.SumOf]
(using NotGiven[CurrentlyDeriving[V]]): Reader[T] = {
val actual = implicitly[Reader[V]].asInstanceOf[TaggedReader[T]]
val tagKey = macros.tagKey[T]
val tagName = macros.tagName[T]
new TaggedReader.Leaf(tagName, actual.findReader(tagName))
new TaggedReader.Leaf(tagKey, tagName, actual.findReader(tagName))
}

// see comment in MacroImplicits as to why Dotty's extension methods aren't used here
Expand Down
14 changes: 12 additions & 2 deletions upickle/implicits/src-3/upickle/implicits/Writers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,19 @@ trait WritersVersionSpecific
}

inline if macros.isSingleton[T] then
annotate[T](SingletonWriter[T](null.asInstanceOf[T]), macros.tagName[T], Annotator.Checker.Val(macros.getSingleton[T]))
annotate[T](
SingletonWriter[T](null.asInstanceOf[T]),
macros.tagKey[T],
macros.tagName[T],
Annotator.Checker.Val(macros.getSingleton[T]),
)
else if macros.isMemberOfSealedHierarchy[T] then
annotate[T](writer, macros.tagName[T], Annotator.Checker.Cls(implicitly[ClassTag[T]].runtimeClass))
annotate[T](
writer,
macros.tagKey[T],
macros.tagName[T],
Annotator.Checker.Cls(implicitly[ClassTag[T]].runtimeClass),
)
else writer

case _: Mirror.SumOf[T] =>
Expand Down
Loading
Loading