Repository: square/moshi
Issue: Adapters that convert null to non-null don’t work with codegen

From [this issue](https://github.com/square/moshi/issues/563#issuecomment-399373718) we have an adapter that converts an explicit null to non-null:

```
class DefaultZeroAdapter {
    @FromJson
    @DefaultZero
    fun fromJson(@Nullable jsonFloat: Float?): Float {
        return jsonFloat ?: 0f
    }

    @ToJson
    fun toJson(@DefaultZero jsonFloat: Float) = jsonFloat
}
```

Unfortunately our generated code forces the _source_ data to be non-null before it is passed to the adapter.

```
    private val floatAtDefaultZeroAdapter: JsonAdapter<Float> =
            moshi.adapter<Float>(Float::class.java, Types.getFieldJsonQualifierAnnotations(javaClass, "floatAtDefaultZeroAdapter")).nonNull()
```

This is a bug. I’m not completely convinced it’s worth fixing, however. The cost in complexity here is pretty high.

Changed files:
kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt
kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/DelegateKey.kt
kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt

diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/DelegateKey.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/DelegateKey.kt
index 3165fed..c42ffce 100644
--- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/DelegateKey.kt
+++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/DelegateKey.kt
@@ -69,11 +69,9 @@ internal data class DelegateKey(
     }
     val finalArgs = arrayOf(*standardArgs, *args)
 
-    val nullModifier = if (nullable) ".nullSafe()" else ".nonNull()"
-
     return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
         .addAnnotations(jsonQualifiers)
-        .initializer("%1N.adapter%2L(%3L$initializerString)$nullModifier", *finalArgs)
+        .initializer("%1N.adapter%2L(%3L$initializerString)", *finalArgs)
         .build()
   }
 }
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt
index be90a49..793cdb9 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt
+++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt
@@ -413,7 +413,7 @@ class GeneratedAdaptersTest {
       jsonAdapter.fromJson("{\\"a\\":null}")
       fail()
     } catch (expected: JsonDataException) {
-      assertThat(expected).hasMessage("Unexpected null at \\$.a")
+      assertThat(expected).hasMessage("Non-null value 'a' was null at \\$.a")
     }
   }
 
@@ -432,7 +432,10 @@ class GeneratedAdaptersTest {
   }
 
   @JsonClass(generateAdapter = true)
-  class HasNonNullConstructorParameter(val a: String)
+  data class HasNonNullConstructorParameter(val a: String)
+
+  @JsonClass(generateAdapter = true)
+  data class HasNullableConstructorParameter(val a: String?)
 
   @Test fun explicitNull() {
     val moshi = Moshi.Builder().build()
@@ -592,7 +595,7 @@ class GeneratedAdaptersTest {
       jsonAdapter.fromJson("{\\"a\\":null}")
       fail()
     } catch (expected: JsonDataException) {
-      assertThat(expected).hasMessage("Unexpected null at \\$.a")
+      assertThat(expected).hasMessage("Non-null value 'a' was null at \\$.a")
     }
   }
 
@@ -1083,6 +1086,50 @@ class GeneratedAdaptersTest {
     assertThat(adapter.fromJson("null")).isNull()
     assertThat(adapter.toJson(null)).isEqualTo("null")
   }
+
+  @Retention(AnnotationRetention.RUNTIME)
+  annotation class Nullable
+
+  @Test fun delegatesToInstalledAdaptersBeforeNullChecking() {
+    val moshi = Moshi.Builder()
+        .add(object {
+          @FromJson fun fromJson(@Nullable string: String?): String {
+            return string ?: "fallback"
+          }
+
+          @ToJson fun toJson(@Nullable value: String?): String {
+            return value ?: "fallback"
+          }
+        })
+        .build()
+
+    val hasNonNullConstructorParameterAdapter =
+        moshi.adapter(HasNonNullConstructorParameter::class.java)
+    assertThat(hasNonNullConstructorParameterAdapter
+        .fromJson("{\\"a\\":null}")).isEqualTo(HasNonNullConstructorParameter("fallback"))
+
+    val hasNullableConstructorParameterAdapter =
+        moshi.adapter(HasNullableConstructorParameter::class.java)
+    assertThat(hasNullableConstructorParameterAdapter
+        .fromJson("{\\"a\\":null}")).isEqualTo(HasNullableConstructorParameter("fallback"))
+    assertThat(hasNullableConstructorParameterAdapter
+        .toJson(HasNullableConstructorParameter(null))).isEqualTo("{\\"a\\":\\"fallback\\"}")
+  }
+
+  @JsonClass(generateAdapter = true)
+  data class HasNullableTypeWithGeneratedAdapter(val a: HasNonNullConstructorParameter?)
+
+  @Test fun delegatesToInstalledAdaptersBeforeNullCheckingWithGeneratedAdapter() {
+    val moshi = Moshi.Builder().build()
+    val adapter = moshi.adapter(HasNullableTypeWithGeneratedAdapter::class.java)
+
+    val encoded = HasNullableTypeWithGeneratedAdapter(null)
+    assertThat(adapter.toJson(encoded)).isEqualTo("""{}""")
+    assertThat(adapter.serializeNulls().toJson(encoded)).isEqualTo("""{"a":null}""")
+
+    val decoded = adapter.fromJson("""{"a":null}""")!!
+    assertThat(decoded.a).isEqualTo(null)
+  }
 }
 
 // Has to be outside to avoid Types seeing an owning class
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
index 89b21de..8993837 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
+++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
@@ -170,7 +170,9 @@ class KotlinJsonAdapterTest {
     }
   }
 
-  class HasNonNullConstructorParameter(val a: String)
+  data class HasNonNullConstructorParameter(val a: String)
+
+  data class HasNullableConstructorParameter(val a: String?)
 
   @Test fun nonNullPropertySetToNullFailsWithJsonDataException() {
     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
@@ -853,6 +855,31 @@ class KotlinJsonAdapterTest {
     assertThat(adapter.toJson(value)).isEqualTo(json)
   }
 
+  @Retention(RUNTIME)
+  annotation class Nullable
+
+  @Test fun delegatesToInstalledAdaptersBeforeNullChecking() {
+    val moshi = Moshi.Builder()
+        .add(object {
+          @FromJson fun fromJson(@Nullable string: String?): String {
+            return string ?: "fallback"
+          }
+
+          @ToJson fun toJson(@Nullable value: String?): String {
+            return value ?: "fallback"
+          }
+        })
+        .build()
+
+    assertThat(moshi.adapter(HasNonNullConstructorParameter::class.java)
+        .fromJson("{\\"a\\":null}")).isEqualTo(HasNonNullConstructorParameter("fallback"))
+
+    assertThat(moshi.adapter(HasNullableConstructorParameter::class.java)
+        .fromJson("{\\"a\\":null}")).isEqualTo(HasNullableConstructorParameter("fallback"))
+    assertThat(moshi.adapter(HasNullableConstructorParameter::class.java)
+        .toJson(HasNullableConstructorParameter(null))).isEqualTo("{\\"a\\":\\"fallback\\"}")
+  }
+
   @Test fun mixingReflectionAndCodegen() {
     val moshi = Moshi.Builder()
         .add(KotlinJsonAdapterFactory())