1 /*
<lambda>null2  * Copyright (C) 2018 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.moshi.kotlin.codegen.api
17 
18 import com.squareup.kotlinpoet.ARRAY
19 import com.squareup.kotlinpoet.AnnotationSpec
20 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
21 import com.squareup.kotlinpoet.CodeBlock
22 import com.squareup.kotlinpoet.FileSpec
23 import com.squareup.kotlinpoet.FunSpec
24 import com.squareup.kotlinpoet.INT
25 import com.squareup.kotlinpoet.KModifier
26 import com.squareup.kotlinpoet.MemberName
27 import com.squareup.kotlinpoet.NameAllocator
28 import com.squareup.kotlinpoet.ParameterSpec
29 import com.squareup.kotlinpoet.ParameterizedTypeName
30 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
31 import com.squareup.kotlinpoet.PropertySpec
32 import com.squareup.kotlinpoet.TypeName
33 import com.squareup.kotlinpoet.TypeSpec
34 import com.squareup.kotlinpoet.TypeVariableName
35 import com.squareup.kotlinpoet.asClassName
36 import com.squareup.kotlinpoet.asTypeName
37 import com.squareup.kotlinpoet.joinToCode
38 import com.squareup.moshi.JsonAdapter
39 import com.squareup.moshi.JsonReader
40 import com.squareup.moshi.JsonWriter
41 import com.squareup.moshi.Moshi
42 import com.squareup.moshi.internal.Util
43 import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterOnly
44 import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterProperty
45 import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly
46 import java.lang.reflect.Constructor
47 import java.lang.reflect.Type
48 import org.objectweb.asm.Type as AsmType
49 
50 private val MOSHI_UTIL = Util::class.asClassName()
51 private const val TO_STRING_PREFIX = "GeneratedJsonAdapter("
52 private const val TO_STRING_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the closing paren
53 
54 /** Generates a JSON adapter for a target type. */
55 @InternalMoshiCodegenApi
56 public class AdapterGenerator(
57   private val target: TargetType,
58   private val propertyList: List<PropertyGenerator>
59 ) {
60 
61   private companion object {
62     private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT)
63     private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of(
64       "%T.DEFAULT_CONSTRUCTOR_MARKER",
65       Util::class
66     )
67     private val CN_MOSHI = Moshi::class.asClassName()
68     private val CN_TYPE = Type::class.asClassName()
69 
70     private val COMMON_SUPPRESS = arrayOf(
71       // https://github.com/square/moshi/issues/1023
72       "DEPRECATION",
73       // Because we look it up reflectively
74       "unused",
75       "UNUSED_PARAMETER",
76       // Because we include underscores
77       "ClassName",
78       // Because we generate redundant `out` variance for some generics and there's no way
79       // for us to know when it's redundant.
80       "REDUNDANT_PROJECTION",
81       // Because we may generate redundant explicit types for local vars with default values.
82       // Example: 'var fooSet: Boolean = false'
83       "RedundantExplicitType",
84       // NameAllocator will just add underscores to differentiate names, which Kotlin doesn't
85       // like for stylistic reasons.
86       "LocalVariableName",
87       // KotlinPoet always generates explicit public modifiers for public members.
88       "RedundantVisibilityModifier",
89       // For LambdaTypeNames we have to import kotlin.functions.* types
90       "PLATFORM_CLASS_MAPPED_TO_KOTLIN",
91       // Cover for calling fromJson() on a Nothing property type. Theoretically nonsensical but we
92       // support it
93       "IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION"
94     ).let { suppressions ->
95       AnnotationSpec.builder(Suppress::class)
96         .useSiteTarget(FILE)
97         .addMember(
98           suppressions.indices.joinToString { "%S" },
99           *suppressions
100         )
101         .build()
102     }
103   }
104 
105   private val nonTransientProperties = propertyList.filterNot { it.isTransient }
106   private val className = target.typeName.rawType()
107   private val visibility = target.visibility
108   private val typeVariables = target.typeVariables
109   private val typeVariableResolver = typeVariables.toTypeVariableResolver()
110   private val targetConstructorParams = target.constructor.parameters
111     .mapKeys { (_, param) -> param.index }
112 
113   private val nameAllocator = NameAllocator()
114   private val adapterName = "${className.simpleNames.joinToString(separator = "_")}JsonAdapter"
115   private val originalTypeName = target.typeName.stripTypeVarVariance(typeVariableResolver)
116   private val originalRawTypeName = originalTypeName.rawType()
117 
118   private val moshiParam = ParameterSpec.builder(
119     nameAllocator.newName("moshi"),
120     CN_MOSHI
121   ).build()
122   private val typesParam = ParameterSpec.builder(
123     nameAllocator.newName("types"),
124     ARRAY.parameterizedBy(CN_TYPE)
125   )
126     .build()
127   private val readerParam = ParameterSpec.builder(
128     nameAllocator.newName("reader"),
129     JsonReader::class
130   )
131     .build()
132   private val writerParam = ParameterSpec.builder(
133     nameAllocator.newName("writer"),
134     JsonWriter::class
135   )
136     .build()
137   private val valueParam = ParameterSpec.builder(
138     nameAllocator.newName("value"),
139     originalTypeName.copy(nullable = true)
140   )
141     .build()
142   private val jsonAdapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(
143     originalTypeName
144   )
145 
146   // selectName() API setup
147   private val optionsProperty = PropertySpec.builder(
148     nameAllocator.newName("options"),
149     JsonReader.Options::class.asTypeName(),
150     KModifier.PRIVATE
151   )
152     .initializer(
153       "%T.of(%L)",
154       JsonReader.Options::class.asTypeName(),
155       nonTransientProperties
156         .map { CodeBlock.of("%S", it.jsonName) }
157         .joinToCode(", ")
158     )
159     .build()
160 
161   private val constructorProperty = PropertySpec.builder(
162     nameAllocator.newName("constructorRef"),
163     Constructor::class.asClassName().parameterizedBy(originalTypeName).copy(nullable = true),
164     KModifier.PRIVATE
165   )
166     .addAnnotation(Volatile::class)
167     .mutable(true)
168     .initializer("null")
169     .build()
170 
171   public fun prepare(generateProguardRules: Boolean, typeHook: (TypeSpec) -> TypeSpec = { it }): PreparedAdapter {
172     val reservedSimpleNames = mutableSetOf<String>()
173     for (property in nonTransientProperties) {
174       // Allocate names for simple property types first to avoid collisions
175       // See https://github.com/square/moshi/issues/1277
176       property.target.type.findRawType()?.simpleName?.let { simpleNameToReserve ->
177         if (reservedSimpleNames.add(simpleNameToReserve)) {
178           nameAllocator.newName(simpleNameToReserve)
179         }
180       }
181       property.allocateNames(nameAllocator)
182     }
183 
184     val generatedAdapter = generateType().let(typeHook)
185     val result = FileSpec.builder(className.packageName, adapterName)
186     result.addFileComment("Code generated by moshi-kotlin-codegen. Do not edit.")
187     result.addAnnotation(COMMON_SUPPRESS)
188     result.addType(generatedAdapter)
189     val proguardConfig = if (generateProguardRules) {
190       generatedAdapter.createProguardRule()
191     } else {
192       null
193     }
194     return PreparedAdapter(result.build(), proguardConfig)
195   }
196 
197   private fun TypeSpec.createProguardRule(): ProguardConfig {
198     val adapterConstructorParams = when (requireNotNull(primaryConstructor).parameters.size) {
199       1 -> listOf(CN_MOSHI.reflectionName())
200       2 -> listOf(CN_MOSHI.reflectionName(), "${CN_TYPE.reflectionName()}[]")
201       // Should never happen
202       else -> error("Unexpected number of arguments on primary constructor: $primaryConstructor")
203     }
204 
205     var hasDefaultProperties = false
206     var parameterTypes = emptyList<String>()
207     target.constructor.signature?.let { constructorSignature ->
208       if (constructorSignature.startsWith("constructor-impl")) {
209         // Inline class, we don't support this yet.
210         // This is a static method with signature like 'constructor-impl(I)I'
211         return@let
212       }
213       hasDefaultProperties = propertyList.any { it.hasDefault }
214       parameterTypes = AsmType.getArgumentTypes(constructorSignature.removePrefix("<init>"))
215         .map { it.toReflectionString() }
216     }
217     return ProguardConfig(
218       targetClass = className,
219       adapterName = adapterName,
220       adapterConstructorParams = adapterConstructorParams,
221       targetConstructorHasDefaults = hasDefaultProperties,
222       targetConstructorParams = parameterTypes,
223     )
224   }
225 
226   private fun generateType(): TypeSpec {
227     val result = TypeSpec.classBuilder(adapterName)
228 
229     result.superclass(jsonAdapterTypeName)
230 
231     if (typeVariables.isNotEmpty()) {
232       result.addTypeVariables(typeVariables.map { it.stripTypeVarVariance(typeVariableResolver) as TypeVariableName })
233       // require(types.size == 1) {
234       //   "TypeVariable mismatch: Expecting 1 type(s) for generic type variables [T], but received ${types.size} with values $types"
235       // }
236       result.addInitializerBlock(
237         CodeBlock.builder()
238           .beginControlFlow("require(types.size == %L)", typeVariables.size)
239           .addStatement(
240             "buildString·{·append(%S).append(%L).append(%S).append(%S).append(%S).append(%L)·}",
241             "TypeVariable mismatch: Expecting ",
242             typeVariables.size,
243             " ${if (typeVariables.size == 1) "type" else "types"} for generic type variables [",
244             typeVariables.joinToString(", ") { it.name },
245             "], but received ",
246             "${typesParam.name}.size"
247           )
248           .endControlFlow()
249           .build()
250       )
251     }
252 
253     // TODO make this configurable. Right now it just matches the source model
254     if (visibility == KModifier.INTERNAL) {
255       result.addModifiers(KModifier.INTERNAL)
256     }
257 
258     result.primaryConstructor(generateConstructor())
259 
260     val typeRenderer: TypeRenderer = object : TypeRenderer() {
261       override fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock {
262         // Match only by name because equality checks for more things than just the name. For example, a base class
263         // may declare "T" but the subclass declares "T : Number", which is legal but will fail an equals() test.
264         val index = typeVariables.indexOfFirst { it.name == typeVariable.name }
265         check(index != -1) {
266           "Unexpected type variable $typeVariable"
267         }
268         return CodeBlock.of("%N[%L]", typesParam, index)
269       }
270     }
271 
272     result.addProperty(optionsProperty)
273     for (uniqueAdapter in nonTransientProperties.distinctBy { it.delegateKey }) {
274       result.addProperty(
275         uniqueAdapter.delegateKey.generateProperty(
276           nameAllocator,
277           typeRenderer,
278           moshiParam,
279           uniqueAdapter.name
280         )
281       )
282     }
283 
284     result.addFunction(generateToStringFun())
285     result.addFunction(generateFromJsonFun(result))
286     result.addFunction(generateToJsonFun())
287 
288     return result.build()
289   }
290 
291   private fun generateConstructor(): FunSpec {
292     val result = FunSpec.constructorBuilder()
293     result.addParameter(moshiParam)
294 
295     if (typeVariables.isNotEmpty()) {
296       result.addParameter(typesParam)
297     }
298 
299     return result.build()
300   }
301 
302   private fun generateToStringFun(): FunSpec {
303     val name = originalRawTypeName.simpleNames.joinToString(".")
304     val size = TO_STRING_SIZE_BASE + name.length
305     return FunSpec.builder("toString")
306       .addModifiers(KModifier.OVERRIDE)
307       .returns(String::class)
308       .addStatement(
309         "return %M(%L)·{ append(%S).append(%S).append('%L') }",
310         MemberName("kotlin.text", "buildString"),
311         size,
312         TO_STRING_PREFIX,
313         name,
314         ")"
315       )
316       .build()
317   }
318 
319   private fun generateFromJsonFun(classBuilder: TypeSpec.Builder): FunSpec {
320     val result = FunSpec.builder("fromJson")
321       .addModifiers(KModifier.OVERRIDE)
322       .addParameter(readerParam)
323       .returns(originalTypeName)
324 
325     for (property in nonTransientProperties) {
326       result.addCode("%L", property.generateLocalProperty())
327       if (property.hasLocalIsPresentName) {
328         result.addCode("%L", property.generateLocalIsPresentProperty())
329       }
330     }
331 
332     val propertiesByIndex = propertyList.asSequence()
333       .filter { it.hasConstructorParameter }
334       .associateBy { it.target.parameterIndex }
335     val components = mutableListOf<FromJsonComponent>()
336 
337     // Add parameters (± properties) first, their index matters
338     for ((index, parameter) in targetConstructorParams) {
339       val property = propertiesByIndex[index]
340       if (property == null) {
341         components += ParameterOnly(parameter)
342       } else {
343         components += ParameterProperty(parameter, property)
344       }
345     }
346 
347     // Now add the remaining properties that aren't parameters
348     for (property in propertyList) {
349       if (property.target.parameterIndex in targetConstructorParams) {
350         continue // Already handled
351       }
352       if (property.isTransient) {
353         continue // We don't care about these outside of constructor parameters
354       }
355       components += PropertyOnly(property)
356     }
357 
358     // Calculate how many masks we'll need. Round up if it's not evenly divisible by 32
359     val propertyCount = targetConstructorParams.size
360     val maskCount = if (propertyCount == 0) {
361       0
362     } else {
363       (propertyCount + 31) / 32
364     }
365     // Allocate mask names
366     val maskNames = Array(maskCount) { index ->
367       nameAllocator.newName("mask$index")
368     }
369     val maskAllSetValues = Array(maskCount) { -1 }
370     val useDefaultsConstructor = components.filterIsInstance<ParameterComponent>()
371       .any { it.parameter.hasDefault }
372     if (useDefaultsConstructor) {
373       // Initialize all our masks, defaulting to fully unset (-1)
374       for (maskName in maskNames) {
375         result.addStatement("var %L = -1", maskName)
376       }
377     }
378     result.addStatement("%N.beginObject()", readerParam)
379     result.beginControlFlow("while (%N.hasNext())", readerParam)
380     result.beginControlFlow("when (%N.selectName(%N))", readerParam, optionsProperty)
381 
382     // We track property index and mask index separately, because mask index is based on _all_
383     // constructor arguments, while property index is only based on the index passed into
384     // JsonReader.Options.
385     var propertyIndex = 0
386     val constructorPropertyTypes = mutableListOf<CodeBlock>()
387 
388     //
389     // Track important indices for masks. Masks generally increment with each parameter (including
390     // transient).
391     //
392     // Mask name index is an index into the maskNames array we initialized above.
393     //
394     // Once the maskIndex reaches 32, we've filled up that mask and have to move to the next mask
395     // name. Reset the maskIndex relative to here and continue incrementing.
396     //
397     var maskIndex = 0
398     var maskNameIndex = 0
399     val updateMaskIndexes = {
400       maskIndex++
401       if (maskIndex == 32) {
402         // Move to the next mask
403         maskIndex = 0
404         maskNameIndex++
405       }
406     }
407 
408     for (input in components) {
409       if (input is ParameterOnly ||
410         (input is ParameterProperty && input.property.isTransient)
411       ) {
412         updateMaskIndexes()
413         constructorPropertyTypes += input.type.asTypeBlock()
414         continue
415       } else if (input is PropertyOnly && input.property.isTransient) {
416         continue
417       }
418 
419       // We've removed all parameter-only types by this point
420       val property = (input as PropertyComponent).property
421 
422       // Proceed as usual
423       if (property.hasLocalIsPresentName || property.hasConstructorDefault) {
424         result.beginControlFlow("%L ->", propertyIndex)
425         if (property.delegateKey.nullable) {
426           result.addStatement(
427             "%N = %N.fromJson(%N)",
428             property.localName,
429             nameAllocator[property.delegateKey],
430             readerParam
431           )
432         } else {
433           val exception = unexpectedNull(property, readerParam)
434           result.addStatement(
435             "%N = %N.fromJson(%N) ?: throw·%L",
436             property.localName,
437             nameAllocator[property.delegateKey],
438             readerParam,
439             exception
440           )
441         }
442         if (property.hasConstructorDefault) {
443           val inverted = (1 shl maskIndex).inv()
444           if (input is ParameterComponent && input.parameter.hasDefault) {
445             maskAllSetValues[maskNameIndex] = maskAllSetValues[maskNameIndex] and inverted
446           }
447           result.addComment("\$mask = \$mask and (1 shl %L).inv()", maskIndex)
448           result.addStatement(
449             "%1L = %1L and 0x%2L.toInt()",
450             maskNames[maskNameIndex],
451             Integer.toHexString(inverted)
452           )
453         } else {
454           // Presence tracker for a mutable property
455           result.addStatement("%N = true", property.localIsPresentName)
456         }
457         result.endControlFlow()
458       } else {
459         if (property.delegateKey.nullable) {
460           result.addStatement(
461             "%L -> %N = %N.fromJson(%N)",
462             propertyIndex,
463             property.localName,
464             nameAllocator[property.delegateKey],
465             readerParam
466           )
467         } else {
468           val exception = unexpectedNull(property, readerParam)
469           result.addStatement(
470             "%L -> %N = %N.fromJson(%N) ?: throw·%L",
471             propertyIndex,
472             property.localName,
473             nameAllocator[property.delegateKey],
474             readerParam,
475             exception
476           )
477         }
478       }
479       if (property.hasConstructorParameter) {
480         constructorPropertyTypes += property.target.type.asTypeBlock()
481       }
482       propertyIndex++
483       updateMaskIndexes()
484     }
485 
486     result.beginControlFlow("-1 ->")
487     result.addComment("Unknown name, skip it.")
488     result.addStatement("%N.skipName()", readerParam)
489     result.addStatement("%N.skipValue()", readerParam)
490     result.endControlFlow()
491 
492     result.endControlFlow() // when
493     result.endControlFlow() // while
494     result.addStatement("%N.endObject()", readerParam)
495 
496     var separator = "\n"
497 
498     val resultName = nameAllocator.newName("result")
499     val hasNonConstructorProperties = nonTransientProperties.any { !it.hasConstructorParameter }
500     val returnOrResultAssignment = if (hasNonConstructorProperties) {
501       // Save the result var for reuse
502       result.addStatement("val %N: %T", resultName, originalTypeName)
503       CodeBlock.of("%N = ", resultName)
504     } else {
505       CodeBlock.of("return·")
506     }
507 
508     // Used to indicate we're in an if-block that's assigning our result value and
509     // needs to be closed with endControlFlow
510     var closeNextControlFlowInAssignment = false
511 
512     if (useDefaultsConstructor) {
513       // Happy path - all parameters with defaults are set
514       val allMasksAreSetBlock = maskNames.withIndex()
515         .map { (index, maskName) ->
516           CodeBlock.of("$maskName·== 0x${Integer.toHexString(maskAllSetValues[index])}.toInt()")
517         }
518         .joinToCode("·&& ")
519       result.beginControlFlow("if (%L)", allMasksAreSetBlock)
520       result.addComment("All parameters with defaults are set, invoke the constructor directly")
521       result.addCode("«%L·%T(", returnOrResultAssignment, originalTypeName)
522       var localSeparator = "\n"
523       val paramsToSet = components.filterIsInstance<ParameterProperty>()
524         .filterNot { it.property.isTransient }
525 
526       // Set all non-transient property parameters
527       for (input in paramsToSet) {
528         result.addCode(localSeparator)
529         val property = input.property
530         result.addCode("%N = %N", property.name, property.localName)
531         if (property.isRequired) {
532           result.addMissingPropertyCheck(property, readerParam)
533         } else if (!input.type.isNullable) {
534           // Unfortunately incurs an intrinsic null-check even though we know it's set, but
535           // maybe in the future we can use contracts to omit them.
536           result.addCode("·as·%T", input.type)
537         }
538         localSeparator = ",\n"
539       }
540       result.addCode("\n»)\n")
541       result.nextControlFlow("else")
542       closeNextControlFlowInAssignment = true
543 
544       classBuilder.addProperty(constructorProperty)
545       result.addComment("Reflectively invoke the synthetic defaults constructor")
546       // Dynamic default constructor call
547       val nonNullConstructorType = constructorProperty.type.copy(nullable = false)
548       val args = constructorPropertyTypes
549         .plus(0.until(maskCount).map { INT_TYPE_BLOCK }) // Masks, one every 32 params
550         .plus(DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK) // Default constructor marker is always last
551         .joinToCode(", ")
552       val coreLookupBlock = CodeBlock.of(
553         "%T::class.java.getDeclaredConstructor(%L)",
554         originalRawTypeName,
555         args
556       )
557       val lookupBlock = if (originalTypeName is ParameterizedTypeName) {
558         CodeBlock.of("(%L·as·%T)", coreLookupBlock, nonNullConstructorType)
559       } else {
560         coreLookupBlock
561       }
562       val initializerBlock = CodeBlock.of(
563         "this.%1N·?: %2L.also·{ this.%1N·= it }",
564         constructorProperty,
565         lookupBlock
566       )
567       val localConstructorProperty = PropertySpec.builder(
568         nameAllocator.newName("localConstructor"),
569         nonNullConstructorType
570       )
571         .addAnnotation(
572           AnnotationSpec.builder(Suppress::class)
573             .addMember("%S", "UNCHECKED_CAST")
574             .build()
575         )
576         .initializer(initializerBlock)
577         .build()
578       result.addCode("%L", localConstructorProperty)
579       result.addCode(
580         "«%L%N.newInstance(",
581         returnOrResultAssignment,
582         localConstructorProperty
583       )
584     } else {
585       // Standard constructor call. Don't omit generics for parameterized types even if they can be
586       // inferred, as calculating the right condition for inference exceeds the value gained from
587       // being less pedantic.
588       result.addCode("«%L%T(", returnOrResultAssignment, originalTypeName)
589     }
590 
591     for (input in components.filterIsInstance<ParameterComponent>()) {
592       result.addCode(separator)
593       if (useDefaultsConstructor) {
594         if (input is ParameterOnly || (input is ParameterProperty && input.property.isTransient)) {
595           // We have to use the default primitive for the available type in order for
596           // invokeDefaultConstructor to properly invoke it. Just using "null" isn't safe because
597           // the transient type may be a primitive type.
598           // Inline a little comment for readability indicating which parameter is it's referring to
599           result.addCode(
600             "/*·%L·*/·%L",
601             input.parameter.name,
602             input.type.rawType().defaultPrimitiveValue()
603           )
604         } else {
605           result.addCode("%N", (input as ParameterProperty).property.localName)
606         }
607       } else if (input !is ParameterOnly) {
608         val property = (input as ParameterProperty).property
609         result.addCode("%N = %N", property.name, property.localName)
610       }
611       if (input is PropertyComponent) {
612         val property = input.property
613         if (!property.isTransient && property.isRequired) {
614           result.addMissingPropertyCheck(property, readerParam)
615         }
616       }
617       separator = ",\n"
618     }
619 
620     if (useDefaultsConstructor) {
621       // Add the masks and a null instance for the trailing default marker instance
622       result.addCode(",\n%L,\n/*·DefaultConstructorMarker·*/·null", maskNames.map { CodeBlock.of("%L", it) }.joinToCode(", "))
623     }
624 
625     result.addCode("\n»)\n")
626 
627     // Close the result assignment control flow, if any
628     if (closeNextControlFlowInAssignment) {
629       result.endControlFlow()
630     }
631 
632     // Assign properties not present in the constructor.
633     for (property in nonTransientProperties) {
634       if (property.hasConstructorParameter) {
635         continue // Property already handled.
636       }
637       if (property.hasLocalIsPresentName) {
638         result.beginControlFlow("if (%N)", property.localIsPresentName)
639         result.addStatement(
640           "%N.%N = %N",
641           resultName,
642           property.name,
643           property.localName
644         )
645         result.endControlFlow()
646       } else {
647         result.addStatement(
648           "%1N.%2N = %3N ?: %1N.%2N",
649           resultName,
650           property.name,
651           property.localName
652         )
653       }
654     }
655 
656     if (hasNonConstructorProperties) {
657       result.addStatement("return·%1N", resultName)
658     }
659     return result.build()
660   }
661 
662   private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock {
663     return CodeBlock.of(
664       "%T.unexpectedNull(%S, %S, %N)",
665       MOSHI_UTIL,
666       property.localName,
667       property.jsonName,
668       reader
669     )
670   }
671 
672   private fun generateToJsonFun(): FunSpec {
673     val result = FunSpec.builder("toJson")
674       .addModifiers(KModifier.OVERRIDE)
675       .addParameter(writerParam)
676       .addParameter(valueParam)
677 
678     result.beginControlFlow("if (%N == null)", valueParam)
679     result.addStatement(
680       "throw·%T(%S)",
681       NullPointerException::class,
682       "${valueParam.name} was null! Wrap in .nullSafe() to write nullable values."
683     )
684     result.endControlFlow()
685 
686     result.addStatement("%N.beginObject()", writerParam)
687     nonTransientProperties.forEach { property ->
688       // We manually put in quotes because we know the jsonName is already escaped
689       result.addStatement("%N.name(%S)", writerParam, property.jsonName)
690       result.addStatement(
691         "%N.toJson(%N, %N.%N)",
692         nameAllocator[property.delegateKey],
693         writerParam,
694         valueParam,
695         property.name
696       )
697     }
698     result.addStatement("%N.endObject()", writerParam)
699 
700     return result.build()
701   }
702 }
703 
FunSpecnull704 private fun FunSpec.Builder.addMissingPropertyCheck(property: PropertyGenerator, readerParam: ParameterSpec) {
705   val missingPropertyBlock =
706     CodeBlock.of(
707       "%T.missingProperty(%S, %S, %N)",
708       MOSHI_UTIL,
709       property.localName,
710       property.jsonName,
711       readerParam
712     )
713   addCode(" ?: throw·%L", missingPropertyBlock)
714 }
715 
716 /** Represents a prepared adapter with its [spec] and optional associated [proguardConfig]. */
717 @InternalMoshiCodegenApi
718 public data class PreparedAdapter(val spec: FileSpec, val proguardConfig: ProguardConfig?)
719 
toReflectionStringnull720 private fun AsmType.toReflectionString(): String {
721   return when (this) {
722     AsmType.VOID_TYPE -> "void"
723     AsmType.BOOLEAN_TYPE -> "boolean"
724     AsmType.CHAR_TYPE -> "char"
725     AsmType.BYTE_TYPE -> "byte"
726     AsmType.SHORT_TYPE -> "short"
727     AsmType.INT_TYPE -> "int"
728     AsmType.FLOAT_TYPE -> "float"
729     AsmType.LONG_TYPE -> "long"
730     AsmType.DOUBLE_TYPE -> "double"
731     else -> when (sort) {
732       AsmType.ARRAY -> "${elementType.toReflectionString()}[]"
733       // Object type
734       else -> className
735     }
736   }
737 }
738 
739 private interface PropertyComponent {
740   val property: PropertyGenerator
741   val type: TypeName
742 }
743 
744 private interface ParameterComponent {
745   val parameter: TargetParameter
746   val type: TypeName
747 }
748 
749 /**
750  * Type hierarchy for describing fromJson() components. Specifically - parameters, properties, and
751  * parameter properties. All three of these scenarios participate in fromJson() parsing.
752  */
753 private sealed class FromJsonComponent {
754 
755   abstract val type: TypeName
756 
757   data class ParameterOnly(
758     override val parameter: TargetParameter
759   ) : FromJsonComponent(), ParameterComponent {
760     override val type: TypeName = parameter.type
761   }
762 
763   data class PropertyOnly(
764     override val property: PropertyGenerator
765   ) : FromJsonComponent(), PropertyComponent {
766     override val type: TypeName = property.target.type
767   }
768 
769   data class ParameterProperty(
770     override val parameter: TargetParameter,
771     override val property: PropertyGenerator
772   ) : FromJsonComponent(), ParameterComponent, PropertyComponent {
773     override val type: TypeName = parameter.type
774   }
775 }
776