Skip to content

Commit 1d6289c

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

File tree

2 files changed

+51
-51
lines changed

2 files changed

+51
-51
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: 46 additions & 50 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,19 @@ 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]]
852854
private val jsonKeyCodecTpe = TypeRepr.of[JsonKeyCodec]
853855
private val jsonValueCodecTpe = TypeRepr.of[JsonValueCodec]
854856
private val newArray = Select(New(TypeIdent(defn.ArrayClass)), defn.ArrayClass.primaryConstructor)
855857
private val newArrayOfAny = TypeApply(newArray, List(Inferred(anyTpe)))
856858
private val fromIArrayMethod = Select.unique(Ref(Symbol.requiredModule("scala.runtime.TupleXXL")), "fromIArray")
857-
private val asInstanceOfMethod = anyTpe.typeSymbol.methodMember("asInstanceOf").head
859+
private val asInstanceOfMethod = anyTpe.typeSymbol.declaredMethod("asInstanceOf").head
858860
private val inferredKeyCodecs = new mutable.HashMap[TypeRepr, Option[Expr[JsonKeyCodec[?]]]]
859861
private val inferredValueCodecs = new mutable.HashMap[TypeRepr, Option[Expr[JsonValueCodec[?]]]]
860862
private val inferredOrderings = new mutable.HashMap[TypeRepr, Term]
@@ -959,17 +961,17 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
959961
else tpe.typeSymbol.companionModule
960962
}
961963

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

965967
private def isNullable(tpe: TypeRepr): Boolean = tpe match
966968
case OrType(left, right) => isNullable(right) || isNullable(left)
967969
case _ => tpe =:= TypeRepr.of[Null]
968970

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

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

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

@@ -1121,43 +1123,18 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
11211123
})
11221124

11231125
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-
11301126
def supportedTransientTypeNames: String =
11311127
if (cfg.scalaTransientSupport) s"'${Type.show[transient]}' (or '${Type.show[scala.transient]}')"
11321128
else s"'${Type.show[transient]}')"
11331129

11341130
val tpeTypeArgs = typeArgs(tpe)
11351131
val tpeClassSym = tpe.classSymbol.getOrElse(fail(s"Expected that ${tpe.show} has classSymbol"))
11361132
val primaryConstructor = tpeClassSym.primaryConstructor
1137-
var annotations = Map.empty[String, FieldAnnotations]
11381133
val caseFields = tpeClassSym.caseFields
1139-
var companionRefAndMembers: (Ref, List[Symbol]) = null
11401134
var fieldMembers: List[Symbol] = null
1135+
var companionRefAndClass: (Ref, Symbol) = null
11411136
var methodMembers: List[Symbol] = null
11421137

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-
11611138
def createFieldInfos(params: List[Symbol], typeParams: List[Symbol], fieldIndex: Boolean => Int): List[FieldInfo] = params.map {
11621139
var i = 0
11631140
symbol =>
@@ -1171,20 +1148,18 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
11711148
case _: TypeBounds =>
11721149
fail(s"Type bounds are not supported for type '${tpe.show}' with field type for $name '${fieldTpe.show}'")
11731150
case _ =>
1174-
val defaultValue = if (!cfg.requireDefaultFields && symbol.flags.is(Flags.HasDefault)) {
1175-
val dvMemberName = "$lessinit$greater$default$" + i
1176-
if (companionRefAndMembers eq null) {
1151+
val defaultValue = if (!cfg.requireDefaultFields && symbol.flags.is(Flags.HasDefault)) new Some({
1152+
if (companionRefAndClass eq null) {
11771153
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")
1154+
companionRefAndClass = (Ref(typeSymbol.companionModule), typeSymbol.companionClass)
11861155
}
1187-
} else None
1156+
val methodSymbol = companionRefAndClass._2.declaredMethod("$lessinit$greater$default$" + i).head
1157+
val dvSelectNoTypes = Select(companionRefAndClass._1, methodSymbol)
1158+
methodSymbol.paramSymss match
1159+
case Nil => dvSelectNoTypes
1160+
case List(params) if params.exists(_.isTypeParam) => TypeApply(dvSelectNoTypes, tpeTypeArgs.map(Inferred(_)))
1161+
case paramss => fail(s"Default method for $name of class ${tpe.show} have a complex parameter list: $paramss")
1162+
}) else None
11881163
val getterOrField = caseFields.find(_.name == name) match
11891164
case Some(caseField) => caseField
11901165
case _ =>
@@ -1199,10 +1174,31 @@ private class JsonCodecMakerInstance(cfg: CodecMakerConfig)(using Quotes) {
11991174
if (!getterOrField.exists || getterOrField.flags.is(Flags.PrivateLocal)) {
12001175
fail(s"Getter or field '$name' of '${tpe.show}' is private. It should be defined as 'val' or 'var' in the primary constructor.")
12011176
}
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)
1177+
var named: Option[Term] = None
1178+
var isStringified: Boolean = false
1179+
var isTransient: Boolean = false
1180+
getterOrField.annotations.foreach { annotation =>
1181+
val aTpe = annotation.tpe
1182+
if (aTpe =:= TypeRepr.of[named]) {
1183+
if (named eq None) named = new Some(annotation)
1184+
else fail(s"Duplicated '${TypeRepr.of[named].show}' defined for '$name' of '${tpe.show}'.")
1185+
} else if (aTpe =:= TypeRepr.of[stringified]) {
1186+
if (isStringified) warn(s"Duplicated '${TypeRepr.of[stringified].show}' defined for '$name' of '${tpe.show}'.")
1187+
isStringified = true
1188+
} else if (aTpe =:= TypeRepr.of[transient] || (cfg.scalaTransientSupport && aTpe =:= TypeRepr.of[scala.transient])) {
1189+
if (isTransient) warn(s"Duplicated $supportedTransientTypeNames defined for '$name' of '${tpe.show}'.")
1190+
isTransient = true
1191+
}
1192+
}
1193+
if (((named ne None) || isStringified) && isTransient) {
1194+
warn(s"Both $supportedTransientTypeNames and '${Type.show[named]}' or " +
1195+
s"$supportedTransientTypeNames and '${Type.show[stringified]}' defined for '$name' of '${tpe.show}'.")
1196+
}
1197+
val mappedName = namedValueOpt(named, tpe) match
1198+
case Some(name1) => name1
1199+
case _ => cfg.fieldNameMapper(name) match
1200+
case Some(name2) => name2
1201+
case _ => name
12061202
val index = fieldIndex(isTransient)
12071203
new FieldInfo(symbol, mappedName, getterOrField, defaultValue, fieldTpe, isTransient, isStringified, index)
12081204
}

0 commit comments

Comments
 (0)