Skip to content

Commit 1411f12

Browse files
committed
More efficient parsing and serialization of Scala 3 generic tuples
1 parent 1f1824a commit 1411f12

File tree

3 files changed

+74
-45
lines changed

3 files changed

+74
-45
lines changed

jsoniter-scala-macros/shared/src/main/scala-3/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -778,11 +778,24 @@ object JsonCodecMaker {
778778
case _ => tpe =:= TypeRepr.of[EmptyTuple]
779779
}
780780

781+
// https://github.com/arainko/ducktape/blob/8d779f0303c23fd45815d3574467ffc321a8db2b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Structure.scala#L253-L270
781782
def tupleTypeArgs(t: Type[?]): List[TypeRepr] = t match {
782783
case '[head *: tail] => TypeRepr.of[head].dealias :: tupleTypeArgs(Type.of[tail])
783784
case _ => Nil
784785
}
785786

787+
// https://github.com/arainko/ducktape/blob/8d779f0303c23fd45815d3574467ffc321a8db2b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Structure.scala#L277-L295
788+
def toTuple(typeArgs: List[TypeRepr]): TypeRepr = {
789+
val size = typeArgs.size
790+
if (size > 0 && size <= 22) defn.TupleClass(size).typeRef.appliedTo(typeArgs)
791+
else {
792+
typeArgs.foldRight(TypeRepr.of[EmptyTuple]) {
793+
val tupleCons = TypeRepr.of[*:]
794+
(curr, acc) => tupleCons.appliedTo(List(curr, acc))
795+
}
796+
}
797+
}
798+
786799
def isNamedTuple(tpe: TypeRepr): Boolean = tpe match {
787800
case AppliedType(ntTpe, _) if ntTpe.dealias.typeSymbol.fullName == "scala.NamedTuple$.NamedTuple" => true
788801
case _ => false
@@ -995,13 +1008,7 @@ object JsonCodecMaker {
9951008
// Borrowed from an amazing work of Aleksander Rainko: https://github.com/arainko/ducktape/blob/8d779f0303c23fd45815d3574467ffc321a8db2b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Structure.scala#L188-L199
9961009
val names = tupleTypeArgs(nTpe.dealias.asType).map { case ConstantType(StringConstant(name)) => name }
9971010
val typeArgs = tupleTypeArgs(tTpe.dealias.asType)
998-
val size = typeArgs.size
999-
val tupleTpe =
1000-
if (size > 0 && size <= 22) defn.TupleClass(size).typeRef.appliedTo(typeArgs)
1001-
else typeArgs.foldRight(TypeRepr.of[EmptyTuple]) {
1002-
val tupleCons = TypeRepr.of[*:]
1003-
(curr, acc) => tupleCons.appliedTo(List(curr, acc))
1004-
}
1011+
val tupleTpe = toTuple(typeArgs)
10051012
val noSymbol = Symbol.noSymbol
10061013
var i = - 1
10071014
NamedTupleInfo(tpe, tupleTpe, typeArgs, List(names.zip(typeArgs).map { case (name, fTpe) =>
@@ -2677,10 +2684,10 @@ object JsonCodecMaker {
26772684
} else if (isNamedTuple(tpe)) withDecoderFor(methodKey, default, in) { (in, default) =>
26782685
genReadNonAbstractScalaClass(getNamedTupleInfo(tpe), types, useDiscriminator, in, default)
26792686
} else if (isTuple(tpe)) withDecoderFor(methodKey, default, in) { (in, default) =>
2680-
val isGeneric = isGenericTuple(tpe)
26812687
val indexedTypes =
2682-
if (isGeneric) tupleTypeArgs(tpe.asType)
2688+
if (isGenericTuple(tpe)) tupleTypeArgs(tpe.asType)
26832689
else typeArgs(tpe)
2690+
val tTpe = toTuple(indexedTypes)
26842691
val valDefs = indexedTypes.map {
26852692
var i = 0
26862693
te =>
@@ -2700,11 +2707,11 @@ object JsonCodecMaker {
27002707
}
27012708
val readCreateBlock = Block(valDefs, '{
27022709
if ($in.isNextToken(']')) ${
2703-
if (isGeneric) {
2704-
if (indexedTypes.isEmpty) Expr(EmptyTuple)
2705-
else Expr.ofTupleFromSeq(valDefs.map(x => Ref(x.symbol).asExprOf[Any]))
2706-
} else {
2707-
Apply(TypeApply(Select.unique(New(Inferred(tpe)), "<init>"),
2710+
val size = indexedTypes.size
2711+
if (size == 0) Expr(EmptyTuple)
2712+
else if (size > 22) Expr.ofTupleFromSeq(valDefs.map(x => Ref(x.symbol).asExprOf[Any]))
2713+
else {
2714+
Apply(TypeApply(Select.unique(New(Inferred(tTpe)), "<init>"),
27082715
indexedTypes.map(x => Inferred(x))), valDefs.map(x => Ref(x.symbol))).asExpr
27092716
}
27102717
} else $in.arrayEndError()
@@ -2744,7 +2751,7 @@ object JsonCodecMaker {
27442751
.unique(Ref(Symbol.requiredModule("scala.NamedTuple")), "toTuple")
27452752
.appliedToTypeTrees(tpe.typeArgs.map { typeArg =>
27462753
typeArg.asType match
2747-
case '[t] => TypeTree.of[t]
2754+
case '[t] => TypeTree.of[t]
27482755
}),
27492756
List(x.asTerm)
27502757
)
@@ -3182,22 +3189,25 @@ object JsonCodecMaker {
31823189
} else if (isNamedTuple(tpe)) withEncoderFor(methodKey, m, out) { (out, x) =>
31833190
genWriteNonAbstractScalaClass(x.asExprOf[T], getNamedTupleInfo(tpe), types, optWriteDiscriminator, out)
31843191
} else if (isTuple(tpe)) withEncoderFor(methodKey, m, out) { (out, x) =>
3185-
val isGeneric = isGenericTuple(tpe)
31863192
val indexedTypes =
3187-
if (isGeneric) tupleTypeArgs(tpe.asType)
3193+
if (isGenericTuple(tpe)) tupleTypeArgs(tpe.asType)
31883194
else typeArgs(tpe)
3195+
val size = indexedTypes.size
3196+
val tTpe = toTuple(indexedTypes)
3197+
val xTerm =
3198+
tTpe.asType match
3199+
case '[tt] => '{ $x.asInstanceOf[tt & Tuple] }.asTerm
31893200
val writeFields = indexedTypes.map {
31903201
var i = 0
31913202
te =>
31923203
i += 1
31933204
te.asType match
31943205
case '[t] =>
31953206
val select =
3196-
if (isGeneric) {
3197-
val getter =
3198-
Select.unique(x.asTerm, "productElement").appliedTo(Literal(IntConstant(i - 1))).asExprOf[Any]
3207+
if (size > 22) {
3208+
val getter = Select.unique(xTerm, "productElement").appliedTo(Literal(IntConstant(i - 1))).asExprOf[Any]
31993209
'{ $getter.asInstanceOf[t] }.asExprOf[t]
3200-
} else Select.unique(x.asTerm, "_" + i).asExprOf[t]
3210+
} else Select.unique(xTerm, "_" + i).asExprOf[t]
32013211
genWriteVal(select, te :: types, isStringified, None, out).asTerm
32023212
}
32033213
Block('{ $out.writeArrayStart() }.asTerm :: writeFields, '{ $out.writeArrayEnd() }.asTerm).asExprOf[Unit]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.github.plokhotnyuk.jsoniter_scala.macros
2+
3+
import java.nio.charset.StandardCharsets.UTF_8
4+
import java.time._
5+
import java.util.{LinkedHashMap, Objects, UUID}
6+
import com.github.plokhotnyuk.jsoniter_scala.core._
7+
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker._
8+
import org.scalatest.exceptions.TestFailedException
9+
import scala.annotation.switch
10+
import scala.collection.mutable
11+
import scala.compiletime.{error, requireConst}
12+
import scala.jdk.CollectionConverters._
13+
import scala.language.implicitConversions
14+
import scala.util.hashing.MurmurHash3
15+
16+
class JsonCodecMakerGenericTuplesSpec extends VerifyingSpec {
17+
"JsonCodecMaker.make generate codecs which" should {
18+
"serialize and deserialize tuples with arities greater than 22" in {
19+
verifySerDeser(make[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, String)],
20+
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, "24"),
21+
"""[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,"24"]""")
22+
}
23+
"serialize and deserialize generic tuples" in {
24+
type B = Byte
25+
type I = Int
26+
type L = Long
27+
type S = Short
28+
29+
verifySerDeser(make[EmptyTuple], EmptyTuple, "[]")
30+
verifySerDeser(make[Tuple.Drop[(Long, Int, String), 3]], EmptyTuple, "[]")
31+
verifySerDeser(make[Byte *: Short *: Int *: Long *: EmptyTuple], (1: Byte, 2: Short, 3, 4L), "[1,2,3,4]")
32+
verifySerDeser(make[B *: S *: I *: L *: EmptyTuple], (1: B) *: (2: S) *: 3 *: 4L *: EmptyTuple, "[1,2,3,4]")
33+
verifySerDeser(make[Byte *: Short *: Tuple2[Int, Long]], (1: Byte, 2: Short, 3, 4L), "[1,2,3,4]")
34+
verifySerDeser(make[Tuple.Concat[(Byte, Short), (Int, Long)]], (1: Byte, 2: Short, 3, 4L), "[1,2,3,4]")
35+
verifySerDeser(make[Tuple.Append[(Byte, Short), Int]], (1: Byte, 2: Short, 3), "[1,2,3]")
36+
verifySerDeser(make[Tuple.Drop[(Long, Int, String), 1]], (1, "VVV"), """[1,"VVV"]""")
37+
verifySerDeser(make[Tuple.Take[(Int, String, Long), 2]], 1 *: "VVV" *: EmptyTuple, """[1,"VVV"]""")
38+
verifySerDeser(make[Tuple.Zip[Tuple1[Int], Tuple1[String]]], (1, "VVV") *: EmptyTuple, """[[1,"VVV"]]""")
39+
verifySerDeser(make[Tuple.Map[(Int, String), Option]], (Some(1), Some("VVV")), """[1,"VVV"]""")
40+
verifySerDeser(make[Tuple.InverseMap[(Option[Int], Option[String]), Option]], (1, "VVV"), """[1,"VVV"]""")
41+
}
42+
}
43+
}

jsoniter-scala-macros/shared/src/test/scala-3/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMakerNewTypeSpec.scala

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,6 @@ class JsonCodecMakerNewTypeSpec extends VerifyingSpec {
5757
val codecOfIArrays = make[IArrays]
5858

5959
"JsonCodecMaker.make generate codecs which" should {
60-
"serialize and deserialize tuples with arities greater than 22" in {
61-
verifySerDeser(make[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, String)],
62-
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, "24"),
63-
"""[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,"24"]""")
64-
}
65-
"serialize and deserialize generic tuples" in {
66-
type B = Byte
67-
type I = Int
68-
type L = Long
69-
type S = Short
70-
71-
verifySerDeser(make[EmptyTuple], EmptyTuple, "[]")
72-
verifySerDeser(make[Tuple.Drop[(Long, Int, String), 3]], EmptyTuple, "[]")
73-
verifySerDeser(make[Byte *: Short *: Int *: Long *: EmptyTuple], (1: Byte, 2: Short, 3, 4L), "[1,2,3,4]")
74-
verifySerDeser(make[B *: S *: I *: L *: EmptyTuple], (1: Byte, 2: Short, 3, 4L), "[1,2,3,4]")
75-
verifySerDeser(make[Byte *: Short *: Tuple2[Int, Long]], (1: Byte, 2: Short, 3, 4L), "[1,2,3,4]")
76-
verifySerDeser(make[Tuple.Concat[(Byte, Short), (Int, Long)]], (1: Byte, 2: Short, 3, 4L), "[1,2,3,4]")
77-
verifySerDeser(make[Tuple.Append[(Byte, Short), Int]], (1: Byte, 2: Short, 3), "[1,2,3]")
78-
verifySerDeser(make[Tuple.Drop[(Long, Int, String), 1]], (1, "VVV"), """[1,"VVV"]""")
79-
verifySerDeser(make[Tuple.Take[(Int, String, Long), 2]], (1, "VVV"), """[1,"VVV"]""")
80-
verifySerDeser(make[Tuple.Zip[Tuple1[Int], Tuple1[String]]], (1, "VVV") *: EmptyTuple, """[[1,"VVV"]]""")
81-
verifySerDeser(make[Tuple.Map[(Int, String), Option]], (Some(1), Some("VVV")), """[1,"VVV"]""")
82-
verifySerDeser(make[Tuple.InverseMap[(Option[Int], Option[String]), Option]], (1, "VVV"), """[1,"VVV"]""")
83-
}
8460
"serialize and deserialize immutable arrays" in {
8561
val json = """{"aa":[[1,2],[3,4]],"a":[1,2,3,4]}"""
8662
val iArrays = IArrays(IArray(IArray[Int](1, 2), IArray[Int](3, 4)), IArray[BigInt](1, 2, 3, 4))

0 commit comments

Comments
 (0)