Repository: square/anvil (kotlin)

Issue:
MembersInjectorGenerator will generate nullable type args for nullable injections
Perhaps this is correct behavior, but to my knowledge Dagger does not allow nulls to be passed through directly.

```kotlin
class Example @Inject constructor() {
  @Inject
  var injected: String? = null
}
```

Will result in member injection refs like this

```kotlin
public class Example_Factory(
  private val param0: Provider<String?>
) : Factory<Example> {
  public override fun get(): Example {
    val instance = newInstance()
    Example_MembersInjector.injectInjected(instance, param0.get())
    return instance
  }
}
```

Pull request:
Fixes #341

Patch:
diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt
index bf4205e81..88ce4daf3 100644
--- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt
+++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt
@@ -42,6 +42,7 @@ public sealed class PropertyReference : AnnotatedReference {
   public abstract val getterAnnotations: List<AnnotationReference>
 
   public abstract fun visibility(): Visibility
+  public abstract fun isLateinit(): Boolean
 
   public fun typeOrNull(): TypeReference? = type
   public fun type(): TypeReference = type
@@ -119,6 +120,10 @@ public sealed class PropertyReference : AnnotatedReference {
       }
     }
 
+    override fun isLateinit(): Boolean {
+      return property.modifierList?.hasModifier(KtTokens.LATEINIT_KEYWORD) ?: false
+    }
+
     internal companion object {
       // There's no single applicable type for a PSI property. The multiple generic bounds prevent
       // us from creating a property out of a function, constructor, or destructuring declaration.
@@ -182,6 +187,8 @@ public sealed class PropertyReference : AnnotatedReference {
         )
       }
     }
+
+    override fun isLateinit(): Boolean = property.isLateInit
   }
 }
 
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
index bf052f468..44e2ef5d2 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
@@ -55,6 +55,7 @@ internal val assistedFactoryFqName = AssistedFactory::class.fqName
 internal val assistedInjectFqName = AssistedInject::class.fqName
 internal val providerFqName = Provider::class.fqName
 internal val jvmSuppressWildcardsFqName = JvmSuppressWildcards::class.fqName
+internal val jvmFieldFqName = JvmField::class.fqName
 internal val publishedApiFqName = PublishedApi::class.fqName
 internal val anyFqName = Any::class.fqName
 
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/DaggerGenerationUtils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/DaggerGenerationUtils.kt
index 2e03c96f2..428994747 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/DaggerGenerationUtils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/DaggerGenerationUtils.kt
@@ -5,6 +5,7 @@ import com.squareup.anvil.compiler.daggerDoubleCheckFqNameString
 import com.squareup.anvil.compiler.daggerLazyFqName
 import com.squareup.anvil.compiler.injectFqName
 import com.squareup.anvil.compiler.internal.capitalize
+import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionPropertyReference
 import com.squareup.anvil.compiler.internal.reference.ClassReference
 import com.squareup.anvil.compiler.internal.reference.ParameterReference
 import com.squareup.anvil.compiler.internal.reference.PropertyReference
@@ -16,6 +17,7 @@ import com.squareup.anvil.compiler.internal.reference.argumentAt
 import com.squareup.anvil.compiler.internal.reference.asClassName
 import com.squareup.anvil.compiler.internal.reference.generateClassName
 import com.squareup.anvil.compiler.internal.withJvmSuppressWildcardsIfNeeded
+import com.squareup.anvil.compiler.jvmFieldFqName
 import com.squareup.anvil.compiler.providerFqName
 import com.squareup.kotlinpoet.ClassName
 import com.squareup.kotlinpoet.FunSpec
