Skip to content

Commit

Permalink
Invoke constructor directly if all params with default are set (#1326)
Browse files Browse the repository at this point in the history
Resolves #1302
  • Loading branch information
ZacSweers authored Mar 30, 2021
1 parent b33a94d commit 7372baa
Showing 1 changed file with 61 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ internal class AdapterGenerator(
val maskNames = Array(maskCount) { index ->
nameAllocator.newName("mask$index")
}
val maskAllSetValues = Array(maskCount) { -1 }
val useDefaultsConstructor = components.filterIsInstance<ParameterComponent>()
.any { it.parameter.hasDefault }
if (useDefaultsConstructor) {
Expand Down Expand Up @@ -440,6 +441,9 @@ internal class AdapterGenerator(
}
if (property.hasConstructorDefault) {
val inverted = (1 shl maskIndex).inv()
if (input is ParameterComponent && input.parameter.hasDefault) {
maskAllSetValues[maskNameIndex] = maskAllSetValues[maskNameIndex] and inverted
}
result.addComment("\$mask = \$mask and (1 shl %L).inv()", maskIndex)
result.addStatement(
"%1L = %1L and 0x%2L.toInt()",
Expand Down Expand Up @@ -495,12 +499,50 @@ internal class AdapterGenerator(
val hasNonConstructorProperties = nonTransientProperties.any { !it.hasConstructorParameter }
val returnOrResultAssignment = if (hasNonConstructorProperties) {
// Save the result var for reuse
CodeBlock.of("val %N = ", resultName)
result.addStatement("val %N: %T", resultName, originalTypeName)
CodeBlock.of("%N = ", resultName)
} else {
CodeBlock.of("return·")
}

// Used to indicate we're in an if-block that's assigning our result value and
// needs to be closed with endControlFlow
var closeNextControlFlowInAssignment = false

if (useDefaultsConstructor) {
// Happy path - all parameters with defaults are set
val allMasksAreSetBlock = maskNames.withIndex()
.map { (index, maskName) ->
CodeBlock.of("$maskName·== 0x${Integer.toHexString(maskAllSetValues[index])}.toInt()")
}
.joinToCode("·&& ")
result.beginControlFlow("if (%L)", allMasksAreSetBlock)
result.addComment("All parameters with defaults are set, invoke the constructor directly")
result.addCode("«%L·%T(", returnOrResultAssignment, originalTypeName)
var localSeparator = "\n"
val paramsToSet = components.filterIsInstance<ParameterProperty>()
.filterNot { it.property.isTransient }

// Set all non-transient property parameters
for (input in paramsToSet) {
result.addCode(localSeparator)
val property = input.property
result.addCode("%N = %N", property.name, property.localName)
if (property.isRequired) {
result.addMissingPropertyCheck(property, readerParam)
} else if (!input.type.isNullable) {
// Unfortunately incurs an intrinsic null-check even though we know it's set, but
// maybe in the future we can use contracts to omit them.
result.addCode("·as·%T", input.type)
}
localSeparator = ",\n"
}
result.addCode("\n»)\n")
result.nextControlFlow("else")
closeNextControlFlowInAssignment = true

classBuilder.addProperty(constructorProperty)
result.addComment("Reflectively invoke the synthetic defaults constructor")
// Dynamic default constructor call
val nonNullConstructorType = constructorProperty.type.copy(nullable = false)
val args = constructorPropertyTypes
Expand Down Expand Up @@ -569,15 +611,7 @@ internal class AdapterGenerator(
if (input is PropertyComponent) {
val property = input.property
if (!property.isTransient && property.isRequired) {
val missingPropertyBlock =
CodeBlock.of(
"%T.missingProperty(%S, %S, %N)",
MOSHI_UTIL,
property.localName,
property.jsonName,
readerParam
)
result.addCode(" ?: throw·%L", missingPropertyBlock)
result.addMissingPropertyCheck(property, readerParam)
}
}
separator = ",\n"
Expand All @@ -590,6 +624,11 @@ internal class AdapterGenerator(

result.addCode("\n»)\n")

// Close the result assignment control flow, if any
if (closeNextControlFlowInAssignment) {
result.endControlFlow()
}

// Assign properties not present in the constructor.
for (property in nonTransientProperties) {
if (property.hasConstructorParameter) {
Expand Down Expand Up @@ -661,6 +700,18 @@ internal class AdapterGenerator(
}
}

private fun FunSpec.Builder.addMissingPropertyCheck(property: PropertyGenerator, readerParam: ParameterSpec) {
val missingPropertyBlock =
CodeBlock.of(
"%T.missingProperty(%S, %S, %N)",
MOSHI_UTIL,
property.localName,
property.jsonName,
readerParam
)
addCode(" ?: throw·%L", missingPropertyBlock)
}

/** Represents a prepared adapter with its [spec] and optional associated [proguardConfig]. */
internal data class PreparedAdapter(val spec: FileSpec, val proguardConfig: ProguardConfig?)

Expand Down

0 comments on commit 7372baa

Please sign in to comment.