Skip to content

Commit 4ff690c

Browse files
committed
More efficient codec derivation with Scala 3
1 parent b83c805 commit 4ff690c

File tree

2 files changed

+56
-53
lines changed

2 files changed

+56
-53
lines changed

build.sbt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import com.typesafe.tools.mima.core.*
12
import org.scalajs.linker.interface.{CheckedBehavior, ESVersion}
23
import sbt.*
34
import scala.scalanative.build.*
@@ -136,7 +137,10 @@ lazy val publishSettings = Seq(
136137
else Set()
137138
},
138139
mimaReportSignatureProblems := true,
139-
mimaBinaryIssueFilters := Seq()
140+
mimaBinaryIssueFilters := Seq(
141+
ProblemFilters.exclude[MissingClassProblem]("com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerInstance$FieldAnnotations"),
142+
ProblemFilters.exclude[MissingClassProblem]("com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerInstance$FieldAnnotations$")
143+
)
140144
)
141145

142146
lazy val `jsoniter-scala` = project.in(file("."))

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

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -809,8 +809,6 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
809809
}
810810
}
811811

812-
private case class FieldAnnotations(partiallyMappedName: Option[String], transient: Boolean, stringified: Boolean)
813-
814812
private case class DecoderMethodKey(tpe: TypeRepr, isStringified: Boolean, useDiscriminator: Boolean)
815813

816814
private case class EncoderMethodKey(tpe: TypeRepr, isStringified: Boolean, discriminatorKeyValue: Option[(String, String)])
@@ -846,15 +844,22 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
846844
private val unitTpe = defn.UnitClass.typeRef
847845
private val anyTpe = defn.AnyClass.typeRef
848846
private val arrayOfAnyTpe = defn.ArrayClass.typeRef.appliedTo(anyTpe)
849-
private val iArrayOfAnyRefTpe = TypeRepr.of[IArray[AnyRef]]
850847
private val stringTpe = TypeRepr.of[String]
848+
private val optionTpe = TypeRepr.of[Option[?]]
851849
private val tupleTpe = TypeRepr.of[Tuple]
850+
private val iterableTpe = TypeRepr.of[Iterable[?]]
851+
private val iteratorTpe = TypeRepr.of[Iterator[?]]
852+
private val arrayTpe = TypeRepr.of[Array[?]]
853+
private val iArrayOfAnyRefTpe = TypeRepr.of[IArray[AnyRef]]
854+
private val namedTpe = TypeRepr.of[named]
855+
private val stringifiedTpe = TypeRepr.of[stringified]
856+
private val transientTpe = TypeRepr.of[transient]
852857
private val jsonKeyCodecTpe = TypeRepr.of[JsonKeyCodec]
853858
private val jsonValueCodecTpe = TypeRepr.of[JsonValueCodec]
854859
private val newArray = Select(New(TypeIdent(defn.ArrayClass)), defn.ArrayClass.primaryConstructor)
855860
private val newArrayOfAny = TypeApply(newArray, List(Inferred(anyTpe)))
856861
private val fromIArrayMethod = Select.unique(Ref(Symbol.requiredModule("scala.runtime.TupleXXL")), "fromIArray")
857-
private val asInstanceOfMethod = anyTpe.typeSymbol.methodMember("asInstanceOf").head
862+
private val asInstanceOfMethod = anyTpe.typeSymbol.declaredMethod("asInstanceOf").head
858863
private val inferredKeyCodecs = new mutable.HashMap[TypeRepr, Option[Expr[JsonKeyCodec[?]]]]
859864
private val inferredValueCodecs = new mutable.HashMap[TypeRepr, Option[Expr[JsonValueCodec[?]]]]
860865
private val inferredOrderings = new mutable.HashMap[TypeRepr, Term]
@@ -959,17 +964,17 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
959964
else tpe.typeSymbol.companionModule
960965
}
961966

962-
private def isOption(tpe: TypeRepr, types: List[TypeRepr]): Boolean = tpe <:< TypeRepr.of[Option[?]] &&
963-
(cfg.skipNestedOptionValues || !types.headOption.exists(_ <:< TypeRepr.of[Option[?]]))
967+
private def isOption(tpe: TypeRepr, types: List[TypeRepr]): Boolean = tpe <:< optionTpe &&
968+
(cfg.skipNestedOptionValues || !types.headOption.exists(_ <:< optionTpe))
964969

965970
private def isNullable(tpe: TypeRepr): Boolean = tpe match
966971
case OrType(left, right) => isNullable(right) || isNullable(left)
967972
case _ => tpe =:= TypeRepr.of[Null]
968973