@@ -197,6 +199,20 @@ private fun PropertyReference.toMemberInjectParameter(
   uniqueName: String,
   implementingClass: ClassReference
 ): MemberInjectParameter {
+  if (
+    !isLateinit() &&
+    !isAnnotatedWith(jvmFieldFqName) &&
+    setterAnnotations.none { it.fqName == injectFqName }
+  ) {
+    // Technically this works with Anvil and we could remove this check. But we prefer consistency
+    // with Dagger.
+    throw AnvilCompilationExceptionPropertyReference(
+      propertyReference = this,
+      message = "Dagger does not support injection into private fields. Either use a " +
+        "'lateinit var' or '@JvmField'."
+    )
+  }
+
   val originalName = name
   val type = type()
 


Test patch:
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/MembersInjectorGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/MembersInjectorGeneratorTest.kt
index f90bcecdf..ba803f43f 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/MembersInjectorGeneratorTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/MembersInjectorGeneratorTest.kt
@@ -11,6 +11,7 @@ import com.squareup.anvil.compiler.internal.testing.getPropertyValue
 import com.squareup.anvil.compiler.internal.testing.getValue
 import com.squareup.anvil.compiler.internal.testing.isStatic
 import com.squareup.anvil.compiler.internal.testing.membersInjector
+import com.squareup.anvil.compiler.isError
 import com.squareup.anvil.compiler.isFullTestRun
 import com.squareup.anvil.compiler.nestedInjectClass
 import com.tschuchort.compiletesting.KotlinCompilation.ExitCode.OK
@@ -2414,6 +2415,86 @@ public final class InjectClass_MembersInjector<T, U, V> implements MembersInject
     }
   }
 
+  @Test
+  fun `a member injector is not generated when Dagger doesn't support it`() {
+    compile(
+      """
+      package com.squareup.test
+ 
+      import javax.inject.Inject
+
+      class InjectClass {
+        @Inject
+        var injected: String? = null
+      }
+      """
+    ) {
+      assertThat(exitCode).isError()
+      assertThat(messages).contains("Dagger does not support injection into private fields")
+
+      assertFailsWith<ClassNotFoundException> {
+        injectClass.membersInjector()
+      }
+    }
+
+    compile(
+      """
+      package com.squareup.test
+ 
+      import javax.inject.Inject
+
+      class InjectClass {
+        @Inject
+        lateinit var injected1: String
+        
+        @Inject @JvmField
+        var injected2: String? = null
+        
+        override fun equals(other: Any?): Boolean {
+          if (this === other) return true
+          if (javaClass != other?.javaClass) return false
+      
+          other as InjectClass
+      
+          if (injected1 != other.injected1) return false
+          if (injected2 != other.injected2) return false
+      
+          return true
+        }
+      
+        override fun hashCode(): Int {
+          var result = injected1.hashCode()
+          result = 31 * result + injected2.hashCode()
+          return result
+        }
+      }
+      """
+    ) {
+      val membersInjector = injectClass.membersInjector()
+
+      val constructor = membersInjector.declaredConstructors.single()
+      assertThat(constructor.parameterTypes.toList())
+        .containsExactly(Provider::class.java, Provider::class.java)
+
+      val membersInjectorInstance = constructor
+        .newInstance(Provider { "a" }, Provider { "b" }) as MembersInjector<Any>
+
+      val injectInstanceConstructor = injectClass.createInstance()
+      membersInjectorInstance.injectMembers(injectInstanceConstructor)
+
+      val injectInstanceStatic = injectClass.createInstance()
+
+      membersInjector.staticInjectMethod("injected1")
+        .invoke(null, injectInstanceStatic, "a")
+
+      membersInjector.staticInjectMethod("injected2")
+        .invoke(null, injectInstanceStatic, "b")
+
+      assertThat(injectInstanceConstructor).isEqualTo(injectInstanceStatic)
+      assertThat(injectInstanceConstructor).isNotSameInstanceAs(injectInstanceStatic)
+    }
+  }
+
   private fun Class<*>.staticInjectMethod(memberName: String): Method {
     // We can't check the @InjectedFieldSignature annotation unfortunately, because it has class
     // retention.