Repository: square/anvil (kotlin)

Issue:
Still MemberInjectors issue for super classes in different Gradle project
@RBusarow 
I just tried latest SNAPSHOT, and we are closer!

I now see this when I try to build here:
https://github.com/JohnBuhanan/anvil-factory-generation-members-injector-issue


```
public class FeatureActivity1_MembersInjector(
  private val b1: Provider<B1>,
  private val b2: Provider<B2>,
  private val f1: Provider<F1>
) : MembersInjector<FeatureActivity1> {
  public override fun injectMembers(instance: FeatureActivity1): Unit {
    BaseActivity2_MembersInjector.injectB1(instance, b1.get())  <--- Should be BaseActivity1 I think?
    BaseActivity2_MembersInjector.injectB2(instance, b2.get())
    FeatureActivity1_MembersInjector.injectF1(instance, f1.get())
  }
...
```

Pull request:
fixes #442

Patch:
diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/ClassReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/ClassReference.kt
index b625625da..ebfbca7bb 100644
--- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/ClassReference.kt
+++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/ClassReference.kt
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
 import org.jetbrains.kotlin.name.ClassId
 import org.jetbrains.kotlin.name.FqName
 import org.jetbrains.kotlin.psi.KtClassOrObject
+import org.jetbrains.kotlin.psi.KtEnumEntry
 import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
 
 /**
@@ -119,11 +120,14 @@ public fun ClassReference.directSuperClassReferences(
     is Descriptor -> clazz.directSuperClassAndInterfaces()
       .asSequence()
       .map { module.requireClassReference(it.fqNameSafe) }
-    is Psi ->
+    is Psi -> if (clazz is KtEnumEntry) {
+      emptySequence()
+    } else {
       clazz.superTypeListEntries
         .asSequence()
         .map { it.requireFqName(module) }
         .map { module.requireClassReference(it) }
+    }
   }
 }
 
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/DescriptorUtils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/DescriptorUtils.kt
index 46f93c111..5e0a1f892 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/DescriptorUtils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/DescriptorUtils.kt
@@ -4,7 +4,6 @@ import com.squareup.anvil.compiler.assistedFqName
 import com.squareup.anvil.compiler.daggerLazyFqName
 import com.squareup.anvil.compiler.injectFqName
 import com.squareup.anvil.compiler.internal.argumentType
-import com.squareup.anvil.compiler.internal.asClassName
 import com.squareup.anvil.compiler.internal.asTypeName
 import com.squareup.anvil.compiler.internal.capitalize
 import com.squareup.anvil.compiler.internal.classDescriptorOrNull
@@ -15,55 +14,25 @@ import com.squareup.anvil.compiler.internal.withJvmSuppressWildcardsIfNeeded
 import com.squareup.anvil.compiler.providerFqName
 import com.squareup.kotlinpoet.ClassName
 import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind
 import org.jetbrains.kotlin.descriptors.ClassDescriptor
 import org.jetbrains.kotlin.descriptors.ModuleDescriptor
 import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.containingPackage
 import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
 import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
 import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
-import org.jetbrains.kotlin.resolve.scopes.MemberScope
 import org.jetbrains.kotlin.types.KotlinType
 import org.jetbrains.kotlin.types.isNullable
 import org.jetbrains.kotlin.utils.addToStdlib.safeAs
 
-internal fun ClassDescriptor.superClassMemberInjectedParameters(
-  module: ModuleDescriptor
-): List<MemberInjectParameter> {
+fun ClassDescriptor.memberInjectedPropertyDescriptors(): List<PropertyDescriptor> {
 
-  val allInjectedPropertyNames = mutableSetOf<Name>()
-
-  return getAllSuperClassifiers()
-    // no point in parsing android/androidx classes for injected params, so skip them
-    .filter { it.containingPackage()?.asString()?.startsWith("android") == false }
-    // the sequence of supers actually starts with itself, so for instance `class Impl : Base()`
-    // returns [ Impl, Base, kotlin.Any ]
-    // drop the first element since this function is supposed to return properties from supers.
-    .drop(1)
-    .map { it.fqNameSafe.requireClassDescriptor(module) }
-    // look for any @Inject-annotated members.
-    // Downstream properties are parsed before the properties they override.
-    // If a property has already been added from a downstream sub-class, then ignore it.
-    .flatMap { classDescriptor ->
-      classDescriptor
-        .unsubstitutedMemberScope
-        .memberInjectedPropertyDescriptors()
-        .mapNotNull { property ->
-          // Only parse the property if it hasn't been added already.
-          property.takeIf { allInjectedPropertyNames.add(property.name) }
-        }
-        .mapToMemberInjectParameters(module, classDescriptor.asClassName())
-    }
-    .toList()
-}
-
-fun MemberScope.memberInjectedPropertyDescriptors(): List<PropertyDescriptor> {
-
-  return getContributedDescriptors(DescriptorKindFilter.VARIABLES)
+  return unsubstitutedMemberScope
+    .getContributedDescriptors(DescriptorKindFilter.VARIABLES)
     .filterIsInstance<PropertyDescriptor>()
+    // only return properties which are directly declared (including overrides)
+    .filter { it.kind == Kind.DECLARATION }
     .filter { it.hasAnnotation(injectFqName) }
 }
 
@@ -85,10 +54,15 @@ fun KotlinType.fqName(): FqName = requireClassDescriptor().fqNameSafe
 
 internal fun List<CallableMemberDescriptor>.mapToMemberInjectParameters(
   module: ModuleDescriptor,
-  containingClassName: ClassName
+  containingClassName: ClassName,
+  superParameters: List<Parameter>
 ): List<MemberInjectParameter> {
 
-  return map { descriptor ->
+  return fold(listOf()) { acc, descriptor ->
+
+    val originalName = descriptor.name.asString()
+
+    val uniqueName = originalName.uniqueParameterName(superParameters, acc)
 
     val type = descriptor.safeAs<PropertyDescriptor>()?.type
       ?: descriptor.valueParameters.first().type
@@ -140,8 +114,6 @@ internal fun List<CallableMemberDescriptor>.mapToMemberInjectParameters(
       containingClassName.packageName, memberInjectorClassName
     )
 
-    val originalName = descriptor.name.asString()
-
     val isSetterInjected = descriptor.safeAs<PropertyDescriptor>()
       ?.isSetterInjected() == true
 
@@ -156,8 +128,9 @@ internal fun List<CallableMemberDescriptor>.mapToMemberInjectParameters(
       .filter { it.isQualifier() }
       .map { it.toAnnotationSpec(module) }
 
-    MemberInjectParameter(
-      name = originalName,
+    acc + MemberInjectParameter(
+      name = uniqueName,
+      originalName = originalName,
       typeName = typeName,
       providerTypeName = typeName.wrapInProvider(),
       lazyTypeName = typeName.wrapInLazy(),
@@ -167,7 +140,6 @@ internal fun List<CallableMemberDescriptor>.mapToMemberInjectParameters(
       isAssisted = assistedAnnotation != null,
       assistedIdentifier = assistedIdentifier,
       memberInjectorClassName = memberInjectorClass,
-      originalName = originalName,
       isSetterInjected = isSetterInjected,
       accessName = accessName,
       qualifierAnnotationSpecs = qualifierAnnotations,
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoetUtils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoetUtils.kt
index a759296da..953197cf3 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoetUtils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoetUtils.kt
@@ -49,12 +49,16 @@ private fun KtTypeElement.singleTypeArgument(): KtTypeReference {
 internal fun List<KtCallableDeclaration>.mapToConstructorParameters(
   module: ModuleDescriptor
 ): List<ConstructorParameter> =
-  map { ktCallableDeclaration ->
-    ktCallableDeclaration.toConstructorParameter(module = module)
+  fold(listOf()) { acc, ktCallableDeclaration ->
+
+    val uniqueName = ktCallableDeclaration.nameAsSafeName.asString().uniqueParameterName(acc)
+
+    acc + ktCallableDeclaration.toConstructorParameter(module = module, uniqueName = uniqueName)
   }
 
 private fun KtCallableDeclaration.toConstructorParameter(
-  module: ModuleDescriptor
+  module: ModuleDescriptor,
+  uniqueName: String
 ): ConstructorParameter {
 
   val typeElement = typeReference?.typeElement
@@ -99,7 +103,8 @@ private fun KtCallableDeclaration.toConstructorParameter(
       ?: ""
 
   return ConstructorParameter(
-    name = nameAsSafeName.asString(),
+    name = uniqueName,
+    originalName = nameAsSafeName.asString(),
     typeName = typeName,
     providerTypeName = typeName.wrapInProvider(),
     lazyTypeName = typeName.wrapInLazy(),
@@ -112,20 +117,22 @@ private fun KtCallableDeclaration.toConstructorParameter(
 }
 
 internal fun List<KtProperty>.mapToMemberInjectParameters(
-  module: ModuleDescriptor
-): List<MemberInjectParameter> = map { ktProperty ->
-  ktProperty.toMemberInjectParameter(
-    module = module
-  )
+  module: ModuleDescriptor,
+  superParameters: List<Parameter>
+): List<MemberInjectParameter> = fold(listOf()) { acc, ktProperty ->
+
+  val uniqueName = ktProperty.nameAsSafeName.asString()
+    .uniqueParameterName(superParameters, acc)
+
+  acc + ktProperty.toMemberInjectParameter(module = module, uniqueName = uniqueName)
 }
 
 private fun KtProperty.toMemberInjectParameter(
-  module: ModuleDescriptor
+  module: ModuleDescriptor,
+  uniqueName: String
 ): MemberInjectParameter {
 
-  val constructorParameter = toConstructorParameter(module)
-
-  val originalName = nameAsSafeName.asString()
+  val constructorParameter = toConstructorParameter(module, uniqueName)
 
   val containingClass = containingClass()!!
   val packageName = containingClass.containingKtFile.packageFqName.asString()
@@ -136,6 +143,8 @@ private fun KtProperty.toMemberInjectParameter(
   val qualifierAnnotations = annotationEntries.qualifierAnnotationSpecs(module)
   val isSetterInjected = isSetterInjected(module)
 
+  val originalName = constructorParameter.originalName
+
   // setter delegates require a "set" prefix for their inject function
   val accessName = if (isSetterInjected) {
     "set${originalName.capitalize()}"
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/Parameter.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/Parameter.kt
index 1bbaee710..ba46e37dd 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/Parameter.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/Parameter.kt
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.name.FqName
 
 sealed interface Parameter {
   val name: String
+  val originalName: String
   val typeName: TypeName
   val providerTypeName: ParameterizedTypeName
   val lazyTypeName: ParameterizedTypeName
@@ -35,8 +36,35 @@ sealed interface Parameter {
     }
 }
 
+/**
+ * Returns a name which is unique when compared to the [Parameter.originalName] of the
+ * [superParameters] argument.
+ *
+ * This is necessary for member-injected parameters, because a subclass may override a parameter
+ * which is member-injected in the super.  The `MembersInjector` corresponding to the subclass must
+ * have unique constructor parameters for each declaration, so their names must be unique.
+ *
+ * This mimics Dagger's method of unique naming.  If there are three parameters named "foo", the
+ * unique parameter names will be [foo, foo2, foo3].
+ */
+internal fun String.uniqueParameterName(
+  vararg superParameters: List<Parameter>
+): String {
+
+  val numDuplicates = superParameters.sumOf { list ->
+    list.count { it.originalName == this }
+  }
+
+  return if (numDuplicates == 0) {
+    this
+  } else {
+    this + (numDuplicates + 1)
+  }
+}
+
 internal data class ConstructorParameter(
   override val name: String,
+  override val originalName: String,
   override val typeName: TypeName,
   override val providerTypeName: ParameterizedTypeName,
   override val lazyTypeName: ParameterizedTypeName,
@@ -53,6 +81,7 @@ internal data class ConstructorParameter(
 
 internal data class MemberInjectParameter(
   override val name: String,
+  override val originalName: String,
   override val typeName: TypeName,
   override val providerTypeName: ParameterizedTypeName,
   override val lazyTypeName: ParameterizedTypeName,
@@ -62,7 +91,6 @@ internal data class MemberInjectParameter(
   override val isAssisted: Boolean,
   override val assistedIdentifier: String,
   val memberInjectorClassName: ClassName,
-  val originalName: String,
   val isSetterInjected: Boolean,
   val accessName: String,
   val qualifierAnnotationSpecs: List<AnnotationSpec>,
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MembersInjectionUtils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MembersInjectionUtils.kt
index c2cc5c178..11f6bbfec 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MembersInjectionUtils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MembersInjectionUtils.kt
@@ -1,15 +1,20 @@
 package com.squareup.anvil.compiler.codegen.dagger
 
 import com.squareup.anvil.compiler.codegen.MemberInjectParameter
+import com.squareup.anvil.compiler.codegen.Parameter
 import com.squareup.anvil.compiler.codegen.mapToMemberInjectParameters
-import com.squareup.anvil.compiler.codegen.superClassMemberInjectedParameters
+import com.squareup.anvil.compiler.codegen.memberInjectedPropertyDescriptors
 import com.squareup.anvil.compiler.daggerDoubleCheckFqNameString
 import com.squareup.anvil.compiler.injectFqName
-import com.squareup.anvil.compiler.internal.allPsiSuperClasses
+import com.squareup.anvil.compiler.internal.ClassReference
+import com.squareup.anvil.compiler.internal.ClassReference.Descriptor
+import com.squareup.anvil.compiler.internal.ClassReference.Psi
+import com.squareup.anvil.compiler.internal.allSuperTypeClassReferences
+import com.squareup.anvil.compiler.internal.asClassName
 import com.squareup.anvil.compiler.internal.capitalize
-import com.squareup.anvil.compiler.internal.classDescriptorOrNull
 import com.squareup.anvil.compiler.internal.findAnnotation
 import com.squareup.anvil.compiler.internal.hasAnnotation
+import com.squareup.anvil.compiler.internal.toClassReference
 import com.squareup.kotlinpoet.FunSpec
 import dagger.internal.ProviderOfLazy
 import org.jetbrains.kotlin.descriptors.ModuleDescriptor
@@ -19,7 +24,6 @@ import org.jetbrains.kotlin.psi.KtClassBody
 import org.jetbrains.kotlin.psi.KtClassOrObject
 import org.jetbrains.kotlin.psi.KtProperty
 import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
-import org.jetbrains.kotlin.utils.addToStdlib.cast
 
 internal fun KtProperty.isSetterInjected(module: ModuleDescriptor): Boolean {
   return setter?.hasAnnotation(injectFqName, module) == true ||
@@ -80,31 +84,28 @@ internal fun KtClassOrObject.memberInjectParameters(
   module: ModuleDescriptor
 ): List<MemberInjectParameter> {
 
-  val psiSuperClasses = allPsiSuperClasses(module)
+  return toClassReference()
+    .allSuperTypeClassReferences(module, includeSelf = true)
+    .toList()
+    .foldRight(listOf()) { classReference, acc ->
 
-  // If a super class is defined in the same module, select that.
-  // Otherwise, select the current KtClassOrObject.
-  val mostSuperPsi = psiSuperClasses.lastOrNull() ?: this@memberInjectParameters
-
-  // Use the descriptor of the most-super PSI class in order to look for more super types in
-  // other modules.
-  val descriptorParams = mostSuperPsi
-    .classDescriptorOrNull(module)
-    ?.superClassMemberInjectedParameters(module)
-    .orEmpty()
-
-  // Everything PSI can tell us about super-class member-injected properties.
-  val allSuperclassParams = psiSuperClasses
-    .reversed()
-    .fold(descriptorParams) { acc, ktClassOrObject ->
-
-      acc + ktClassOrObject.injectedMembers(module)
-        .mapToMemberInjectParameters(module)
+      acc + classReference.declaredMemberInjectParameters(module, acc)
     }
+}
 
-  // Finally, the class itself.
-  val thisClassMemberInjectedParameters = injectedMembers(module)
-    .mapToMemberInjectParameters(module)
-
-  return allSuperclassParams.plus(thisClassMemberInjectedParameters).cast()
+/**
+ * @param superParameters injected parameters from any super-classes, regardless of whether they're
+ * overridden by the receiver class
+ * @return the member-injected parameters for this class only, not including any super-classes
+ */
+private fun ClassReference.declaredMemberInjectParameters(
+  module: ModuleDescriptor,
+  superParameters: List<Parameter>
+): List<MemberInjectParameter> {
+  return when (this) {
+    is Descriptor -> clazz.memberInjectedPropertyDescriptors()
+      .mapToMemberInjectParameters(module, clazz.asClassName(), superParameters)
+    is Psi -> clazz.injectedMembers(module)
+      .mapToMemberInjectParameters(module, superParameters)
+  }
 }
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MembersInjectorGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MembersInjectorGenerator.kt
index e2c8c3da6..eddbf1ca5 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MembersInjectorGenerator.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MembersInjectorGenerator.kt
@@ -141,7 +141,7 @@ internal class MembersInjectorGenerator : PrivateCodeGenerator() {
                   .filter { it.memberInjectorClassName == memberInjectorClass }
                   .forEach { parameter ->
 
-                    val originalName = parameter.originalName
+                    val name = parameter.name
 
                     addFunction(
                       FunSpec.builder("inject${parameter.accessName.capitalize()}")
@@ -158,8 +158,8 @@ internal class MembersInjectorGenerator : PrivateCodeGenerator() {
                         }
                         .addAnnotations(parameter.qualifierAnnotationSpecs)
                         .addParameter("instance", classType)
-                        .addParameter(originalName, parameter.originalTypeName)
-                        .addStatement("instance.$originalName = $originalName")
+                        .addParameter(name, parameter.originalTypeName)
+                        .addStatement("instance.${parameter.originalName} = $name")
                         .build()
                     )
                   }


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 23af827f8..a81c92ebf 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
@@ -1627,6 +1627,272 @@ public final class InjectClass_MembersInjector<T, U, V> implements MembersInject
     }
   }
 
+  @Test
+  fun `a member injector is generated for a class with two super classes in another module`() {
+
+    val otherModuleResult = compile(
+      """
+      package com.squareup.test
+
+      import javax.inject.Inject
+
+      abstract class Base1 {
+        @Inject lateinit var string: String
+      }
+
+      abstract class Base2 : Base1() {
+        @Inject lateinit var chars: List<Char>
+      }
+      """
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+    }
+
+    compile(
+      """
+      package com.squareup.test
+
+      import javax.inject.Inject
+
+      abstract class Mid : Base2() {
+        @Inject lateinit var strings: List<String>
+      }
+
+      class InjectClass : Mid() {
+        @Inject lateinit var numbers: List<Int>
+     
+        override fun equals(other: Any?): Boolean {
+          if (this === other) return true
+          if (javaClass != other?.javaClass) return false
+     
+          other as InjectClass
+     
+          if (numbers != other.numbers) return false
+          if (strings != other.strings) return false
+          if (string != other.string) return false
+     
+          return true
+        }
+      }
+      """,
+      previousCompilationResult = otherModuleResult
+    ) {
+
+      val base1MembersInjector = classLoader.loadClass("com.squareup.test.Base1")
+        .membersInjector()
+      val base2MembersInjector = classLoader.loadClass("com.squareup.test.Base2")
+        .membersInjector()
+      val midMembersInjector = classLoader.loadClass("com.squareup.test.Mid")
+        .membersInjector()
+
+      val injectClassMembersInjector = injectClass.membersInjector()
+
+      val constructor = injectClassMembersInjector.declaredConstructors.single()
+      assertThat(constructor.parameterTypes.toList())
+        .containsExactly(
+          Provider::class.java,
+          Provider::class.java,
+          Provider::class.java,
+          Provider::class.java
+        )
+
+      val membersInjectorInstance = constructor
+        .newInstance(
+          Provider { "a" },
+          Provider { listOf('b', 'c') },
+          Provider { listOf("d") },
+          Provider { listOf(1, 2) }
+        ) as MembersInjector<Any>
+
+      val injectInstanceConstructor = injectClass.createInstance()
+      membersInjectorInstance.injectMembers(injectInstanceConstructor)
+
+      val injectInstanceStatic = injectClass.createInstance()
+
+      injectClassMembersInjector.staticInjectMethod("numbers")
+        .invoke(null, injectInstanceStatic, listOf(1, 2))
+      midMembersInjector.staticInjectMethod("strings")
+        .invoke(null, injectInstanceStatic, listOf("d"))
+      base1MembersInjector.staticInjectMethod("string")
+        .invoke(null, injectInstanceStatic, "a")
+      base2MembersInjector.staticInjectMethod("chars")
+        .invoke(null, injectInstanceStatic, listOf('b', 'c'))
+
+      assertThat(injectInstanceConstructor).isEqualTo(injectInstanceStatic)
+      assertThat(injectInstanceConstructor).isNotSameInstanceAs(injectInstanceStatic)
+    }
+  }
+
+  @Test
+  fun `a member injector is generated for a class with two super classes with an overridden property in another module`() {
+
+    val otherModuleResult = compile(
+      """
+      package com.squareup.test
+
+      import javax.inject.Inject
+
+      abstract class Base1 {
+        @Inject open lateinit var string: String
+      }
+
+      abstract class Base2 : Base1() {
+        @Inject override lateinit var string: String
+      } 
+      """
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+    }
+
+    compile(
+      """
+      package com.squareup.test
+ 
+      import javax.inject.Inject
+
+      abstract class Mid : Base2() {
+        @Inject lateinit var strings: List<String>
+      }
+
+      class InjectClass : Mid() {
+        @Inject lateinit var numbers: List<Int>
+     
+        override fun equals(other: Any?): Boolean {
+          if (this === other) return true
+          if (javaClass != other?.javaClass) return false
+     
+          other as InjectClass
+     
+          if (numbers != other.numbers) return false
+          if (strings != other.strings) return false
+          if (string != other.string) return false
+     
+          return true
+        }
+      }
+      """,
+      previousCompilationResult = otherModuleResult
+    ) {
+
+      val base1MembersInjector = classLoader.loadClass("com.squareup.test.Base1")
+        .membersInjector()
+      val base2MembersInjector = classLoader.loadClass("com.squareup.test.Base2")
+        .membersInjector()
+      val midMembersInjector = classLoader.loadClass("com.squareup.test.Mid")
+        .membersInjector()
+
+      val injectClassMembersInjector = injectClass.membersInjector()
+
+      val constructor = injectClassMembersInjector.declaredConstructors.single()
+      assertThat(constructor.parameterTypes.toList())
+        .containsExactly(
+          Provider::class.java,
+          Provider::class.java,
+          Provider::class.java,
+          Provider::class.java
+        )
+
+      val membersInjectorInstance = constructor
+        .newInstance(
+          Provider { "a" },
+          Provider { "b" },
+          Provider { listOf("b") },
+          Provider { listOf(1, 2) }
+        ) as MembersInjector<Any>
+
+      val injectInstanceConstructor = injectClass.createInstance()
+      membersInjectorInstance.injectMembers(injectInstanceConstructor)
+
+      val injectInstanceStatic = injectClass.createInstance()
+
+      injectClassMembersInjector.staticInjectMethod("numbers")
+        .invoke(null, injectInstanceStatic, listOf(1, 2))
+      midMembersInjector.staticInjectMethod("strings")
+        .invoke(null, injectInstanceStatic, listOf("b"))
+      base1MembersInjector.staticInjectMethod("string")
+        .invoke(null, injectInstanceStatic, "a")
+      base2MembersInjector.staticInjectMethod("string")
+        .invoke(null, injectInstanceStatic, "b")
+
+      assertThat(injectInstanceConstructor).isEqualTo(injectInstanceStatic)
+      assertThat(injectInstanceConstructor).isNotSameInstanceAs(injectInstanceStatic)
+    }
+  }
+
+  @Test
+  fun `a member injector is generated for a class with an overridden property in the same module`() {
+
+    compile(
+      """
+      package com.squareup.test
+
+      import javax.inject.Inject
+
+      abstract class Base {
+        @Inject open lateinit var string: String
+      }
+
+      abstract class Mid : Base() {
+        @Inject override lateinit var string: String
+      }
+
+      class InjectClass : Mid() {
+        @Inject lateinit var numbers: List<Int>
+     
+        override fun equals(other: Any?): Boolean {
+          if (this === other) return true
+          if (javaClass != other?.javaClass) return false
+     
+          other as InjectClass
+
+          if (string != other.string) return false
+          if (numbers != other.numbers) return false
+     
+          return true
+        }
+      }
+      """
+    ) {
+
+      val baseMembersInjector = classLoader.loadClass("com.squareup.test.Base")
+        .membersInjector()
+      val midMembersInjector = classLoader.loadClass("com.squareup.test.Mid")
+        .membersInjector()
+
+      val injectClassMembersInjector = injectClass.membersInjector()
+
+      val constructor = injectClassMembersInjector.declaredConstructors.single()
+      assertThat(constructor.parameterTypes.toList())
+        .containsExactly(
+          Provider::class.java,
+          Provider::class.java,
+          Provider::class.java
+        )
+
+      val membersInjectorInstance = constructor
+        .newInstance(
+          Provider { "a" },
+          Provider { "b" },
+          Provider { listOf(1, 2) }
+        ) as MembersInjector<Any>
+
+      val injectInstanceConstructor = injectClass.createInstance()
+      membersInjectorInstance.injectMembers(injectInstanceConstructor)
+
+      val injectInstanceStatic = injectClass.createInstance()
+
+      baseMembersInjector.staticInjectMethod("string")
+        .invoke(null, injectInstanceStatic, "a")
+      midMembersInjector.staticInjectMethod("string")
+        .invoke(null, injectInstanceStatic, "b")
+      injectClassMembersInjector.staticInjectMethod("numbers")
+        .invoke(null, injectInstanceStatic, listOf(1, 2))
+
+      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.