Skip to content

Commit 40959cb

Browse files
committed
More efficient codec derivation
1 parent a08f055 commit 40959cb

File tree

3 files changed

+226
-175
lines changed

3 files changed

+226
-175
lines changed

jsoniter-scala-macros/jvm/src/test/scala/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMakerJVMSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class JsonCodecMakerJVMSpec extends VerifyingSpec {
2828
val json = "{" + "\"n\":{" * 1000000 + "}" * 1000000 + "}"
2929
val readStackTrace = TestUtils.assertStackOverflow(readFromString[Nested](json))
3030
assert(readStackTrace.contains("d0"))
31-
assert(!readStackTrace.contains("d1"))
31+
assert(!readStackTrace.contains("d2"))
3232
assert(!readStackTrace.contains("decodeValue"))
3333
val writeStackTrace = TestUtils.assertStackOverflow(writeToString[Nested](construct()))
34-
assert(writeStackTrace.contains("e0"))
35-
assert(!writeStackTrace.contains("e1"))
34+
assert(writeStackTrace.contains("e1"))
35+
assert(!writeStackTrace.contains("e3"))
3636
assert(!writeStackTrace.contains("encodeValue"))
3737
}
3838
}

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

Lines changed: 113 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -775,57 +775,80 @@ object JsonCodecMaker {
775775
}
776776
}
777777

778+
sealed trait TypeInfo
779+
780+
case class JavaEnumValueInfo(value: Tree, name: String)
781+
782+
case class JavaEnumInfo(valueInfos: List[JavaEnumValueInfo], hasTransformed: Boolean, doEncoding: Boolean) extends TypeInfo {
783+
val doLinearSearch: Boolean = valueInfos.size <= 8 && valueInfos.foldLeft(0)(_ + _.name.length) <= 64
784+
}
785+
786+
case class FieldInfo(symbol: TermSymbol, mappedName: String, tmpName: TermName, getter: MethodSymbol,
787+
defaultValue: Option[Tree], resolvedTpe: Type, isStringified: Boolean)
788+
789+
case class ClassInfo(tpe: Type, paramLists: List[List[FieldInfo]]) extends TypeInfo {
790+
val fields: List[FieldInfo] = paramLists.flatten
791+
}
792+
793+
sealed trait TermNameKey
794+
795+
case class DecoderMethodKey(tpe: Type, isStringified: Boolean, discriminator: Tree) extends TermNameKey
796+
797+
case class EncoderMethodKey(tpe: Type, isStringified: Boolean, discriminator: Tree) extends TermNameKey
798+
799+
case class EqualsMethodKey(tpe: Type) extends TermNameKey
800+
801+
case class FieldIndexMethodKey(tpe: Type) extends TermNameKey
802+
803+
case class NullValueKey(tpe: Type) extends TermNameKey
804+
805+
case class ScalaEnumValueKey(tpe: Type) extends TermNameKey
806+
807+
case class MathContextValueKey(precision: Int) extends TermNameKey
808+
778809
val rootTpe = weakTypeOf[A].dealias
779810
val inferredKeyCodecs = mutable.Map[Type, Tree]((rootTpe, EmptyTree))
811+
val inferredValueCodecs = mutable.Map[Type, Tree]((rootTpe, EmptyTree))
812+
val typeInfos = new mutable.HashMap[Type, TypeInfo]
813+
val termNames = new mutable.HashMap[TermNameKey, TermName]
814+
val trees = new mutable.ArrayBuffer[Tree]
780815

781816
def findImplicitKeyCodec(tpe: Type): Tree = inferredKeyCodecs.getOrElseUpdate(tpe, {
782817
c.inferImplicitValue(c.typecheck(tq"com.github.plokhotnyuk.jsoniter_scala.core.JsonKeyCodec[$tpe]", c.TYPEmode).tpe)
783818
})
784819

785-
val inferredValueCodecs = mutable.Map[Type, Tree]((rootTpe, EmptyTree))
786-
787820
def findImplicitValueCodec(tpe: Type): Tree = inferredValueCodecs.getOrElseUpdate(tpe, {
788821
c.inferImplicitValue(c.typecheck(tq"com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[$tpe]", c.TYPEmode).tpe)
789822
})
790823

