Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
cc.otavia.json.JsonMacro.scala Maven / Gradle / Ivy
/*
* Copyright 2022 Yan Kun
*
* This file fork from jsoniter-scala.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.otavia.json
import scala.quoted.*
import scala.collection.mutable
import scala.reflect.ClassTag
import java.math.MathContext
import scala.language.unsafeNulls
import java.util.concurrent.ConcurrentHashMap
import cc.otavia.serde.annotation.rename
import cc.otavia.serde.annotation.stringfield
import cc.otavia.serde.annotation.ignore
import cc.otavia.buffer.Buffer
import cc.otavia.json.types.StringJsonSerde
import java.math.BigInteger
import java.util.Currency
import scala.concurrent.duration.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.Locale
import java.time.LocalTime
import cc.otavia.datatype.Money
import java.time.MonthDay
import java.time.OffsetDateTime
import java.time.OffsetTime
import java.time.Period
import java.util.UUID
import java.time.Year
import java.time.YearMonth
import java.time.ZonedDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import scala.collection.immutable
object JsonMacro {
def derivedMacro[A: Type](using quotes: Quotes): Expr[JsonSerde[A]] = {
import quotes.reflect.*
def fail(msg: String): Nothing = report.errorAndAbort(msg, Position.ofMacroExpansion)
def warn(msg: String): Unit = report.warning(msg, Position.ofMacroExpansion)
def typeArgs(tpe: TypeRepr): List[TypeRepr] = tpe match
case AppliedType(_, typeArgs) => typeArgs.map(_.dealias)
case _ => Nil
def typeArg1(tpe: TypeRepr): TypeRepr = typeArgs(tpe).head
def typeArg2(tpe: TypeRepr): TypeRepr = typeArgs(tpe).tail.head
def isTuple(tpe: TypeRepr): Boolean = tpe <:< TypeRepr.of[Tuple]
def valueClassValueSymbol(tpe: TypeRepr): Symbol = tpe.typeSymbol.fieldMembers(0)
def valueClassValueType(tpe: TypeRepr): TypeRepr = tpe.memberType(tpe.typeSymbol.fieldMembers(0)).dealias
def isNonAbstractScalaClass(tpe: TypeRepr): Boolean = tpe.classSymbol.fold(false) { sym =>
val flags = sym.flags
!flags.is(Flags.Abstract) && !flags.is(Flags.JavaDefined) && !flags.is(Flags.Trait)
}
def isSealedClass(tpe: TypeRepr): Boolean = tpe.typeSymbol.flags.is(Flags.Sealed)
def isConstType(tpe: TypeRepr): Boolean = tpe match
case ConstantType(_) => true
case _ => false
def isEnumOrModuleValue(tpe: TypeRepr): Boolean = tpe.isSingleton &&
(tpe.typeSymbol.flags.is(Flags.Module) || tpe.termSymbol.flags.is(Flags.Enum))
def adtChildren(tpe: TypeRepr): Seq[TypeRepr] = { // TODO: explore yet one variant with mirrors
def resolveParentTypeArg(
child: Symbol,
fromNudeChildTarg: TypeRepr,
parentTarg: TypeRepr,
binding: Map[String, TypeRepr]
): Map[String, TypeRepr] = {
if (fromNudeChildTarg.typeSymbol.isTypeParam) { // TODO: check for paramRef instead ?
val paramName = fromNudeChildTarg.typeSymbol.name
binding.get(paramName) match
case None => binding.updated(paramName, parentTarg)
case Some(oldBinding) =>
if (oldBinding =:= parentTarg) binding
else
fail(
s"Type parameter $paramName in class ${child.name} appeared in the constructor of " +
s"${tpe.show} two times differently, with ${oldBinding.show} and ${parentTarg.show}"
)
} else if (fromNudeChildTarg <:< parentTarg)
binding // TODO: assure parentTag is covariant, get covariance from type parameters
else {
(fromNudeChildTarg, parentTarg) match
case (AppliedType(ctycon, ctargs), AppliedType(ptycon, ptargs)) =>
ctargs.zip(ptargs).foldLeft(resolveParentTypeArg(child, ctycon, ptycon, binding)) {
(b, e) =>
resolveParentTypeArg(child, e._1, e._2, b)
}
case _ =>
fail(
s"Failed unification of type parameters of ${tpe.show} from child $child - " +
s"${fromNudeChildTarg.show} and ${parentTarg.show}"
)
}
}
def resolveParentTypeArgs(
child: Symbol,
nudeChildParentTags: List[TypeRepr],
parentTags: List[TypeRepr],
binding: Map[String, TypeRepr]
): Map[String, TypeRepr] = {
nudeChildParentTags
.zip(parentTags)
.foldLeft(binding)((s, e) => resolveParentTypeArg(child, e._1, e._2, s))
}
tpe.typeSymbol.children.map { sym =>
if (sym.isType) {
if (
sym.name == ""
) // problem - we have no other way to find this other return the name
fail(
s"Local child symbols are not supported, please consider change '${tpe.show}' or implement a " +
"custom implicitly accessible codec"
)
val nudeSubtype = TypeIdent(sym).tpe
val tpeArgsFromChild = typeArgs(nudeSubtype.baseType(tpe.typeSymbol))
nudeSubtype.memberType(sym.primaryConstructor) match
case MethodType(_, _, resTp) => resTp
case PolyType(names, bounds, resPolyTp) =>
val targs = typeArgs(tpe)
val tpBindings = resolveParentTypeArgs(sym, tpeArgsFromChild, targs, Map.empty)
val ctArgs = names.map { name =>
tpBindings.getOrElse(
name,
fail(
s"Type parameter $name of $sym can't be deduced from " +
s"type arguments of ${tpe.show}. Please provide a custom implicitly accessible codec for it."
)
)
}
val polyRes = resPolyTp match
case MethodType(_, _, resTp) => resTp
case other => other
if (ctArgs.isEmpty) polyRes
else
polyRes match
case AppliedType(base, _) => base.appliedTo(ctArgs)
case AnnotatedType(AppliedType(base, _), annot) =>
AnnotatedType(base.appliedTo(ctArgs), annot)
case other =>
fail(s"Primary constructior for ${tpe.show} is not MethodType or PolyType but $other")
} else if (sym.isTerm) Ref(sym).tpe
else
fail(
"Only Scala classes & objects are supported for ADT leaf classes. Please consider using of " +
s"them for ADT with base '${tpe.show}' or provide a custom implicitly accessible codec for the ADT base. " +
s"Failed symbol: $sym (fullName=${sym.fullName})\n"
)
}
}
def adtLeafClasses(adtBaseTpe: TypeRepr): Seq[TypeRepr] = {
def collectRecursively(tpe: TypeRepr): Seq[TypeRepr] = {
val leafTpes = adtChildren(tpe).flatMap { subTpe =>
if (isEnumOrModuleValue(subTpe) || subTpe =:= TypeRepr.of[None.type]) subTpe :: Nil
else if (isSealedClass(subTpe)) collectRecursively(subTpe)
else if (isNonAbstractScalaClass(subTpe)) subTpe :: Nil
else {
if (subTpe.typeSymbol.flags.is(Flags.Abstract) || subTpe.typeSymbol.flags.is(Flags.Trait))
fail(
"Only sealed intermediate traits or abstract classes are supported. Please consider using of them " +
s"for ADT with base '${adtBaseTpe.show}' or provide a custom implicitly accessible codec for the ADT base."
)
else
fail(
"Only Scala classes & objects are supported for ADT leaf classes. Please consider using of them " +
s"for ADT with base '${adtBaseTpe.show}' or provide a custom implicitly accessible codec for the ADT base."
)
}
}
if (isNonAbstractScalaClass(tpe)) leafTpes :+ tpe else leafTpes
}
val classes = collectRecursively(adtBaseTpe).distinct
if (classes.isEmpty)
fail(
s"Cannot find leaf classes for ADT base '${adtBaseTpe.show}'. " +
"Please add them or provide a custom implicitly accessible codec for the ADT base."
)
classes
}
def isOption(tpe: TypeRepr, types: List[TypeRepr]): Boolean = tpe <:< TypeRepr.of[Option[?]] &&
!types.headOption.exists(_ <:< TypeRepr.of[Option[?]])
def isCollection(tpe: TypeRepr): Boolean =
tpe <:< TypeRepr.of[Iterable[?]] || tpe <:< TypeRepr.of[Iterator[?]] ||
tpe <:< TypeRepr.of[Array[?]] || tpe.typeSymbol.fullName == "scala.IArray$package$.IArray"
def isJavaEnum(tpe: TypeRepr): Boolean = tpe <:< TypeRepr.of[java.lang.Enum[?]]
def scalaCollectionCompanion(tpe: TypeRepr): Term = if (tpe.typeSymbol.fullName.startsWith("scala.collection."))
Ref(tpe.typeSymbol.companionModule)
else fail(s"Unsupported type '${tpe.show}'. Please consider using a custom implicitly accessible codec for it.")
def scalaCollectionEmptyNoArgs(cTpe: TypeRepr, eTpe: TypeRepr): Term =
TypeApply(Select.unique(scalaCollectionCompanion(cTpe), "empty"), List(Inferred(eTpe)))
def scalaMapEmptyNoArgs(cTpe: TypeRepr, kTpe: TypeRepr, vTpe: TypeRepr): Term =
TypeApply(Select.unique(scalaCollectionCompanion(cTpe), "empty"), List(Inferred(kTpe), Inferred(vTpe)))
def scala2EnumerationObject(tpe: TypeRepr): Expr[Enumeration] = tpe match
case TypeRef(ct, _) => Ref(ct.termSymbol).asExprOf[Enumeration]
def findScala2EnumerationById[C <: AnyRef: Type](tpe: TypeRepr, i: Expr[Int])(using Quotes): Expr[Option[C]] =
'{ ${ scala2EnumerationObject(tpe) }.values.iterator.find(_.id == $i) }.asExprOf[Option[C]]
def findScala2EnumerationByName[C <: AnyRef: Type](tpe: TypeRepr, name: Expr[String])(using
Quotes
): Expr[Option[C]] =
'{ ${ scala2EnumerationObject(tpe) }.values.iterator.find(_.toString() == $name) }.asExprOf[Option[C]]
def genNewArray[T: Type](size: Expr[Int])(using Quotes): Expr[Array[T]] =
Apply(
TypeApply(
Select(New(TypeIdent(defn.ArrayClass)), defn.ArrayClass.primaryConstructor),
List(TypeTree.of[T])
),
List(size.asTerm)
).asExprOf[Array[T]]
val rootType = TypeRepr.of[A].dealias
val inferredOrderings = mutable.Map.empty[TypeRepr, Term]
val inferredSerde = mutable.Map.empty[TypeRepr, Option[Expr[JsonSerde[?]]]]
val classTags = mutable.Map.empty[TypeRepr, ValDef]
def summonOrdering(tpe: TypeRepr): Term = inferredOrderings.getOrElseUpdate(
tpe, {
tpe.asType match
case '[t] => Expr.summon[Ordering[t]].fold(fail(s"Can't summon Ordering[${tpe.show}]"))(_.asTerm)
}
)
def symbol(name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags): Symbol =
Symbol.newVal(Symbol.spliceOwner, name, tpe, flags, Symbol.noSymbol)
def summonClassTag(tpe: TypeRepr): Term = Ref(
classTags
.getOrElseUpdate(
tpe, {
tpe.asType match
case '[t] =>
val sym = symbol("ct" + classTags.size, TypeRepr.of[ClassTag[t]])
val ct =
Expr.summon[ClassTag[t]].fold(fail(s"Can't summon ClassTag[${tpe.show}]"))(_.asTerm)
ValDef(sym, Some(ct.changeOwner(sym)))
}
)
.symbol
)
def inferImplicitValue[T: Type](typeToSearch: TypeRepr): Option[Expr[T]] = Implicits.search(typeToSearch) match
case v: ImplicitSearchSuccess => Some(v.tree.asExprOf[T])
case _ => None
def checkRecursionInTypes(types: List[TypeRepr]): Unit = {
val tpe = types.head
val nestedTypes = types.tail
val recursiveIdx = nestedTypes.indexOf(tpe)
if (recursiveIdx >= 0) {
val recTypes = nestedTypes.take(recursiveIdx + 1).map(_.show).reverse.mkString("'", "', '", "'")
fail(s"Recursive type(s) detected: $recTypes. Please consider using a custom implicitly ")
}
}
def findImplicitSerde(tpe: TypeRepr): Option[Expr[JsonSerde[?]]] = {
if (tpe =:= rootType) None
else
inferredSerde.getOrElseUpdate(
tpe, {
inferImplicitValue[JsonSerde[?]](TypeRepr.of[JsonSerde].appliedTo(tpe))
}
)
}
val mathContexts = new mutable.LinkedHashMap[Int, ValDef]
def withMathContextFor(precision: Int): Expr[MathContext] = {
if (precision == MathContext.DECIMAL128.getPrecision) '{ (MathContext.DECIMAL128: java.math.MathContext) }
else if (precision == MathContext.DECIMAL64.getPrecision) '{
(MathContext.DECIMAL64: java.math.MathContext)
}
else if (precision == MathContext.DECIMAL32.getPrecision) '{
(MathContext.DECIMAL32: java.math.MathContext)
}
else if (precision == MathContext.UNLIMITED.getPrecision) '{
(MathContext.UNLIMITED: java.math.MathContext)
}
else
Ref(
mathContexts
.getOrElseUpdate(
precision, {
val sym = symbol("mc" + mathContexts.size, TypeRepr.of[MathContext])
val rhs = '{ new MathContext(${ Expr(34) }, java.math.RoundingMode.HALF_EVEN) }.asTerm
ValDef(sym, Some(rhs.changeOwner(sym)))
}
)
.symbol
).asExprOf[MathContext]
}
val scalaEnumCaches = new mutable.LinkedHashMap[TypeRepr, ValDef]
def withScalaEnumCacheFor[K: Type, T: Type](tpe: TypeRepr)(using Quotes): Expr[ConcurrentHashMap[K, T]] = {
Ref(
scalaEnumCaches
.getOrElseUpdate(
tpe, {
val sym = symbol("ec" + scalaEnumCaches.size, TypeRepr.of[ConcurrentHashMap[K, T]])
ValDef(sym, Some('{ new ConcurrentHashMap[K, T] }.asTerm.changeOwner(sym)))
}
)
.symbol
).asExprOf[ConcurrentHashMap[K, T]]
}
case class JavaEnumValueInfo(value: Symbol, name: String, transformed: Boolean)
val javaEnumValueInfos = new mutable.LinkedHashMap[TypeRepr, List[JavaEnumValueInfo]]
def javaEnumValues(tpe: TypeRepr): List[JavaEnumValueInfo] = javaEnumValueInfos.getOrElseUpdate(
tpe, {
val classSym = tpe.classSymbol.getOrElse(fail(s"$tpe is not a class"))
val values = classSym.children.map { sym =>
val name = sym.name
val transformed = name
JavaEnumValueInfo(sym, transformed, name != transformed)
}
val nameCollisions = values.map(_.name).distinct // TODO:
// TODO:
values
}
)
// genReadJavaEnumValue
case class FieldInfo(
symbol: Symbol,
mappedName: String,
getterOrField: Symbol,
defaultValue: Option[Term],
resolvedTpe: TypeRepr,
isTransient: Boolean,
isStringified: Boolean,
nonTransientFieldIndex: Int
)
case class ClassInfo(tpe: TypeRepr, primaryConstructor: Symbol, paramLists: List[List[FieldInfo]]) {
val fields: List[FieldInfo] = paramLists.flatten.filter(!_.isTransient)
def genNew(arg: Term): Term = genNew(List(List(arg)))
def genNew(argss: List[List[Term]]): Term = {
val constructorNoTypes = Select(New(Inferred(tpe)), primaryConstructor)
val constructor = typeArgs(tpe) match
case Nil => constructorNoTypes
case typeArgs => TypeApply(constructorNoTypes, typeArgs.map(t => Inferred(t)))
argss.tail.foldLeft(Apply(constructor, argss.head))((acc, args) => Apply(acc, args))
}
}
case class FieldAnnotations(partiallyMappedName: String, transient: Boolean, stringfield: Boolean)
val classInfos = new mutable.LinkedHashMap[TypeRepr, ClassInfo]
def getPrimaryConstructor(tpe: TypeRepr): Symbol = tpe.classSymbol match
case Some(sym) if sym.primaryConstructor.exists => sym.primaryConstructor
case _ => fail(s"Cannot find a primary constructor for '$tpe'")
def hasSupportedAnnotation(m: Symbol): Boolean =
m.annotations.exists(a =>
a.tpe =:= TypeRepr.of[rename] || a.tpe =:= TypeRepr.of[ignore] || a.tpe =:= TypeRepr.of[stringfield]
)
def supportedTransientTypeNames: String = s"'${Type.show[ignore]}')"
def namedValueOpt(namedAnnotation: Option[Term], tpe: TypeRepr): Option[String] = namedAnnotation.map {
case Apply(_, List(param)) =>
param match
case Literal(StringConstant(s)) => s
case _ =>
fail(s"Cannot evaluate a parameter of the '@rename' annotation in type '${tpe.show}': $param.")
case a => fail(s"Invalid rename annotation ${a.show}")
}
def getClassInfo(tpe: TypeRepr): ClassInfo = classInfos.getOrElseUpdate(
tpe, {
val tpeClassSym = tpe.classSymbol.getOrElse(fail(s"Expected that ${tpe.show} has classSymbol"))
val annotations = tpeClassSym.fieldMembers.collect {
case m: Symbol if hasSupportedAnnotation(m) =>
val name = m.name
val named = m.annotations.filter(_.tpe =:= TypeRepr.of[rename])
if (named.size > 1)
fail(s"Duplicated '${TypeRepr.of[rename].show}' defined for '$name' of '${tpe.show}'.")
val trans = m.annotations.filter(_.tpe =:= TypeRepr.of[ignore])
if (trans.size > 1)
warn(s"Duplicated $supportedTransientTypeNames defined for '$name' of '${tpe.show}'.")
val strings = m.annotations.filter(_.tpe =:= TypeRepr.of[stringfield])
if (strings.size > 1)
warn(s"Duplicated '${TypeRepr.of[stringfield].show}' defined for '$name' of '${tpe.show}'.")
if ((named.nonEmpty || strings.nonEmpty) && trans.nonEmpty)
warn(
s"Both $supportedTransientTypeNames and '${Type.show[rename]}' or " +
s"$supportedTransientTypeNames and '${Type.show[stringfield]}' defined for '$name' of '${tpe.show}'."
)
val partiallyMappedName = namedValueOpt(named.headOption, tpe).getOrElse(name)
(name, FieldAnnotations(partiallyMappedName, trans.nonEmpty, strings.nonEmpty))
}.toMap
val primaryConstructor = getPrimaryConstructor(tpe)
def createFieldInfos(
params: List[Symbol],
typeParams: List[Symbol],
fieldIndex: Boolean => Int
): List[FieldInfo] = {
var i = 0
params.map { symbol =>
i += 1
val name = symbol.name
val annotationOption = annotations.get(name)
val mappedName = annotationOption.fold(name)(_.partiallyMappedName)
val field = tpeClassSym.fieldMember(name)
val getterOrField = if (field.exists) {
if (field.flags.is(Flags.PrivateLocal))
fail(
s"Field '$name' in class '${tpe.show}' is private. " +
"It should be defined as 'val' or 'var' in the primary constructor."
)
field
} else {
val getters = tpeClassSym
.methodMember(name)
.filter(_.flags.is(Flags.CaseAccessor | Flags.FieldAccessor | Flags.ParamAccessor))
if (getters.isEmpty) { // Scala3 doesn't set FieldAccess flag for val parameters of constructor
val namedMembers = tpeClassSym.methodMember(name).filter(_.paramSymss == Nil)
if (namedMembers.isEmpty)
fail(
s"Field and getter not found: '$name' parameter of '${tpe.show}' " +
s"should be defined as 'val' or 'var' in the primary constructor."
)
namedMembers.head.privateWithin match
case None => namedMembers.head
case _ =>
fail(
s"Getter is private: '$name' paramter of '${tpe.show}' should be defined " +
"as 'val' or 'var' in the primary constructor."
)
} else getters.head
}
val defaultValue: Option[Term] = None
val isStringified = annotationOption.exists(_.stringfield)
val isTransient = annotationOption.exists(_.transient)
val originFieldType = tpe.memberType(symbol).dealias
val fieldType = typeArgs(tpe) match
case Nil => originFieldType
case typeArgs => originFieldType.substituteTypes(typeParams, typeArgs)
fieldType match
case TypeLambda(_, _, _) =>
fail(
s"Hight-kinded types are not supported for type ${tpe.show} " +
s"with field type for '$name' (symbol=$symbol) : ${fieldType.show}, originFieldType=" +
s"${originFieldType.show}, constructor typeParams=$typeParams, "
)
case TypeBounds(_, _) =>
fail(
s"Type bounds are not supported for type '${tpe.show}' with field " +
s"type for $name '${fieldType.show}'"
)
case _ =>
if (defaultValue.isDefined && !(defaultValue.get.tpe <:< fieldType)) {
fail(
"Polymorphic expression cannot be instantiated to expected type: default value for " +
s"field $symbol of class ${tpe.show} have type ${defaultValue.get.tpe.show} but field type " +
s"is ${fieldType.show}"
)
}
FieldInfo(
symbol,
mappedName,
getterOrField,
defaultValue,
fieldType,
isTransient,
isStringified,
fieldIndex(isTransient)
)
}
}
def isTypeParamsList(symbols: List[Symbol]): Boolean = symbols.exists(_.isTypeParam)
val fieldIndex: Boolean => Int = {
var i = -1
(isTransient: Boolean) => {
if (!isTransient) i += 1
i
}
}
val paramLists = primaryConstructor.paramSymss match
case tps :: ps :: Nil if isTypeParamsList(tps) => createFieldInfos(ps, tps, fieldIndex) :: Nil
case tps :: pss if isTypeParamsList(tps) => pss.map(ps => createFieldInfos(ps, tps, fieldIndex))
case pss => pss.map(ps => createFieldInfos(ps, Nil, fieldIndex))
ClassInfo(tpe, primaryConstructor, paramLists)
}
)
def isValueClass(tpe: TypeRepr): Boolean = !isConstType(tpe) &&
(isNonAbstractScalaClass(tpe) && !isCollection(tpe) && getClassInfo(tpe).fields.size == 1) ||
tpe <:< TypeRepr.of[AnyVal]
case class DecoderMethodKey(tpe: TypeRepr, isStringified: Boolean, useDiscriminator: Boolean)
val decodeMethodSyms = new mutable.HashMap[DecoderMethodKey, Symbol]
val decodeMethodDefs = new mutable.ArrayBuffer[DefDef]
case class EncoderMethodKey(
tpe: TypeRepr,
isStringified: Boolean,
discriminatorKeyValue: Option[(String, String)]
)
val encodeMethodSyms = new mutable.HashMap[EncoderMethodKey, Symbol]
val encodeMethodDefs = new mutable.ArrayBuffer[DefDef]
def withDecoderFor[T: Type](methodKey: DecoderMethodKey, in: Expr[Buffer])(
f: (Expr[Buffer]) => Expr[T]
)(using Quotes): Expr[T] = {
val sym =
if (decodeMethodSyms.contains(methodKey)) decodeMethodSyms(methodKey)
else {
val sym = Symbol.newMethod(
Symbol.spliceOwner,
"d" + decodeMethodSyms.size,
MethodType(List("in"))(_ => List(TypeRepr.of[Buffer]), _ => TypeRepr.of[T])
)
decodeMethodSyms.update(methodKey, sym)
decodeMethodDefs += DefDef(
sym,
params => {
val List(List(in)) = params
Some(f(in.asExprOf[Buffer]).asTerm.changeOwner(sym))
}
)
sym
}
Apply(Ref(sym), List(in.asTerm)).asExprOf[T]
}
def withEncoderFor[T: Type](methodKey: EncoderMethodKey, arg: Expr[T], out: Expr[Buffer])(
f: (Expr[T], Expr[Buffer]) => Expr[Unit]
): Expr[Unit] = {
val sym =
if (encodeMethodSyms.contains(methodKey)) encodeMethodSyms(methodKey)
else {
val sym = Symbol.newMethod(
Symbol.spliceOwner,
"e" + encodeMethodSyms.size,
MethodType(List("value", "out"))(
_ => List(TypeRepr.of[T], TypeRepr.of[Buffer]),
_ => TypeRepr.of[Unit]
)
)
encodeMethodSyms.update(methodKey, sym)
encodeMethodDefs += DefDef(
sym,
params => {
val List(List(value, out)) = params
Some(f(value.asExprOf[T], out.asExprOf[Buffer]).asTerm.changeOwner(sym))
}
)
sym
}
Apply(Ref(sym), List(arg.asTerm, out.asTerm)).asExprOf[Unit]
}
val stringable = Set(
TypeRepr.of[Boolean],
TypeRepr.of[BigDecimal],
TypeRepr.of[BigInteger],
TypeRepr.of[BigInt],
TypeRepr.of[Byte],
TypeRepr.of[Double],
TypeRepr.of[Float],
TypeRepr.of[Int],
TypeRepr.of[java.math.BigDecimal],
TypeRepr.of[Long],
TypeRepr.of[Short]
)
def isStringableType(tpe: TypeRepr): Boolean = if (tpe <:< TypeRepr.of[Option[?]])
isStringableType(typeArg1(tpe))
else stringable.exists(p => tpe =:= p)
def genWriteNonAbstractScalaClass[T: Type](x: Expr[T], tpe: TypeRepr, out: Expr[Buffer])(using
Quotes
): Expr[Unit] = {
val classInfo = getClassInfo(tpe)
val writeFields = new mutable.ArrayBuffer[Term](classInfo.fields.length)
for (fieldInfo <- classInfo.fields if !fieldInfo.isTransient) {
val fTpe = fieldInfo.resolvedTpe
fTpe.asType match
case '[ft] =>
val vterm = Select(x.asTerm, fieldInfo.getterOrField).asExprOf[ft]
val term = if (fieldInfo.isStringified && isStringableType(fTpe)) '{
JsonHelper.serializeKey(${ Expr(fieldInfo.mappedName) }, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
${ genWriteCode(fTpe, vterm, out) }
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}.asTerm
else
'{
JsonHelper.serializeKey(${ Expr(fieldInfo.mappedName) }, $out)
${ genWriteCode(fTpe, vterm, out) }
}.asTerm
writeFields.addOne(term)
writeFields.append('{ $out.writeByte(JsonConstants.TOKEN_COMMA) }.asTerm)
}
Block(
'{ JsonHelper.serializeObjectStart($out) }.asTerm :: writeFields.init.toList,
'{ JsonHelper.serializeObjectEnd($out) }.asTerm
).asExprOf[Unit]
}
def genReadCode[T: Type](tpe: TypeRepr, in: Expr[Buffer], isString: Boolean = false)(using Quotes): Expr[T] = {
val impli = findImplicitSerde(tpe)
impli match
case Some(serde) =>
if (isString && isStringableType(tpe)) '{
JsonHelper.skipBlanks($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
val ret = ${ serde.asExprOf[JsonSerde[T]] }.deserialize($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
ret
}
else '{ JsonHelper.skipBlanks($in); ${ serde.asExprOf[JsonSerde[T]] }.deserialize($in) }
case None =>
val methodKey = DecoderMethodKey(tpe, isString, false)
val decodeMethodSym = decodeMethodSyms.get(methodKey)
if (decodeMethodSym.isDefined) Apply(Ref(decodeMethodSym.get), List(in.asTerm)).asExprOf[T]
else if (tpe =:= TypeRepr.of[Byte])
if (isString) '{
JsonHelper.skipBlanks($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
val ret = JsonHelper.deserializeByte($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
ret
}.asExprOf[T]
else '{ JsonHelper.deserializeByte($in) }.asExprOf[T]
else if (tpe =:= TypeRepr.of[Boolean])
if (isString) '{
JsonHelper.skipBlanks($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
val ret = JsonHelper.deserializeBoolean($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
ret
}.asExprOf[T]
else '{ JsonHelper.deserializeBoolean($in) }.asExprOf[T]
else if (tpe =:= TypeRepr.of[Char])
'{ JsonHelper.skipBlanks($in); JsonHelper.deserializeChar($in) }.asExprOf[T]
else if (tpe =:= TypeRepr.of[Short])
if (isString) '{
JsonHelper.skipBlanks($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
val ret = JsonHelper.deserializeShort($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
ret
}.asExprOf[T]
else '{ JsonHelper.deserializeShort($in) }.asExprOf[T]
else if (tpe =:= TypeRepr.of[Int])
if (isString) '{
JsonHelper.skipBlanks($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
val ret = JsonHelper.deserializeInt($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
ret
}.asExprOf[T]
else '{ JsonHelper.deserializeInt($in) }.asExprOf[T]
else if (tpe =:= TypeRepr.of[Long])
if (isString) '{
JsonHelper.skipBlanks($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
val ret = JsonHelper.deserializeLong($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
ret
}.asExprOf[T]
else '{ JsonHelper.deserializeLong($in) }.asExprOf[T]
else if (tpe =:= TypeRepr.of[Float])
if (isString) '{
JsonHelper.skipBlanks($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
val ret = JsonHelper.deserializeFloat($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
ret
}.asExprOf[T]
else '{ JsonHelper.deserializeFloat($in) }.asExprOf[T]
else if (tpe =:= TypeRepr.of[Double])
if (isString) '{
JsonHelper.skipBlanks($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
val ret = JsonHelper.deserializeDouble($in)
$in.skipIfNextIs(JsonConstants.TOKEN_DOUBLE_QUOTE)
ret
}.asExprOf[T]
else '{ JsonHelper.deserializeDouble($in) }.asExprOf[T]
else if (tpe <:< TypeRepr.of[Option[?]]) {
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t1] =>
'{
JsonHelper.skipBlanks($in)
if ($in.skipIfNextAre(JsonConstants.TOKEN_NULL)) None
else Some(${ genReadCode[t1](tpe1, in, isString) })
}.asExprOf[T]
} else if (isTuple(tpe))
withDecoderFor(methodKey, in) { in =>
val indexedTypes = typeArgs(tpe)
var i = 0
val valDefs = indexedTypes.map { te =>
i += 1
te.asType match
case '[t] =>
val sym = symbol("_r" + i, te)
val rhs =
if (i == 1) genReadCode[t](te, in)
else
'{
assert(
JsonHelper.isNextToken($in, JsonConstants.TOKEN_COMMA),
"decode error, except ','"
)
${ genReadCode[t](te, in) }
}
ValDef(sym, Some(rhs.asTerm.changeOwner(sym)))
}
val readCreateBlock = Block(
valDefs,
'{
assert(
JsonHelper.isNextToken($in, JsonConstants.TOKEN_ARRAY_END),
"decode error, except ']'"
)
${
Apply(
TypeApply(
Select.unique(New(Inferred(tpe)), ""),
indexedTypes.map(x => Inferred(x))
),
valDefs.map(x => Ref(x.symbol))
).asExpr
}
}.asTerm
)
'{
assert(
JsonHelper.isNextToken($in, JsonConstants.TOKEN_ARRAY_START),
"decode error, except '['"
)
${ readCreateBlock.asExprOf[T] }
}
}
else if (tpe <:< TypeRepr.of[AnyVal]) {
val te = valueClassValueType(tpe)
te.asType match
case '[t] =>
val readVal = genReadCode[t](te, in, isString)
getClassInfo(tpe).genNew(readVal.asTerm).asExprOf[T]
} else '{ ??? }
}
def genWriteCode[T: Type](tpe: TypeRepr, value: Expr[T], out: Expr[Buffer], isString: Boolean = false)(using
Quotes
): Expr[Unit] = {
val impli = findImplicitSerde(tpe)
impli match
case Some(serde) =>
if (isString && isStringableType(tpe)) '{
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
${ serde.asExprOf[JsonSerde[T]] }.serialize($value, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}
else '{ ${ serde.asExprOf[JsonSerde[T]] }.serialize($value, $out) }
case None =>
val methodKey = EncoderMethodKey(tpe, isString, None)
val encodeMethodSym = encodeMethodSyms.get(methodKey)
if (encodeMethodSym.isDefined)
Apply(Ref(encodeMethodSym.get), List(value.asTerm, out.asTerm)).asExprOf[Unit]
else if (tpe =:= TypeRepr.of[Byte])
if (isString) '{
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
JsonHelper.serializeByte(${ value.asExprOf[Byte] }, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}
else '{ JsonHelper.serializeByte(${ value.asExprOf[Byte] }, $out) }
else if (tpe =:= TypeRepr.of[Boolean])
if (isString) '{
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
JsonHelper.serializeBoolean(${ value.asExprOf[Boolean] }, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}
else '{ JsonHelper.serializeBoolean(${ value.asExprOf[Boolean] }, $out) }
else if (tpe =:= TypeRepr.of[Char]) '{ JsonHelper.serializeChar(${ value.asExprOf[Char] }, $out) }
else if (tpe =:= TypeRepr.of[Short])
if (isString) '{
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
JsonHelper.serializeShort(${ value.asExprOf[Short] }, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}
else '{ JsonHelper.serializeShort(${ value.asExprOf[Short] }, $out) }
else if (tpe =:= TypeRepr.of[Int])
if (isString) '{
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
JsonHelper.serializeInt(${ value.asExprOf[Int] }, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}
else '{ JsonHelper.serializeInt(${ value.asExprOf[Int] }, $out) }
else if (tpe =:= TypeRepr.of[Long])
if (isString) '{
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
JsonHelper.serializeLong(${ value.asExprOf[Long] }, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}
else '{ JsonHelper.serializeLong(${ value.asExprOf[Long] }, $out) }
else if (tpe =:= TypeRepr.of[Float])
if (isString) '{
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
JsonHelper.serializeFloat(${ value.asExprOf[Float] }, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}
else '{ JsonHelper.serializeFloat(${ value.asExprOf[Float] }, $out) }
else if (tpe =:= TypeRepr.of[Double])
if (isString) '{
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
JsonHelper.serializeDouble(${ value.asExprOf[Double] }, $out)
$out.writeByte(JsonConstants.TOKEN_DOUBLE_QUOTE)
}
else '{ JsonHelper.serializeDouble(${ value.asExprOf[Double] }, $out) }
else if (tpe =:= TypeRepr.of[String])
'{ JsonHelper.serializeString(${ value.asExprOf[String] }, $out) }
else if (tpe <:< TypeRepr.of[AnyVal]) {
val te = valueClassValueType(tpe)
genWriteCode(te, Select(value.asTerm, valueClassValueSymbol(tpe)).asExpr, out, isString)
} else if (tpe <:< TypeRepr.of[Option[?]]) {
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t1] =>
'{
${ value.asExprOf[Option[t1]] } match
case None => JsonHelper.serializeNull($out)
case Some(v) => ${ genWriteCode(tpe1, 'v, out, isString) }
}
} else if (tpe <:< TypeRepr.of[Array[?]]) withEncoderFor(methodKey, value, out) { (x, out) =>
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t1] =>
val tx = x.asExprOf[Array[t1]]
'{
JsonHelper.serializeArrayStart($out)
var i = 0
while (i < $tx.length) {
${ genWriteCode(tpe1, '{ $tx.apply(i) }, out) }
i += 1
if (i != $tx.length) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
JsonHelper.serializeArrayEnd($out)
}
}
else if (tpe <:< TypeRepr.of[mutable.ArraySeq[?]]) withEncoderFor(methodKey, value, out) {
(x, out) =>
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t] =>
val tx = x.asExprOf[mutable.ArraySeq[t]]
'{
JsonHelper.serializeArrayStart($out)
val xs = $tx.array.asInstanceOf[Array[t]]
var i = 0
while (i < xs.length) {
${ genWriteCode[t](tpe1, '{ xs(i) }, out) }
i += 1
if (i != xs.length) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
JsonHelper.serializeArrayEnd($out)
}
}
else if (tpe <:< TypeRepr.of[immutable.ArraySeq[?]]) withEncoderFor(methodKey, value, out) {
(x, out) =>
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t] =>
val tx = x.asExprOf[immutable.ArraySeq[t]]
'{
JsonHelper.serializeArrayStart($out)
val xs = $tx.unsafeArray.asInstanceOf[Array[t]]
var i = 0
while (i < xs.length) {
${ genWriteCode[t](tpe1, '{ xs(i) }, out) }
i += 1
if (i != xs.length) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
JsonHelper.serializeArrayEnd($out)
}
}
else if (tpe.typeSymbol.fullName == "scala.IArray$package$.IArray")
withEncoderFor(methodKey, value, out) { (x, out) =>
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t] =>
val tx = x.asExprOf[IArray[t]]
'{
JsonHelper.serializeArrayStart($out)
var i = 0
while (i < $tx.length) {
${ genWriteCode[t](tpe1, '{ $tx(i) }, out) }
i += 1
if (i != $tx.length) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
JsonHelper.serializeArrayEnd($out)
}
}
else if (tpe <:< TypeRepr.of[List[?]]) withEncoderFor(methodKey, value, out) { (x, out) =>
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t1] =>
val tx = x.asExprOf[List[t1]]
'{
JsonHelper.serializeObjectStart($out)
var l = $tx
while (l ne Nil) {
${ genWriteCode[t1](tpe1, '{ l.head }, out) }
l = l.tail
if (l ne Nil) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
JsonHelper.serializeObjectEnd($out)
}
}
else if (tpe <:< TypeRepr.of[IndexedSeq[?]]) withEncoderFor(methodKey, value, out) { (x, out) =>
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t1] =>
val tx = x.asExprOf[IndexedSeq[t1]]
'{
JsonHelper.serializeObjectStart($out)
val l = $tx.length
if (l <= 32) {
var i = 0
while (i < l) {
${ genWriteCode[t1](tpe1, '{ $tx(i) }, out) }
i += 1
if (i != l) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
} else {
var i = 0
while (i < $tx.length) {
${ genWriteCode(tpe1, '{ $tx.apply(i) }, out) }
i += 1
if (i != $tx.length) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
}
JsonHelper.serializeObjectEnd($out)
}
}
else if (tpe <:< TypeRepr.of[scala.collection.Seq[?]]) withEncoderFor(methodKey, value, out) {
(x, out) =>
val tpe1 = typeArg1(tpe)
tpe1.asType match
case '[t1] =>
val tx = x.asExprOf[scala.collection.Seq[t1]]
'{
JsonHelper.serializeArrayStart($out)
var i = 0
while (i < $tx.length) {
${ genWriteCode(tpe1, '{ $tx.apply(i) }, out) }
i += 1
if (i != $tx.length) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
JsonHelper.serializeArrayEnd($out)
}
}
else if (tpe <:< TypeRepr.of[scala.collection.Map[?, ?]]) withEncoderFor(methodKey, value, out) {
(x, out) =>
val tpe1 = typeArg1(tpe)
val tpe2 = typeArg2(tpe)
(tpe1.asType, tpe2.asType) match
case ('[tk], '[tv]) =>
val tx = x.asExprOf[collection.Map[tk, tv]]
'{
JsonHelper.serializeObjectStart($out)
var i = 0
val length = $tx.size
$tx.foreach { (k, v) =>
JsonHelper.serializeKey(k.toString(), $out)
${ genWriteCode(tpe2, 'v, out) }
i += 1
if (i != length) $out.writeByte(JsonConstants.TOKEN_COMMA)
}
JsonHelper.serializeObjectEnd($out)
}
}
else if (isTuple(tpe)) withEncoderFor(methodKey, value, out) { (x, out) =>
val tpes = typeArgs(tpe)
val length = tpes.length
val writeFields = tpes.zipWithIndex.map { (te, i) =>
val idx = i + 1
te.asType match
case '[t] =>
if (idx != length)
'{
${ genWriteCode(te, Select.unique(x.asTerm, "_" + idx).asExprOf[t], out) }
$out.writeByte(JsonConstants.TOKEN_COMMA)
}.asTerm
else genWriteCode(te, Select.unique(x.asTerm, "_" + idx).asExprOf[t], out).asTerm
}
Block(
'{ JsonHelper.serializeArrayStart($out) }.asTerm :: writeFields,
'{ JsonHelper.serializeArrayEnd($out) }.asTerm
).asExprOf[Unit]
}
else if (isNonAbstractScalaClass(tpe)) withEncoderFor(methodKey, value, out) { (x, out) =>
genWriteNonAbstractScalaClass(x, tpe, out)
}
else '{ ??? }
}
val clz = '{
new JsonSerde[A] {
override def serialize(value: A, out: Buffer): Unit = ${ genWriteCode(rootType, 'value, 'out) }
override def deserialize(in: Buffer): A = ${ genReadCode(rootType, 'in) }
}
}.asTerm
val needDefs =
classTags.values ++
mathContexts.values ++
scalaEnumCaches.values ++
decodeMethodDefs ++
encodeMethodDefs
val serde = Block(needDefs.toList, clz).asExprOf[JsonSerde[A]]
report.info(
s"Generated JSON serde for type '${rootType.show}':\n${serde.show}",
Position.ofMacroExpansion
)
serde
}
}