diff --git a/core/src/upickle/core/Types.scala b/core/src/upickle/core/Types.scala index a2e9c34ed..7d54bdf8f 100644 --- a/core/src/upickle/core/Types.scala +++ b/core/src/upickle/core/Types.scala @@ -203,17 +203,25 @@ trait Types{ types => 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]{ + trait BaseCaseObjectContext{ def storeAggregatedValue(currentIndex: Int, v: Any): Unit - var found = 0L + def visitKey(index: Int) = _root_.upickle.core.StringVisitor var currentIndex = -1 + protected def storeValueIfNotFound(i: Int, v: Any): Unit + protected def errorMissingKeys(rawArgsLength: Int, mappedArgs: Array[String]): Unit + protected def checkErrorMissingKeys(rawArgsBitset: Long): Boolean + } + + 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 visitKey(index: Int) = _root_.upickle.core.StringVisitor + protected def storeValueIfNotFound(i: Int, v: Any) = { if ((found & (1L << i)) == 0) { found |= (1L << i) @@ -233,17 +241,16 @@ trait Types{ types => found != rawArgsBitset } } - abstract class HugeCaseObjectContext(fieldCount: Int) extends ObjVisitor[Any, V]{ - def storeAggregatedValue(currentIndex: Int, v: Any): Unit + abstract class HugeCaseObjectContext(fieldCount: Int) extends ObjVisitor[Any, V] with BaseCaseObjectContext{ var found = new Array[Long](fieldCount / 64 + 1) - var currentIndex = -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 visitKey(index: Int) = _root_.upickle.core.StringVisitor + protected def storeValueIfNotFound(i: Int, v: Any) = { if ((found(i / 64) & (1L << i)) == 0) { found(i / 64) |= (1L << i) @@ -259,7 +266,7 @@ trait Types{ types => "missing keys in dictionary: " + keys.mkString(", ") ) } - protected def checkErrorMissingKeys(rawArgsLength: Int) = { + protected def checkErrorMissingKeys(rawArgsLength: Long) = { var bits = 0 for(v <- found) bits += java.lang.Long.bitCount(v) bits != rawArgsLength @@ -277,16 +284,16 @@ trait Types{ types => ctx.visitEnd(-1) } } - protected def writeSnippet[R, V](objectAttributeKeyWriteMap: CharSequence => CharSequence, - ctx: _root_.upickle.core.ObjVisitor[_, R], - mappedArgsI: String, - w: Writer[V], - value: V) = { + 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.write(ctx.subVisitor, value), -1) + ctx.narrow.visitValue(w.asInstanceOf[Writer[Any]].write(ctx.subVisitor, value), -1) } } class SingletonR[T](t: T) extends CaseR[T]{ diff --git a/implicits/src-3/upickle/implicits/CaseClassReader.scala b/implicits/src-3/upickle/implicits/CaseClassReader.scala deleted file mode 100644 index 0ae0e0529..000000000 --- a/implicits/src-3/upickle/implicits/CaseClassReader.scala +++ /dev/null @@ -1,114 +0,0 @@ -package upickle.implicits - -import compiletime.summonInline -import deriving.Mirror -import upickle.core.{Annotator, ObjVisitor, Visitor, Abort} -import upickle.implicits.macros.EnumDescription - -trait CaseClassReaderPiece extends MacrosCommon: - this: upickle.core.Types with Readers with Annotator => - trait CaseClassReader[T] extends CaseR[T]: - def make(bldr: Map[String, Any]): T - - def visitorForKey(currentKey: String): Visitor[_, _] - - override def visitObject(length: Int, jsonableKeys: Boolean, index: Int) = new ObjVisitor[Any, T] { - private val builder = collection.mutable.Map.empty[String, Any] - var currentKey: String = null - - def subVisitor: Visitor[_, _] = visitorForKey(currentKey) - - def visitKey(index: Int): Visitor[_, _] = StringReader - - def visitKeyValue(v: Any): Unit = - currentKey = objectAttributeKeyReadMap(v.asInstanceOf[String]).toString - - def visitValue(v: Any, index: Int): Unit = - builder(currentKey) = v - - def visitEnd(index: Int): T = - make(builder.toMap) - } - override def visitString(v: CharSequence, index: Int) = make(Map()) - end CaseClassReader - - class EnumReader[T](f: String => T, description: EnumDescription) extends SimpleReader[T]: - override def expectedMsg = "expected string enumeration" - override def visitString(s: CharSequence, index: Int) = { - val str = s.toString - try { - f(str) - } catch { - case _: IllegalArgumentException => - throw new Abort(s"Value '$str' was not found in enumeration ${description.pretty}") - } - } - end EnumReader - - inline def macroR[T](using m: Mirror.Of[T]): Reader[T] = inline m match { - case m: Mirror.ProductOf[T] => - val labels: List[String] = macros.fieldLabels[T] - val visitors: List[Visitor[_, _]] = - macros.summonList[Tuple.Map[m.MirroredElemTypes, Reader]] - .asInstanceOf[List[upickle.core.Visitor[_, _]]] - val defaultParams: Map[String, AnyRef] = macros.getDefaultParams[T] - - val reader = new CaseClassReader[T] { - override def visitorForKey(key: String) = - labels.zip(visitors).toMap.get(key) match { - case None => upickle.core.NoOpVisitor - case Some(v) => v - } - - override def make(params: Map[String, Any]): T = - val values = collection.mutable.ListBuffer.empty[AnyRef] - val missingKeys = collection.mutable.ListBuffer.empty[String] - - labels.zip(visitors).map { case (fieldName, _) => - params.get(fieldName) match { - case Some(value) => values += value.asInstanceOf[AnyRef] - case None => - defaultParams.get(fieldName) match { - case Some(fallback) => values += fallback.asInstanceOf[AnyRef] - case None => missingKeys += fieldName - } - } - } - - if (!missingKeys.isEmpty) { - throw new upickle.core.Abort("missing keys in dictionary: " + missingKeys.mkString(", ")) - } - - val valuesArray = values.toArray - m.fromProduct(new Product { - def canEqual(that: Any): Boolean = true - def productArity: Int = valuesArray.length - def productElement(i: Int): Any = valuesArray(i) - }) - end make - } - - if macros.isMemberOfSealedHierarchy[T] then annotate(reader, macros.fullClassName[T]) - else reader - - case m: Mirror.SumOf[T] => - inline compiletime.erasedValue[T] match { - case _: scala.reflect.Enum => - val valueOf = macros.enumValueOf[T] - val description = macros.enumDescription[T] - new EnumReader[T](valueOf, description) - case _ => - val readers: List[Reader[_ <: T]] = macros.summonList[Tuple.Map[m.MirroredElemTypes, Reader]] - .asInstanceOf[List[Reader[_ <: T]]] - Reader.merge[T](readers:_*) - } - } - - inline given [T <: Singleton: Mirror.Of]: Reader[T] = macroR[T] - - // see comment in MacroImplicits as to why Dotty's extension methods aren't used here - implicit class ReaderExtension(r: Reader.type): - inline def derived[T](using Mirror.Of[T]): Reader[T] = macroR[T] - end ReaderExtension - -end CaseClassReaderPiece diff --git a/implicits/src-3/upickle/implicits/CaseClassWriter.scala b/implicits/src-3/upickle/implicits/CaseClassWriter.scala deleted file mode 100644 index 22777b116..000000000 --- a/implicits/src-3/upickle/implicits/CaseClassWriter.scala +++ /dev/null @@ -1,80 +0,0 @@ -package upickle.implicits - -import compiletime.{summonInline} -import deriving.Mirror -import scala.reflect.ClassTag -import upickle.core.{ Visitor, ObjVisitor, Annotator } - -trait CaseClassWriterPiece extends MacrosCommon: - this: upickle.core.Types with Writers with Annotator => - class CaseClassWriter[V]( - elemsInfo: V => List[(String, Writer[_], Any)], - defaultParams: Map[String, AnyRef]) extends CaseW[V]: - def length(v: V): Int = - var n = 0 - for - (name, _, value) <- elemsInfo(v) - defaultValue = defaultParams.get(name) - if serializeDefaults || defaultValue.isEmpty || defaultValue.get != value - do n += 1 - n - end length - - def writeToObject[R](ctx: ObjVisitor[_, R], v: V): Unit = - for - (name, writer, value) <- elemsInfo(v) - defaultValue = defaultParams.get(name) - if serializeDefaults || defaultValue.isEmpty || defaultValue.get != value - do - val keyVisitor = ctx.visitKey(-1) - ctx.visitKeyValue( - keyVisitor.visitString( - objectAttributeKeyWriteMap(name), - -1 - ) - ) - ctx.narrow.visitValue( - writer.narrow.write(ctx.subVisitor, value), -1) - end writeToObject - end CaseClassWriter - - class EnumWriter[T] extends Writer[T]: - override def write0[V](out: Visitor[_, V], v: T): V = out.visitString(v.toString, -1) - end EnumWriter - - inline def macroW[T: ClassTag](using m: Mirror.Of[T]): Writer[T] = inline m match { - case m: Mirror.ProductOf[T] => - - def elemsInfo(v: T): List[(String, Writer[_], Any)] = - val labels: List[String] = macros.fieldLabels[T] - val writers: List[Writer[_]] = - macros.summonList[Tuple.Map[m.MirroredElemTypes, Writer]] - .asInstanceOf[List[Writer[_]]] - val values: List[Any] = v.asInstanceOf[Product].productIterator.toList - for ((l, w), v) <- labels.zip(writers).zip(values) - yield (l, w, v) - end elemsInfo - val writer = CaseClassWriter[T](elemsInfo, macros.getDefaultParams[T]) - if macros.isSingleton[T] then - annotate(SingletonW[T](null.asInstanceOf[T]), macros.fullClassName[T]) - - else if macros.isMemberOfSealedHierarchy[T] then annotate(writer, macros.fullClassName[T]) - else writer - case _: Mirror.SumOf[T] => - inline compiletime.erasedValue[T] match { - case _: scala.reflect.Enum => new EnumWriter[T] - case _ => - val writers: List[Writer[_ <: T]] = macros.summonList[Tuple.Map[m.MirroredElemTypes, Writer]] - .asInstanceOf[List[Writer[_ <: T]]] - Writer.merge[T](writers:_*) - } - } - - inline given [T <: Singleton: Mirror.Of: ClassTag]: Writer[T] = macroW[T] - - // see comment in MacroImplicits as to why Dotty's extension methods aren't used here - implicit class WriterExtension(r: Writer.type): - inline def derived[T](using Mirror.Of[T], ClassTag[T]): Writer[T] = macroW[T] - end WriterExtension - -end CaseClassWriterPiece diff --git a/implicits/src-3/upickle/implicits/Readers.scala b/implicits/src-3/upickle/implicits/Readers.scala index 93a4cc164..995a9c2b5 100644 --- a/implicits/src-3/upickle/implicits/Readers.scala +++ b/implicits/src-3/upickle/implicits/Readers.scala @@ -1,9 +1,110 @@ package upickle.implicits -import upickle.core.{ Visitor, ObjVisitor, Annotator } +import compiletime.summonInline +import deriving.Mirror +import upickle.core.{Annotator, ObjVisitor, Visitor, Abort} +import upickle.implicits.macros.EnumDescription -import deriving._, compiletime._ - -trait ReadersVersionSpecific extends CaseClassReaderPiece: +trait ReadersVersionSpecific extends MacrosCommon: this: upickle.core.Types with Readers with Annotator => + + class CaseReader[T](visitors0: => Product, + fromProduct: Product => T, + keyToIndex: Map[String, Int], + defaultParams: Array[() => Any], + missingKeyCount: Long) extends CaseR[T] { + + val paramCount = keyToIndex.size + lazy val visitors = visitors0 + lazy val indexToKey = keyToIndex.map(_.swap) + + trait ObjectContext extends ObjVisitor[Any, T] with BaseCaseObjectContext{ + private val params = new Array[Any](paramCount) + + def storeAggregatedValue(currentIndex: Int, v: Any): Unit = params(currentIndex) = v + + def subVisitor: Visitor[_, _] = + if (currentIndex == -1) upickle.core.NoOpVisitor + else visitors.productElement(currentIndex).asInstanceOf[Visitor[_, _]] + + def visitKeyValue(v: Any): Unit = + val k = objectAttributeKeyReadMap(v.toString).toString + currentIndex = keyToIndex.getOrElse(k, -1) + + def visitEnd(index: Int): T = + var i = 0 + while (i < paramCount) + defaultParams(i) match + case null => + case computeDefault => storeValueIfNotFound(i, computeDefault()) + + i += 1 + + // Special-case 64 because java bit shifting ignores any RHS values above 63 + // https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.19 + if (this.checkErrorMissingKeys(missingKeyCount)) + this.errorMissingKeys(paramCount, indexToKey.toSeq.sortBy(_._1).map(_._2).toArray) + + fromProduct(new Product { + def canEqual(that: Any): Boolean = true + def productArity: Int = params.length + def productElement(i: Int): Any = params(i) + }) + } + override def visitObject(length: Int, + jsonableKeys: Boolean, + index: Int) = + if (paramCount <= 64) new CaseObjectContext(paramCount) with ObjectContext + else new HugeCaseObjectContext(paramCount) with ObjectContext + } + + class EnumReader[T](f: String => T, description: EnumDescription) extends SimpleReader[T] : + override def expectedMsg = "expected string enumeration" + + override def visitString(s: CharSequence, index: Int) = { + val str = s.toString + try { + f(str) + } catch { + case _: IllegalArgumentException => + throw new Abort(s"Value '$str' was not found in enumeration ${description.pretty}") + } + } + end EnumReader + + inline def macroR[T](using m: Mirror.Of[T]): Reader[T] = inline m match { + case m: Mirror.ProductOf[T] => + + val reader = new CaseReader( + compiletime.summonAll[Tuple.Map[m.MirroredElemTypes, Reader]], + m.fromProduct(_), + macros.fieldLabels[T].map(_._2).zipWithIndex.toMap, + macros.getDefaultParamsArray[T], + macros.checkErrorMissingKeysCount[T]() + ) + + if macros.isMemberOfSealedHierarchy[T] then annotate(reader, macros.fullClassName[T]) + else reader + + case m: Mirror.SumOf[T] => + inline compiletime.erasedValue[T] match { + case _: scala.reflect.Enum => + val valueOf = macros.enumValueOf[T] + val description = macros.enumDescription[T] + new EnumReader[T](valueOf, description) + case _ => + val readers: List[Reader[_ <: T]] = compiletime.summonAll[Tuple.Map[m.MirroredElemTypes, Reader]] + .toList + .asInstanceOf[List[Reader[_ <: T]]] + + Reader.merge[T](readers: _*) + } + } + + inline given[T <: Singleton : Mirror.Of]: Reader[T] = macroR[T] + + // see comment in MacroImplicits as to why Dotty's extension methods aren't used here + implicit class ReaderExtension(r: Reader.type): + inline def derived[T](using Mirror.Of[T]): Reader[T] = macroR[T] + end ReaderExtension end ReadersVersionSpecific diff --git a/implicits/src-3/upickle/implicits/Writers.scala b/implicits/src-3/upickle/implicits/Writers.scala index eb50091eb..99f916e12 100644 --- a/implicits/src-3/upickle/implicits/Writers.scala +++ b/implicits/src-3/upickle/implicits/Writers.scala @@ -2,6 +2,52 @@ package upickle.implicits import upickle.core.Annotator -trait WritersVersionSpecific extends CaseClassWriterPiece: - this: upickle.core.Types with Writers with Annotator => +import compiletime.{summonInline} +import deriving.Mirror +import scala.reflect.ClassTag +import upickle.core.{ Visitor, ObjVisitor, Annotator } + +trait WritersVersionSpecific extends MacrosCommon: + outerThis: upickle.core.Types with Writers with Annotator => + + class EnumWriter[T] extends Writer[T] : + override def write0[V](out: Visitor[_, V], v: T): V = out.visitString(v.toString, -1) + end EnumWriter + + inline def macroW[T: ClassTag](using m: Mirror.Of[T]): Writer[T] = inline m match { + case m: Mirror.ProductOf[T] => + def writer = new CaseW[T] { + def length(v: T) = macros.writeLength[T](outerThis, v) + + def writeToObject[R](ctx: _root_.upickle.core.ObjVisitor[_, R], v: T): Unit = + macros.writeSnippets[R, T, Tuple.Map[m.MirroredElemTypes, Writer]]( + outerThis, + this, + v, + ctx + ) + } + + if macros.isSingleton[T] then annotate(SingletonW[T](null.asInstanceOf[T]), macros.fullClassName[T]) + else if macros.isMemberOfSealedHierarchy[T] then annotate(writer, macros.fullClassName[T]) + else writer + + case _: Mirror.SumOf[T] => + inline compiletime.erasedValue[T] match { + case _: scala.reflect.Enum => new EnumWriter[T] + case _ => + val writers: List[Writer[_ <: T]] = compiletime.summonAll[Tuple.Map[m.MirroredElemTypes, Writer]] + .toList + .asInstanceOf[List[Writer[_ <: T]]] + Writer.merge[T](writers: _*) + } + } + + inline given[T <: Singleton : Mirror.Of : ClassTag]: Writer[T] = macroW[T] + + // see comment in MacroImplicits as to why Dotty's extension methods aren't used here + implicit class WriterExtension(r: Writer.type): + inline def derived[T](using Mirror.Of[T], ClassTag[T]): Writer[T] = macroW[T] + end WriterExtension + end WritersVersionSpecific \ No newline at end of file diff --git a/implicits/src-3/upickle/implicits/macros.scala b/implicits/src-3/upickle/implicits/macros.scala index 183034f1c..ebb1c1d30 100644 --- a/implicits/src-3/upickle/implicits/macros.scala +++ b/implicits/src-3/upickle/implicits/macros.scala @@ -3,12 +3,14 @@ package upickle.implicits.macros import scala.quoted.{ given, _ } import deriving._, compiletime._ -inline def getDefaultParams[T]: Map[String, AnyRef] = ${ getDefaultParamsImpl[T] } -def getDefaultParamsImpl[T](using Quotes, Type[T]): Expr[Map[String, AnyRef]] = +type IsInt[A <: Int] = A + +def getDefaultParamsImpl0[T](using Quotes, Type[T]): Map[String, Expr[AnyRef]] = import quotes.reflect._ val sym = TypeTree.of[T].symbol - if (sym.isClassDef) { + if (!sym.isClassDef) Map.empty + else val comp = if (sym.isClassDef && !sym.companionClass.isNoSymbol ) sym.companionClass else sym @@ -16,29 +18,28 @@ def getDefaultParamsImpl[T](using Quotes, Type[T]): Expr[Map[String, AnyRef]] = val hasDefaults = for p <- sym.caseFields yield p.flags.is(Flags.HasDefault) - val names = fieldLabelsImpl0[T].zip(hasDefaults).collect{case (n, true) => n} - val namesExpr: Expr[List[String]] = - Expr.ofList(names.map(Expr(_))) + + val names = fieldLabelsImpl0[T].map(_._2).zip(hasDefaults).collect{case (n, true) => n} val body = comp.tree.asInstanceOf[ClassDef].body + val idents: List[Ref] = for case deff @ DefDef(name, _, _, _) <- body if name.startsWith("$lessinit$greater$default") yield Ref(deff.symbol) - val identsExpr: Expr[List[Any]] = - Expr.ofList(idents.map(_.asExpr)) - '{ $namesExpr.zip($identsExpr.map(_.asInstanceOf[AnyRef])).toMap } - } else { - '{ Map.empty } - } -end getDefaultParamsImpl + names.zip(idents.map(_.asExpr).map(e => '{$e.asInstanceOf[AnyRef]})).toMap + +inline def getDefaultParamsArray[T] = ${getDefaultParamsArray1[T]} +def getDefaultParamsArray1[T](using Quotes, Type[T]): Expr[Array[() => Any]] = + '{${Expr.ofSeq(getDefaultParamsArray0[T])}.toArray} -inline def summonList[T <: Tuple]: List[_] = - inline erasedValue[T] match - case _: EmptyTuple => Nil - case _: (t *: ts) => summonInline[t] :: summonList[ts] -end summonList +def getDefaultParamsArray0[T](using Quotes, Type[T]): Seq[Expr[() => Any]] = + val map = getDefaultParamsImpl0[T] + fieldLabelsImpl0.map(_._2).map(map.get(_) match{ + case None => '{null} + case Some(v) => '{() => $v} + }) def extractKey[A](using Quotes)(sym: quotes.reflect.Symbol): Option[String] = import quotes.reflect._ @@ -46,10 +47,9 @@ def extractKey[A](using Quotes)(sym: quotes.reflect.Symbol): Option[String] = .annotations .find(_.tpe =:= TypeRepr.of[upickle.implicits.key]) .map{case Apply(_, Literal(StringConstant(s)) :: Nil) => s} -end extractKey -inline def fieldLabels[T] = ${fieldLabelsImpl[T]} -def fieldLabelsImpl0[T](using Quotes, Type[T]): List[String] = +inline def fieldLabels[T]: List[(String, String)] = ${fieldLabelsImpl[T]} +def fieldLabelsImpl0[T](using Quotes, Type[T]): List[(quotes.reflect.Symbol, String)] = import quotes.reflect._ val fields: List[Symbol] = TypeRepr.of[T].typeSymbol .primaryConstructor @@ -58,17 +58,79 @@ def fieldLabelsImpl0[T](using Quotes, Type[T]): List[String] = .filterNot(_.isType) fields.map{ sym => - extractKey(sym) match { - case Some(name) => name - case None => sym.name - } + extractKey(sym) match + case Some(name) => (sym, name) + case None => (sym, sym.name) } -end fieldLabelsImpl0 - -def fieldLabelsImpl[T](using Quotes, Type[T]): Expr[List[String]] = -Expr.ofList(fieldLabelsImpl0[T].map(Expr(_))) -end fieldLabelsImpl +def fieldLabelsImpl[T](using Quotes, Type[T]): Expr[List[(String, String)]] = + Expr.ofList(fieldLabelsImpl0[T].map((a, b) => Expr((a.name, b)))) + +inline def writeLength[T](inline thisOuter: upickle.core.Types with upickle.implicits.MacrosCommon, + inline v: T): Int = + ${writeLengthImpl[T]('thisOuter, 'v)} + +def writeLengthImpl[T](thisOuter: Expr[upickle.core.Types with upickle.implicits.MacrosCommon], + v: Expr[T]) + (using Quotes, Type[T]): Expr[Int] = + import quotes.reflect.* + fieldLabelsImpl0[T] + .map{(rawLabel, label) => + val defaults = getDefaultParamsImpl0[T] + val select = Select.unique(v.asTerm, rawLabel.name).asExprOf[Any] + if (!defaults.contains(rawLabel.name)) '{1} + else '{if (${thisOuter}.serializeDefaults || ${select} != ${defaults(rawLabel.name)}) 1 else 0} + } + .foldLeft('{0}) { case (prev, next) => '{$prev + $next} } + +inline def checkErrorMissingKeysCount[T](): Long = + ${checkErrorMissingKeysCountImpl[T]()} + +def checkErrorMissingKeysCountImpl[T]()(using Quotes, Type[T]): Expr[Long] = + import quotes.reflect.* + val paramCount = fieldLabelsImpl0[T].size + if (paramCount <= 64) if (paramCount == 64) Expr(-1) else Expr((1L << paramCount) - 1) + else Expr(paramCount) + +inline def writeSnippets[R, T, WS <: Tuple](inline thisOuter: upickle.core.Types with upickle.implicits.MacrosCommon, + inline self: upickle.core.Types#CaseW[T], + inline v: T, + inline ctx: _root_.upickle.core.ObjVisitor[_, R]): Unit = + ${writeSnippetsImpl[R, T, WS]('thisOuter, 'self, 'v, 'ctx)} + +def writeSnippetsImpl[R, T, WS <: Tuple](thisOuter: Expr[upickle.core.Types with upickle.implicits.MacrosCommon], + self: Expr[upickle.core.Types#CaseW[T]], + v: Expr[T], + ctx: Expr[_root_.upickle.core.ObjVisitor[_, R]]) + (using Quotes, Type[T], Type[R], Type[WS]): Expr[Unit] = + + import quotes.reflect.* + + Expr.block( + for (((rawLabel, label), i) <- fieldLabelsImpl0[T].zipWithIndex) yield { + + val tpe0 = TypeRepr.of[T].memberType(rawLabel).asType + tpe0 match + case '[tpe] => + val defaults = getDefaultParamsImpl0[T] + Literal(IntConstant(i)).tpe.asType match + case '[IsInt[index]] => + val select = Select.unique(v.asTerm, rawLabel.name).asExprOf[Any] + val snippet = '{ + ${self}.writeSnippet[R, tpe]( + ${thisOuter}.objectAttributeKeyWriteMap, + ${ctx}, + ${Expr(label)}, + summonInline[Tuple.Elem[WS, index]], + ${select}, + ) + } + if (!defaults.contains(rawLabel.name)) snippet + else '{if (${thisOuter}.serializeDefaults || ${select} != ${defaults(rawLabel.name)}) $snippet} + + }, + '{()} + ) inline def isMemberOfSealedHierarchy[T]: Boolean = ${ isMemberOfSealedHierarchyImpl[T] } def isMemberOfSealedHierarchyImpl[T](using Quotes, Type[T]): Expr[Boolean] = @@ -78,18 +140,15 @@ def isMemberOfSealedHierarchyImpl[T](using Quotes, Type[T]): Expr[Boolean] = Expr(parents.exists { p => p.flags.is(Flags.Sealed) }) - inline def fullClassName[T]: String = ${ fullClassNameImpl[T] } def fullClassNameImpl[T](using Quotes, Type[T]): Expr[String] = import quotes.reflect._ val sym = TypeTree.of[T].symbol - extractKey(sym) match { - case Some(name) => Expr(name) - case None => Expr(sym.fullName.replace("$", "")) - } -end fullClassNameImpl + extractKey(sym) match + case Some(name) => Expr(name) + case None => Expr(TypeTree.of[T].tpe.typeSymbol.fullName.filter(_ != '$')) inline def enumValueOf[T]: String => T = ${ enumValueOfImpl[T] } def enumValueOfImpl[T](using Quotes, Type[T]): Expr[String => T] = @@ -98,29 +157,25 @@ def enumValueOfImpl[T](using Quotes, Type[T]): Expr[String => T] = val sym = TypeTree.of[T].symbol val companion = sym.companionClass.tree.asInstanceOf[ClassDef] - val valueOfMethod: DefDef = companion.body.collectFirst { - case dd @ DefDef("valueOf", _, _, _) => dd - }.getOrElse { - throw Exception("Enumeration valueOf method not found") - } + val valueOfMethod: DefDef = companion + .body + .collectFirst { case dd @ DefDef("valueOf", _, _, _) => dd } + .getOrElse { throw Exception("Enumeration valueOf method not found") } val methodSymbol = valueOfMethod.symbol Ref(methodSymbol).etaExpand(methodSymbol.owner).asExpr.asInstanceOf[Expr[String => T]] -end enumValueOfImpl -case class EnumDescription(name: String, values: Seq[String]) { +case class EnumDescription(name: String, values: Seq[String]): def pretty = s"$name[values: ${values.mkString(", ")}]" -} -inline def enumDescription[T](using m: Mirror.Of[T]): EnumDescription = inline m match { - case m: Mirror.ProductOf[T] => - throw new UnsupportedOperationException("Products cannot have enum descriptions") +inline def enumDescription[T](using m: Mirror.Of[T]): EnumDescription = inline m match +case m: Mirror.ProductOf[T] => + throw new UnsupportedOperationException("Products cannot have enum descriptions") - case m: Mirror.SumOf[T] => - val name = constValue[m.MirroredLabel] - val values = constValueTuple[m.MirroredElemLabels].productIterator.toSeq.map(_.toString) - EnumDescription(name, values) -} +case m: Mirror.SumOf[T] => + val name = constValue[m.MirroredLabel] + val values = constValueTuple[m.MirroredElemLabels].productIterator.toSeq.map(_.toString) + EnumDescription(name, values) inline def isSingleton[T]: Boolean = ${ isSingletonImpl[T] } diff --git a/upickle/test/src-2/upickle/AdvancedTestsScala2Only.scala b/upickle/test/src-2/upickle/AdvancedTestsScala2Only.scala new file mode 100644 index 000000000..718e5c0f5 --- /dev/null +++ b/upickle/test/src-2/upickle/AdvancedTestsScala2Only.scala @@ -0,0 +1,37 @@ +package upickle +import utest._ +import upickle.TestUtil.rw + +object AdvancedTestsScala2Only extends TestSuite { + import All._ + val tests = Tests { + "complexTraits" - { + val reader = implicitly[upickle.default.Reader[Outers]] + val writer = implicitly[upickle.default.Writer[Outers]] + assert(reader != null) + assert(writer != null) + } + test("GenericDataTypes"){ + test("simple"){ + import Generic.A + test - rw(A(Seq("1", "2", "3")), """{"t":["1","2","3"]}""") + } + } + + test("gadt"){ + test("simple"){ + test - rw(Gadt.Exists("hello"): Gadt[_], """{"$type":"upickle.Gadt.Exists","path":"hello"}""") + test - rw(Gadt.IsDir(" "): Gadt[_], """{"$type":"upickle.Gadt.IsDir","path":" "}""") + test - rw(Gadt.ReadBytes("\""): Gadt[_], """{"$type":"upickle.Gadt.ReadBytes","path":"\""}""") + test - rw(Gadt.CopyOver(Seq(1, 2, 3), ""): Gadt[_], """{"$type":"upickle.Gadt.CopyOver","src":[1,2,3],"path":""}""") + } + test("partial"){ + test - rw(Gadt2.Exists("hello"): Gadt2[_, String], """{"$type":"upickle.Gadt2.Exists","v":"hello"}""") + test - rw(Gadt2.IsDir(123): Gadt2[_, Int], """{"$type":"upickle.Gadt2.IsDir","v":123}""") + test - rw(Gadt2.ReadBytes('h'): Gadt2[_, Char], """{"$type":"upickle.Gadt2.ReadBytes","v":"h"}""") + test - rw(Gadt2.CopyOver(Seq(1, 2, 3), ""): Gadt2[_, Unit], """{"$type":"upickle.Gadt2.CopyOver","src":[1,2,3],"v":""}""") + } + } + + } +} diff --git a/upickle/test/src-3/upickle/Derivation.scala b/upickle/test/src-3/upickle/Derivation.scala index 9f4939924..122ed486f 100644 --- a/upickle/test/src-3/upickle/Derivation.scala +++ b/upickle/test/src-3/upickle/Derivation.scala @@ -104,5 +104,9 @@ object DerivationTests extends TestSuite { val expectedDeserialized = Person("Peter", "Somewhere", 30) assert(deserialized == expectedDeserialized) } + test("recursive"){ + case class Recur(recur: Option[Recur]) derives ReadWriter + assert(write(Recur(None)) == """{"recur":[]}""") + } } } diff --git a/upickle/test/src-2/upickle/AdvancedTests.scala b/upickle/test/src/upickle/AdvancedTests.scala similarity index 90% rename from upickle/test/src-2/upickle/AdvancedTests.scala rename to upickle/test/src/upickle/AdvancedTests.scala index 0169cc24b..ae7c1eefe 100644 --- a/upickle/test/src-2/upickle/AdvancedTests.scala +++ b/upickle/test/src/upickle/AdvancedTests.scala @@ -1,4 +1,5 @@ package upickle + import utest._ import upickle.TestUtil.rw @@ -116,7 +117,7 @@ object AdvancedTests extends TestSuite { import Generic.A test - rw(A(1), """{"t":1}""") test - rw(A("1"), """{"t":"1"}""") - test - rw(A(Seq("1", "2", "3")), """{"t":["1","2","3"]}""") +// test - rw(A(Seq("1", "2", "3")), """{"t":["1","2","3"]}""") test - rw(A(A(A(A(A(A(A(1))))))), """{"t":{"t":{"t":{"t":{"t":{"t":{"t":1}}}}}}}""") } test("large"){ @@ -251,23 +252,23 @@ object AdvancedTests extends TestSuite { test("gadt"){ test("simple"){ test - rw(Gadt.Exists("hello"), """{"$type":"upickle.Gadt.Exists","path":"hello"}""") - test - rw(Gadt.Exists("hello"): Gadt[_], """{"$type":"upickle.Gadt.Exists","path":"hello"}""") +// test - rw(Gadt.Exists("hello"): Gadt[_], """{"$type":"upickle.Gadt.Exists","path":"hello"}""") test - rw(Gadt.IsDir(" "), """{"$type":"upickle.Gadt.IsDir","path":" "}""") - test - rw(Gadt.IsDir(" "): Gadt[_], """{"$type":"upickle.Gadt.IsDir","path":" "}""") +// test - rw(Gadt.IsDir(" "): Gadt[_], """{"$type":"upickle.Gadt.IsDir","path":" "}""") test - rw(Gadt.ReadBytes("\""), """{"$type":"upickle.Gadt.ReadBytes","path":"\""}""") - test - rw(Gadt.ReadBytes("\""): Gadt[_], """{"$type":"upickle.Gadt.ReadBytes","path":"\""}""") +// test - rw(Gadt.ReadBytes("\""): Gadt[_], """{"$type":"upickle.Gadt.ReadBytes","path":"\""}""") test - rw(Gadt.CopyOver(Seq(1, 2, 3), ""), """{"$type":"upickle.Gadt.CopyOver","src":[1,2,3],"path":""}""") - test - rw(Gadt.CopyOver(Seq(1, 2, 3), ""): Gadt[_], """{"$type":"upickle.Gadt.CopyOver","src":[1,2,3],"path":""}""") +// test - rw(Gadt.CopyOver(Seq(1, 2, 3), ""): Gadt[_], """{"$type":"upickle.Gadt.CopyOver","src":[1,2,3],"path":""}""") } test("partial"){ test - rw(Gadt2.Exists("hello"), """{"$type":"upickle.Gadt2.Exists","v":"hello"}""") - test - rw(Gadt2.Exists("hello"): Gadt2[_, String], """{"$type":"upickle.Gadt2.Exists","v":"hello"}""") +// test - rw(Gadt2.Exists("hello"): Gadt2[_, String], """{"$type":"upickle.Gadt2.Exists","v":"hello"}""") test - rw(Gadt2.IsDir(123), """{"$type":"upickle.Gadt2.IsDir","v":123}""") - test - rw(Gadt2.IsDir(123): Gadt2[_, Int], """{"$type":"upickle.Gadt2.IsDir","v":123}""") +// test - rw(Gadt2.IsDir(123): Gadt2[_, Int], """{"$type":"upickle.Gadt2.IsDir","v":123}""") test - rw(Gadt2.ReadBytes('h'), """{"$type":"upickle.Gadt2.ReadBytes","v":"h"}""") - test - rw(Gadt2.ReadBytes('h'): Gadt2[_, Char], """{"$type":"upickle.Gadt2.ReadBytes","v":"h"}""") +// test - rw(Gadt2.ReadBytes('h'): Gadt2[_, Char], """{"$type":"upickle.Gadt2.ReadBytes","v":"h"}""") test - rw(Gadt2.CopyOver(Seq(1, 2, 3), ""), """{"$type":"upickle.Gadt2.CopyOver","src":[1,2,3],"v":""}""") - test - rw(Gadt2.CopyOver(Seq(1, 2, 3), ""): Gadt2[_, Unit], """{"$type":"upickle.Gadt2.CopyOver","src":[1,2,3],"v":""}""") +// test - rw(Gadt2.CopyOver(Seq(1, 2, 3), ""): Gadt2[_, Unit], """{"$type":"upickle.Gadt2.CopyOver","src":[1,2,3],"v":""}""") } } test("issues"){ @@ -316,11 +317,10 @@ object AdvancedTests extends TestSuite { rw(header: Ast.Block.Sub, headerText) rw(header: Ast.Chain.Sub, headerText) } + test("scala-issue-11768"){ // Make sure this compiles - class Thing[T: upickle.default.Writer, V: upickle.default.Writer](t: Option[(V, T)]){ - implicitly[upickle.default.Writer[Option[(V, T)]]] - } + new Thing[Int, String](None) } // test("companionImplicitPickedUp"){ // assert(implicitly[upickle.default.Reader[TypedFoo]] eq TypedFoo.readWriter) @@ -334,6 +334,10 @@ object AdvancedTests extends TestSuite { // rw(TypedFoo.Quz(true): TypedFoo, """{"$type": "upickle.TypedFoo.Quz", "b": true}""") // } } + } + class Thing[T: upickle.default.Writer, V: upickle.default.Writer](t: Option[(V, T)]) { + implicitly[upickle.default.Writer[Option[(V, T)]]] } } +