791-
val mathContexts = new mutable.LinkedHashMap[Int, (TermName, Tree)]
792-
793824
def withMathContextFor(precision: Int): Tree =
794825
if (precision == java.math.MathContext.DECIMAL128.getPrecision) q"_root_.java.math.MathContext.DECIMAL128"
795826
else if (precision == java.math.MathContext.DECIMAL64.getPrecision) q"_root_.java.math.MathContext.DECIMAL64"
796827
else if (precision == java.math.MathContext.DECIMAL32.getPrecision) q"_root_.java.math.MathContext.DECIMAL32"
797828
else if (precision == java.math.MathContext.UNLIMITED.getPrecision) q"_root_.java.math.MathContext.UNLIMITED"
798-
else Ident(mathContexts.getOrElseUpdate(precision, {
799-
val name = TermName(s"mc${mathContexts.size}")
800-
(name, q"private[this] val $name = new _root_.java.math.MathContext(${cfg.bigDecimalPrecision}, _root_.java.math.RoundingMode.HALF_EVEN)")
801-
})._1)
802-
803-
val scalaEnumCaches = new mutable.LinkedHashMap[Type, (TermName, Tree)]
804-
805-
def withScalaEnumCacheFor(tpe: Type): Tree = Ident(scalaEnumCaches.getOrElseUpdate(tpe, {
806-
val name = TermName(s"ec${scalaEnumCaches.size}")
807-
val keyTpe =
808-
if (cfg.useScalaEnumValueId) tq"Int"
809-
else tq"String"
810-
(name, q"private[this] val $name = new _root_.java.util.concurrent.ConcurrentHashMap[$keyTpe, $tpe]")
811-
})._1)
812-
813-
sealed trait TypeInfo
814-
815-
case class JavaEnumValueInfo(value: Tree, name: String)
816-
817-
case class JavaEnumInfo(valueInfos: List[JavaEnumValueInfo], hasTransformed: Boolean, doEncoding: Boolean) extends TypeInfo {
818-
val doLinearSearch: Boolean = valueInfos.size <= 8 && valueInfos.foldLeft(0)(_ + _.name.length) <= 64
819-
}
820-
821-
case class FieldInfo(symbol: TermSymbol, mappedName: String, tmpName: TermName, getter: MethodSymbol,
822-
defaultValue: Option[Tree], resolvedTpe: Type, isStringified: Boolean)
829+
else Ident({
830+
val termNameKey = new MathContextValueKey(precision)
831+
termNames.getOrElse(termNameKey, {
832+
val name = TermName(s"mc${termNames.size}")
833+
termNames.update(termNameKey, name)
834+
trees += q"private[this] val $name = new _root_.java.math.MathContext(${cfg.bigDecimalPrecision}, _root_.java.math.RoundingMode.HALF_EVEN)"
835+
name
836+
})
837+
})
823838

824-
case class ClassInfo(tpe: Type, paramLists: List[List[FieldInfo]]) extends TypeInfo {
825-
val fields: List[FieldInfo] = paramLists.flatten
826-
}
839+
def withScalaEnumCacheFor(tpe: Type): Tree = Ident({
840+
val termNameKey = new ScalaEnumValueKey(tpe)
841+
termNames.getOrElse(termNameKey, {
842+
val name = TermName(s"ec${termNames.size}")
843+
termNames.update(termNameKey, name)
844+
val keyTpe =
845+
if (cfg.useScalaEnumValueId) tq"Int"
846+
else tq"String"
847+
trees += q"private[this] val $name = new _root_.java.util.concurrent.ConcurrentHashMap[$keyTpe, $tpe]"
848+
name
849+
})
850+
})
827851

828-
val typeInfos = new mutable.HashMap[Type, TypeInfo]
829852

