mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Inline mask generation and constructor invocation (#908)
* Differentiate local naming from properties with constructor defaults * Remove constructor invocation and mask creation methods We're inlining all this * Add explanatory comment for why we use the default primitive value * Inline mask generation and constructor invocation to generated adapters * Remove unused argument Co-Authored-By: Jake Wharton <jakew@google.com> * Compute masks directly during code gen * Opportunistic: remove extraneous double space from control flow * Just mask and invert directly
This commit is contained in:
@@ -30,7 +30,6 @@ import com.squareup.kotlinpoet.TypeSpec
|
||||
import com.squareup.kotlinpoet.TypeVariableName
|
||||
import com.squareup.kotlinpoet.asClassName
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import com.squareup.kotlinpoet.joinToCode
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
@@ -184,13 +183,29 @@ internal class AdapterGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
val maskName = nameAllocator.newName("mask")
|
||||
val useConstructorDefaults = nonTransientProperties.any { it.hasConstructorDefault }
|
||||
if (useConstructorDefaults) {
|
||||
result.addStatement("var %L = -1", maskName)
|
||||
}
|
||||
result.addStatement("%N.beginObject()", readerParam)
|
||||
result.beginControlFlow("while (%N.hasNext())", readerParam)
|
||||
result.beginControlFlow("when (%N.selectName(%N))", readerParam, optionsProperty)
|
||||
|
||||
nonTransientProperties.forEachIndexed { index, property ->
|
||||
if (property.hasLocalIsPresentName) {
|
||||
result.beginControlFlow("%L -> ", index)
|
||||
// We track property index and mask index separately, because mask index is based on _all_
|
||||
// constructor arguments, while property index is only based on the index passed into
|
||||
// JsonReader.Options.
|
||||
var propertyIndex = 0
|
||||
var maskIndex = 0
|
||||
for (property in propertyList) {
|
||||
if (property.isTransient) {
|
||||
if (property.hasConstructorParameter) {
|
||||
maskIndex++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (property.hasLocalIsPresentName || property.hasConstructorDefault) {
|
||||
result.beginControlFlow("%L ->", propertyIndex)
|
||||
if (property.delegateKey.nullable) {
|
||||
result.addStatement("%N = %N.fromJson(%N)",
|
||||
property.localName, nameAllocator[property.delegateKey], readerParam)
|
||||
@@ -199,19 +214,28 @@ internal class AdapterGenerator(
|
||||
result.addStatement("%N = %N.fromJson(%N) ?: throw·%L",
|
||||
property.localName, nameAllocator[property.delegateKey], readerParam, exception)
|
||||
}
|
||||
result.addStatement("%N = true", property.localIsPresentName)
|
||||
if (property.hasConstructorDefault) {
|
||||
val inverted = (1 shl maskIndex).inv()
|
||||
result.addComment("\$mask = \$mask and (1 shl %L).inv()", maskIndex)
|
||||
result.addStatement("%1L = %1L and %2L", maskName, inverted)
|
||||
} else {
|
||||
// Presence tracker for a mutable property
|
||||
result.addStatement("%N = true", property.localIsPresentName)
|
||||
}
|
||||
result.endControlFlow()
|
||||
} else {
|
||||
if (property.delegateKey.nullable) {
|
||||
result.addStatement("%L -> %N = %N.fromJson(%N)",
|
||||
index, property.localName, nameAllocator[property.delegateKey], readerParam)
|
||||
propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam)
|
||||
} else {
|
||||
val exception = unexpectedNull(property.localName, readerParam)
|
||||
result.addStatement("%L -> %N = %N.fromJson(%N) ?: throw·%L",
|
||||
index, property.localName, nameAllocator[property.delegateKey], readerParam,
|
||||
propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam,
|
||||
exception)
|
||||
}
|
||||
}
|
||||
propertyIndex++
|
||||
maskIndex++
|
||||
}
|
||||
|
||||
result.beginControlFlow("-1 ->")
|
||||
@@ -236,18 +260,10 @@ internal class AdapterGenerator(
|
||||
} else {
|
||||
CodeBlock.of("return·")
|
||||
}
|
||||
val maskName = nameAllocator.newName("mask")
|
||||
val localConstructorName = nameAllocator.newName("localConstructor")
|
||||
if (useDefaultsConstructor) {
|
||||
classBuilder.addProperty(constructorProperty)
|
||||
// Dynamic default constructor call
|
||||
val booleanArrayBlock = parameterProperties.map { param ->
|
||||
when {
|
||||
param.isTransient -> CodeBlock.of("false")
|
||||
param.hasLocalIsPresentName -> CodeBlock.of(param.localIsPresentName)
|
||||
else -> CodeBlock.of("true")
|
||||
}
|
||||
}.joinToCode(", ")
|
||||
result.addStatement(
|
||||
"val %1L·= this.%2N ?: %3T.lookupDefaultsConstructor(%4T::class.java).also·{ this.%2N·= it }",
|
||||
localConstructorName,
|
||||
@@ -255,15 +271,10 @@ internal class AdapterGenerator(
|
||||
MOSHI_UTIL,
|
||||
originalTypeName
|
||||
)
|
||||
result.addStatement("val %L = %T.createDefaultValuesParametersMask(%L)",
|
||||
maskName, MOSHI_UTIL, booleanArrayBlock)
|
||||
result.addCode(
|
||||
"«%L%T.invokeDefaultConstructor(%T::class.java, %L, %L, ",
|
||||
"«%L%L.newInstance(",
|
||||
returnOrResultAssignment,
|
||||
MOSHI_UTIL,
|
||||
originalTypeName,
|
||||
localConstructorName,
|
||||
maskName
|
||||
localConstructorName
|
||||
)
|
||||
} else {
|
||||
// Standard constructor call
|
||||
@@ -292,6 +303,11 @@ internal class AdapterGenerator(
|
||||
separator = ",\n"
|
||||
}
|
||||
|
||||
if (useDefaultsConstructor) {
|
||||
// Add the mask and a null instance for the trailing default marker instance
|
||||
result.addCode(",\n%L,\nnull", maskName)
|
||||
}
|
||||
|
||||
result.addCode("\n»)\n")
|
||||
|
||||
// Assign properties not present in the constructor.
|
||||
@@ -299,7 +315,7 @@ internal class AdapterGenerator(
|
||||
if (property.hasConstructorParameter) {
|
||||
continue // Property already handled.
|
||||
}
|
||||
if (property.differentiateAbsentFromNull) {
|
||||
if (property.hasLocalIsPresentName) {
|
||||
result.addStatement("%1N.%2N = if (%3N) %4N else %1N.%2N",
|
||||
resultName, property.name, property.localIsPresentName, property.localName)
|
||||
} else {
|
||||
|
@@ -36,18 +36,18 @@ internal class PropertyGenerator(
|
||||
|
||||
val hasConstructorParameter get() = target.parameterIndex != -1
|
||||
|
||||
/** We prefer to use 'null' to mean absent, but for some properties those are distinct. */
|
||||
val differentiateAbsentFromNull get() = delegateKey.nullable && hasDefault
|
||||
|
||||
/**
|
||||
* IsPresent is required if the following conditions are met:
|
||||
* - Is not transient
|
||||
* - Has a default and one of the below
|
||||
* - Is a constructor property
|
||||
* - Is a nullable non-constructor property
|
||||
* - Has a default
|
||||
* - Is not a constructor parameter (for constructors we use a defaults mask)
|
||||
* - Is nullable (because we differentiate absent from null)
|
||||
*
|
||||
* This is used to indicate that presence should be checked first before possible assigning null
|
||||
* to an absent value
|
||||
*/
|
||||
val hasLocalIsPresentName = !isTransient && hasDefault &&
|
||||
(hasConstructorParameter || delegateKey.nullable)
|
||||
val hasLocalIsPresentName = !isTransient && hasDefault && !hasConstructorParameter && delegateKey.nullable
|
||||
val hasConstructorDefault = hasDefault && hasConstructorParameter
|
||||
|
||||
fun allocateNames(nameAllocator: NameAllocator) {
|
||||
localName = nameAllocator.newName(name)
|
||||
@@ -58,7 +58,10 @@ internal class PropertyGenerator(
|
||||
return PropertySpec.builder(localName, target.type.copy(nullable = true))
|
||||
.mutable(true)
|
||||
.apply {
|
||||
if (hasLocalIsPresentName) {
|
||||
if (hasConstructorDefault) {
|
||||
// We default to the primitive default type, as reflectively invoking the constructor
|
||||
// without this (even though it's a throwaway) will fail argument type resolution in
|
||||
// the reflective invocation.
|
||||
initializer(target.type.defaultPrimitiveValue())
|
||||
} else {
|
||||
initializer("null")
|
||||
|
Reference in New Issue
Block a user