969974
private def isIArray(tpe: TypeRepr): Boolean = tpe.typeSymbol.fullName == "scala.IArray$package$.IArray"
970975

971-
private def isCollection(tpe: TypeRepr): Boolean = tpe <:< TypeRepr.of[Iterable[?]] ||
972-
tpe <:< TypeRepr.of[Iterator[?]] || tpe <:< TypeRepr.of[Array[?]] || isIArray(tpe)
976+
private def isCollection(tpe: TypeRepr): Boolean =
977+
tpe <:< iterableTpe || tpe <:< iteratorTpe || tpe <:< arrayTpe || isIArray(tpe)
973978

974979
private def isJavaEnum(tpe: TypeRepr): Boolean = tpe <:< TypeRepr.of[java.lang.Enum[?]]
975980

@@ -1121,43 +1126,18 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
11211126
})
11221127

11231128
private def getClassInfo(tpe: TypeRepr): ClassInfo = classInfos.getOrElseUpdate(tpe, {
1124-
def hasSupportedAnnotation(m: Symbol): Boolean = m.annotations.exists { a =>
1125-
val tpe = a.tpe
1126-
tpe =:= TypeRepr.of[named] || tpe =:= TypeRepr.of[transient] || tpe =:= TypeRepr.of[stringified] ||
1127-
(cfg.scalaTransientSupport && tpe =:= TypeRepr.of[scala.transient])
1128-
}
1129-
11301129
def supportedTransientTypeNames: String =
1131-
if (cfg.scalaTransientSupport) s"'${Type.show[transient]}' (or '${Type.show[scala.transient]}')"
1132-
else s"'${Type.show[transient]}')"
1130+
if (cfg.scalaTransientSupport) s"'${transientTpe.show}' (or '${TypeRepr.of[scala.transient].show}')"
1131+
else s"'${transientTpe.show}')"
11331132

11341133
val tpeTypeArgs = typeArgs(tpe)
11351134
val tpeClassSym = tpe.classSymbol.getOrElse(fail(s"Expected that ${tpe.show} has classSymbol"))
11361135
val primaryConstructor = tpeClassSym.primaryConstructor
1137-
var annotations = Map.empty[String, FieldAnnotations]
11381136
val caseFields = tpeClassSym.caseFields
1139-
var companionRefAndMembers: (Ref, List[Symbol]) = null
11401137
var fieldMembers: List[Symbol] = null
1138+
var companionRefAndClass: (Ref, Symbol) = null
11411139
var methodMembers: List[Symbol] = null
11421140

1143-
tpeClassSym.fieldMembers.foreach {
1144-
case m: Symbol if hasSupportedAnnotation(m) =>
1145-
val name = m.name
1146-
val named = m.annotations.count(_.tpe =:= TypeRepr.of[named])
1147-
if (named > 1) fail(s"Duplicated '${TypeRepr.of[named].show}' defined for '$name' of '${tpe.show}'.")
1148-
val trans = m.annotations.count(a => a.tpe =:= TypeRepr.of[transient] ||
1149-
(cfg.scalaTransientSupport && a.tpe =:= TypeRepr.of[scala.transient]))
1150-
if (trans > 1) warn(s"Duplicated $supportedTransientTypeNames defined for '$name' of '${tpe.show}'.")
1151-
val strings = m.annotations.count(_.tpe =:= TypeRepr.of[stringified])
1152-
if (strings > 1) warn(s"Duplicated '${TypeRepr.of[stringified].show}' defined for '$name' of '${tpe.show}'.")
1153-
if ((named > 0 || strings > 0) && trans > 0)
1154-
warn(s"Both $supportedTransientTypeNames and '${Type.show[named]}' or " +
1155-
s"$supportedTransientTypeNames and '${Type.show[stringified]}' defined for '$name' of '${tpe.show}'.")
1156-
val partiallyMappedName = namedValueOpt(m.annotations.find(_.tpe =:= TypeRepr.of[named]), tpe)
1157-
annotations = annotations.updated(name, new FieldAnnotations(partiallyMappedName, trans > 0, strings > 0))
1158-
case _ =>
1159-
}
1160-
11611141
def createFieldInfos(params: List[Symbol], typeParams: List[Symbol], fieldIndex: Boolean => Int): List[FieldInfo] = params.map {
11621142
var i = 0
11631143
symbol =>
@@ -1171,20 +1151,18 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
11711151
case _: TypeBounds =>
11721152
fail(s"Type bounds are not supported for type '${tpe.show}' with field type for $name '${fieldTpe.show}'")
11731153
case _ =>
1174-
val defaultValue = if (!cfg.requireDefaultFields && symbol.flags.is(Flags.HasDefault)) {
1175-
val dvMemberName = "$lessinit$greater$default$" + i
1176-
if (companionRefAndMembers eq null) {
1154+
val defaultValue = if (!cfg.requireDefaultFields && symbol.flags.is(Flags.HasDefault)) new Some({
1155+
if (companionRefAndClass eq null) {
11771156
val typeSymbol = tpe.typeSymbol
1178-
companionRefAndMembers = (Ref(typeSymbol.companionModule), typeSymbol.companionClass.methodMembers)
1179-
}
1180-
companionRefAndMembers._2.collectFirst { case methodSymbol if methodSymbol.name == dvMemberName =>
1181-
val dvSelectNoTypes = Select(companionRefAndMembers._1, methodSymbol)
1182-
methodSymbol.paramSymss match
1183-
case Nil => dvSelectNoTypes
1184-
case List(params) if params.exists(_.isTypeParam) => TypeApply(dvSelectNoTypes, tpeTypeArgs.map(Inferred(_)))
1185-
case paramss => fail(s"Default method for $name of class ${tpe.show} have a complex parameter list: $paramss")
1157+
companionRefAndClass = (Ref(typeSymbol.companionModule), typeSymbol.companionClass)
11861158
}
1187-
} else None
1159+
val methodSymbol = companionRefAndClass._2.declaredMethod("$lessinit$greater$default$" + i).head
1160+
val dvSelectNoTypes = Select(companionRefAndClass._1, methodSymbol)
1161+
methodSymbol.paramSymss match
1162+
case Nil => dvSelectNoTypes
1163+
case List(params) if params.exists(_.isTypeParam) => TypeApply(dvSelectNoTypes, tpeTypeArgs.map(Inferred(_)))
1164+
case paramss => fail(s"Default method for $name of class ${tpe.show} have a complex parameter list: $paramss")
1165+
}) else None
11881166
val getterOrField = caseFields.find(_.name == name) match
11891167
case Some(caseField) => caseField
11901168
case _ =>
@@ -1199,10 +1177,31 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
11991177
if (!getterOrField.exists || getterOrField.flags.is(Flags.PrivateLocal)) {
12001178
fail(s"Getter or field '$name' of '${tpe.show}' is private. It should be defined as 'val' or 'var' in the primary constructor.")
12011179
}
1202-
val annotationOption = annotations.get(name)
1203-
val mappedName = annotationOption.flatMap(_.partiallyMappedName).getOrElse(cfg.fieldNameMapper(name).getOrElse(name))
1204-
val isStringified = annotationOption.exists(_.stringified)
1205-
val isTransient = annotationOption.exists(_.transient)
1180+
var named: Option[Term] = None
1181+
var isStringified: Boolean = false
1182+
var isTransient: Boolean = false
1183+
getterOrField.annotations.foreach { annotation =>
1184+
val aTpe = annotation.tpe
1185+
if (aTpe =:= namedTpe) {
1186+
if (named eq None) named = new Some(annotation)
1187+
else fail(s"Duplicated '${namedTpe.show}' defined for '$name' of '${tpe.show}'.")
1188+
} else if (aTpe =:= stringifiedTpe) {
1189+
if (isStringified) warn(s"Duplicated '${stringifiedTpe.show}' defined for '$name' of '${tpe.show}'.")
1190+
isStringified = true
1191+
} else if (aTpe =:= transientTpe || (cfg.scalaTransientSupport && aTpe =:= TypeRepr.of[scala.transient])) {
1192+
if (isTransient) warn(s"Duplicated $supportedTransientTypeNames defined for '$name' of '${tpe.show}'.")
1193+
isTransient = true
1194+
}
1195+
}
1196+
if (((named ne None) || isStringified) && isTransient) {
1197+
warn(s"Both $supportedTransientTypeNames and '${namedTpe.show}' or " +
1198+
s"$supportedTransientTypeNames and '${stringifiedTpe.show}' defined for '$name' of '${tpe.show}'.")
1199+
}
1200+
val mappedName = namedValueOpt(named, tpe) match
1201+
case Some(name1) => name1
1202+
case _ => cfg.fieldNameMapper(name) match
1203+
case Some(name2) => name2
1204+
case _ => name
12061205
val index = fieldIndex(isTransient)
12071206
new FieldInfo(symbol, mappedName, getterOrField, defaultValue, fieldTpe, isTransient, isStringified, index)
12081207
}

0 commit comments

Comments
 (0)