830853
def getJavaEnumInfo(tpe: Type): JavaEnumInfo = typeInfos.getOrElseUpdate(tpe, {
831854
val javaEnumValueNameMapper: String => String = n => cfg.javaEnumValueNameMapper.lift(n).getOrElse(n)
@@ -1337,44 +1360,49 @@ object JsonCodecMaker {
13371360
}
13381361
}
13391362

1340-
val nullValues = new mutable.LinkedHashMap[Type, (TermName, Tree)]
1341-
1342-
def withNullValueFor(tpe: Type)(f: => Tree): Tree = Ident(nullValues.getOrElseUpdate(tpe, {
1343-
val name = TermName(s"c${nullValues.size}")
1344-
(name, q"private[this] val $name: $tpe = $f")
1345-
})._1)
1346-
1347-
val fields = new mutable.LinkedHashMap[Type, (TermName, Tree)]
1348-
1349-
def withFieldsFor(tpe: Type)(f: => Seq[String]): Tree = Ident(fields.getOrElseUpdate(tpe, {
1350-
val name = TermName(s"f${fields.size}")
1351-
val cases = f.map {
1352-
var i = -1
1353-
n =>
1354-
i += 1
1355-
cq"$i => $n"
1356-
}
1357-
(name,
1358-
q"""private[this] def $name(i: Int): String =
1359-
(i: @_root_.scala.annotation.switch @_root_.scala.unchecked) match {
1360-
case ..$cases
1361-
}""")
1362-
})._1)
1363-
1364-
val equalsMethods = new mutable.LinkedHashMap[Type, (TermName, Tree)]
1363+
def withNullValueFor(tpe: Type)(f: => Tree): Tree = Ident({
1364+
val termNameKey = new NullValueKey(tpe)
1365+
termNames.getOrElse(termNameKey, {
1366+
val name = TermName(s"c${termNames.size}")
1367+
termNames.update(termNameKey, name)
1368+
trees += q"private[this] val $name: $tpe = $f"
1369+
name
1370+
})
1371+
})
13651372

1366-
def withEqualsFor(tpe: Type, arg1: Tree, arg2: Tree)(f: => Tree): Tree = {
1367-
val equalsMethodName = equalsMethods.getOrElseUpdate(tpe, {
1368-
val name = TermName(s"q${equalsMethods.size}")
1369-
(name, q"private[this] def $name(x1: $tpe, x2: $tpe): _root_.scala.Boolean = $f")
1370-
})._1
1373+
def withFieldsByIndexFor(termNameKey: FieldIndexMethodKey)(f: => Seq[String]): Tree =
1374+
Ident(termNames.getOrElse(termNameKey, {
1375+
val name = TermName(s"f${termNames.size}")
1376+
termNames.update(termNameKey, name)
1377+
val cases = f.map {
1378+
var i = -1
1379+
n =>
1380+
i += 1
1381+
cq"$i => $n"
1382+
}
1383+
trees +=
1384+
q"""private[this] def $name(i: Int): String =
1385+
(i: @_root_.scala.annotation.switch @_root_.scala.unchecked) match {
1386+
case ..$cases
1387+
}"""
1388+
name
1389+
}))
1390+
1391+
def withEqualsFor(termNameKey: EqualsMethodKey, arg1: Tree, arg2: Tree)(f: => Tree): Tree = {
1392+
val equalsMethodName = termNames.getOrElse(termNameKey, {
1393+
val name = TermName(s"q${termNames.size}")
1394+
termNames.update(termNameKey, name)
1395+
val mTpe = termNameKey.tpe
1396+
trees += q"private[this] def $name(x1: $mTpe, x2: $mTpe): _root_.scala.Boolean = $f"
1397+
name
1398+
})
13711399
q"$equalsMethodName($arg1, $arg2)"
13721400
}
13731401

13741402
def genArrayEquals(tpe: Type): Tree = {
13751403
val tpe1 = typeArg1(tpe)
13761404
if (tpe1 <:< typeOf[Array[?]]) {
1377-
val equals = withEqualsFor(tpe1, q"x1(i)", q"x2(i)")(genArrayEquals(tpe1))
1405+
val equals = withEqualsFor(new EqualsMethodKey(tpe1), q"x1(i)", q"x2(i)")(genArrayEquals(tpe1))
13781406
q"""(x1 eq x2) || ((x1 ne null) && (x2 ne null) && {
13791407
val l = x1.length
13801408
(x2.length == l) && {
@@ -1386,31 +1414,24 @@ object JsonCodecMaker {
13861414
} else q"_root_.java.util.Arrays.equals(x1, x2)"
13871415
}
13881416

1389-
case class MethodKey(tpe: Type, isStringified: Boolean, discriminator: Tree)
1390-
1391-
val decodeMethodNames = new mutable.HashMap[MethodKey, TermName]
1392-
val methodTrees = new mutable.ArrayBuffer[Tree]
1393-
1394-
def withDecoderFor(methodKey: MethodKey, arg: Tree)(f: => Tree): Tree = {
1395-
val decodeMethodName = decodeMethodNames.getOrElse(methodKey, {
1396-
val name = TermName(s"d${decodeMethodNames.size}")
1397-
val mtpe = methodKey.tpe
1398-
decodeMethodNames.update(methodKey, name)
1399-
methodTrees +=
1400-
q"private[this] def $name(in: _root_.com.github.plokhotnyuk.jsoniter_scala.core.JsonReader, default: $mtpe): $mtpe = $f"
1417+
def withDecoderFor(termNameKey: DecoderMethodKey, arg: Tree)(f: => Tree): Tree = {
1418+
val decodeMethodName = termNames.getOrElse(termNameKey, {
1419+
val name = TermName(s"d${termNames.size}")
1420+
termNames.update(termNameKey, name)
1421+
val mTpe = termNameKey.tpe
1422+
trees +=
1423+
q"private[this] def $name(in: _root_.com.github.plokhotnyuk.jsoniter_scala.core.JsonReader, default: $mTpe): $mTpe = $f"
14011424
name
14021425
})
14031426
q"$decodeMethodName(in, $arg)"
14041427
}
14051428

1406-
val encodeMethodNames = new mutable.HashMap[MethodKey, TermName]
1407-
1408-
def withEncoderFor(methodKey: MethodKey, arg: Tree)(f: => Tree): Tree = {
1409-
val encodeMethodName = encodeMethodNames.getOrElse(methodKey, {
1410-
val name = TermName(s"e${encodeMethodNames.size}")
1411-
encodeMethodNames.update(methodKey, name)
1412-
methodTrees +=
1413-
q"private[this] def $name(x: ${methodKey.tpe}, out: _root_.com.github.plokhotnyuk.jsoniter_scala.core.JsonWriter): _root_.scala.Unit = $f"
1429+
def withEncoderFor(termNameKey: EncoderMethodKey, arg: Tree)(f: => Tree): Tree = {
1430+
val encodeMethodName = termNames.getOrElse(termNameKey, {
1431+
val name = TermName(s"e${termNames.size}")
1432+
termNames.update(termNameKey, name)
1433+
val mTpe = termNameKey.tpe
1434+
trees += q"private[this] def $name(x: $mTpe, out: _root_.com.github.plokhotnyuk.jsoniter_scala.core.JsonWriter): _root_.scala.Unit = $f"
14141435
name
14151436
})
14161437
q"$encodeMethodName($arg, out)"
@@ -1510,7 +1531,7 @@ object JsonCodecMaker {
15101531
val checkReqVars =
15111532
if (required.isEmpty) Nil
15121533
else {
1513-
val names = withFieldsFor(tpe)(mappedNames)
1534+
val names = withFieldsByIndexFor(new FieldIndexMethodKey(tpe))(mappedNames)
15141535
val reqMasks = fields.grouped(32).toArray.map(_.foldLeft(0) {
15151536
var i = -1
15161537
(acc, fieldInfo) =>
@@ -1695,7 +1716,7 @@ object JsonCodecMaker {
16951716
q"new $tpe(${genReadVal(types1, genNullValue(types1), isStringified, EmptyTree)})"
16961717
} else {
16971718
val isColl = isCollection(tpe)
1698-
val methodKey = new MethodKey(tpe, isColl & isStringified, discriminator)
1719+
val methodKey = new DecoderMethodKey(tpe, isColl & isStringified, discriminator)
16991720
if (isColl) {
17001721
if (tpe <:< typeOf[Array[?]] || isImmutableArraySeq(tpe) || isMutableArraySeq(tpe)) withDecoderFor(methodKey, default) {
17011722
val tpe1 = typeArg1(tpe)
@@ -2074,10 +2095,11 @@ object JsonCodecMaker {
20742095
..${genWriteVal(q"v.get", typeArg1(fTpe) :: allTypes, fieldInfo.isStringified, EmptyTree)}
20752096
}"""
20762097
} else if (fTpe <:< typeOf[Array[?]]) {
2098+
val methodKey = new EqualsMethodKey(fTpe)
20772099
val cond =
20782100
if (cfg.transientEmpty) {
2079-
q"v.length != 0 && !${withEqualsFor(fTpe, q"v", d)(genArrayEquals(fTpe))}"
2080-
} else q"!${withEqualsFor(fTpe, q"v", d)(genArrayEquals(fTpe))}"
2101+
q"v.length != 0 && !${withEqualsFor(methodKey, q"v", d)(genArrayEquals(fTpe))}"
2102+
} else q"!${withEqualsFor(methodKey, q"v", d)(genArrayEquals(fTpe))}"
20812103
q"""val v = x.${fieldInfo.getter}
20822104
if ($cond) {
20832105
..${genWriteConstantKey(fieldInfo.mappedName)}
@@ -2169,7 +2191,7 @@ object JsonCodecMaker {
21692191
genWriteVal(q"$m.${valueClassValueSymbol(tpe)}", valueClassValueType(tpe) :: types, isStringified, EmptyTree)
21702192
} else {
21712193
val isColl = isCollection(tpe)
2172-
val methodKey = new MethodKey(tpe, isColl & isStringified, discriminator)
2194+
val methodKey = new EncoderMethodKey(tpe, isColl & isStringified, discriminator)
21732195
if (isColl) {
21742196
if (tpe <:< typeOf[Array[?]] || isImmutableArraySeq(tpe) || isMutableArraySeq(tpe)) withEncoderFor(methodKey, m) {
21752197
val tpe1 = typeArg1(tpe)
@@ -2363,12 +2385,8 @@ object JsonCodecMaker {
23632385
if (cfg.decodingOnly) q"_root_.scala.Predef.???"
23642386
else genWriteVal(q"x", types, cfg.isStringified, EmptyTree)
23652387
}
2366-
..$methodTrees
2367-
..${fields.values.map(_._2)}
2368-
..${equalsMethods.values.map(_._2)}
2369-
..${nullValues.values.map(_._2)}
2370-
..${mathContexts.values.map(_._2)}
2371-
..${scalaEnumCaches.values.map(_._2)}
2388+
2389+
..$trees
23722390
}
23732391
x
23742392
}"""

0 commit comments

Comments
 (0)