Repository: square/anvil (kotlin)

Issue:
MissingBinding: Subcomponent cannot be provided without an @Provides-annotated method
Anvil version: 2.3.11-1-6-10

I have a common set of subcomponent hierarchies: _FirstScope_ and _SecondScope_. They are connected to different components.
At the moment, I began to receive the following compilation error:

```
/Users/trip/Documents/Projects/anvil-exxt/anvil/sample-app/build/tmp/kapt3/stubs/debug/experiment1/GroundAppComponent.java:9: error: [Dagger/MissingBinding] experiment1.FirstSubcomponent cannot be provided without an @Provides-annotated method.
public abstract interface GroundAppComponent extends experiment1.ZeroSubcomponent.ParentComponent, anvil.component.experiment1.groundappcomponent.ZeroSubcomponentA.ParentComponent {
                ^
      experiment1.FirstSubcomponent is requested at
          experiment1.FirstSubcomponent.ParentComponent.firstSubcomponent() [experiment1.GroundAppComponent → anvil.component.experiment1.groundappcomponent.ZeroSubcomponentA]
```

Full code listing leading to the error:
```
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class SingleIn(val scope: KClass<*>)

abstract class ZeroScope private constructor()
abstract class FirstScope private constructor()
abstract class SecondScope private constructor()

@ContributesSubcomponent(
    scope = FirstScope::class,
    parentScope = ZeroScope::class
)
@SingleIn(FirstScope::class)
interface FirstSubcomponent {

    @ContributesTo(ZeroScope::class)
    interface ParentComponent {
        fun firstSubcomponent(): FirstSubcomponent
    }
}

@ContributesSubcomponent(
    scope = SecondScope::class,
    parentScope = FirstScope::class
)
@SingleIn(SecondScope::class)
interface SecondSubcomponent {

    @ContributesTo(FirstScope::class)
    interface ParentComponent {
        fun secondSubcomponent(): SecondSubcomponent
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

@MergeComponent(ZeroScope::class)
@SingleIn(ZeroScope::class)
interface ZeroAppComponent {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance app: Application): ZeroAppComponent
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

abstract class GroundScope private constructor()

@MergeComponent(GroundScope::class)
@SingleIn(GroundScope::class)
interface GroundAppComponent {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance app: Application): GroundAppComponent
    }
}

@ContributesSubcomponent(
    scope = ZeroScope::class,
    parentScope = GroundScope::class
)
@SingleIn(ZeroScope::class)
interface ZeroSubcomponent {

    @ContributesTo(GroundScope::class)
    interface ParentComponent {
        fun zeroSubcomponent(): ZeroSubcomponent
    }
}
```

Pull request description:
Before, the code relied on the fact that the trigger was always found before the contributed subcomponent.

Fixes #478

Patch:
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt
index 2f70523ac..023b59ff7 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt
@@ -93,7 +93,7 @@ internal class ContributesSubcomponentHandlerGenerator(
   private val triggers = mutableListOf<Trigger>()
   private val contributions = mutableSetOf<Contribution>()
   private val replacedReferences = mutableSetOf<ClassReference>()
-  private val generatedContributions = mutableSetOf<Contribution>()
+  private val processedEvents = mutableSetOf<GenerateCodeEvent>()
 
   private var isFirstRound = true
 
@@ -167,7 +167,7 @@ internal class ContributesSubcomponentHandlerGenerator(
       contribution.classReference in replacedReferences
     }
 
-    val newContributions = contributions
+    return contributions
       .flatMap { contribution ->
         triggers
           .filter { trigger ->
@@ -178,13 +178,11 @@ internal class ContributesSubcomponentHandlerGenerator(
             GenerateCodeEvent(trigger, contribution)
           }
       }
+      // Don't generate code for the same event twice.
+      .minus(processedEvents)
       .also {
-        generatedContributions += it.map { generateCodeEvent -> generateCodeEvent.contribution }
+        processedEvents += it
       }
-
-    contributions -= newContributions.mapTo(mutableSetOf()) { it.contribution }
-
-    return newContributions
       .map { generateCodeEvent ->
         val contribution = generateCodeEvent.contribution
         val generatedAnvilSubcomponent = generateCodeEvent.generatedAnvilSubcomponent
@@ -432,7 +430,7 @@ internal class ContributesSubcomponentHandlerGenerator(
     replacedReferences: List<ClassReference>
   ) {
     replacedReferences.forEach { replacedReference ->
-      if (generatedContributions.any { it.classReference == replacedReference }) {
+      if (processedEvents.any { it.contribution.classReference == replacedReference }) {
         throw AnvilCompilationExceptionClassReference(
           classReference = contributedReference,
           message = "${contributedReference.fqName} tries to replace " +


Test patch:
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGeneratorTest.kt
index 4f64fe31c..48665b281 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGeneratorTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGeneratorTest.kt
@@ -673,6 +673,67 @@ class ContributesSubcomponentHandlerGeneratorTest {
     }
   }
 
+  @Test
+  fun `the parent interface of a contributed subcomponent is picked up by components and other contributed subcomponents`() {
+    compile(
+      """
+        package com.squareup.test
+  
+        import com.squareup.anvil.annotations.ContributesSubcomponent
+        import com.squareup.anvil.annotations.ContributesTo
+        import com.squareup.anvil.annotations.MergeComponent
+        import dagger.Module
+        import dagger.Provides
+
+        @ContributesSubcomponent(Any::class, parentScope = Unit::class)
+        interface SubcomponentInterface1 {
+          @ContributesTo(Unit::class)
+          interface AnyParentComponent {
+            fun createComponent(): SubcomponentInterface1
+          }
+        }
+
+        @ContributesSubcomponent(Unit::class, parentScope = Int::class)
+        interface SubcomponentInterface2 {
+          @ContributesTo(Int::class)
+          interface AnyParentComponent {
+            fun createComponent(): SubcomponentInterface2
+          }
+        }
+        
+        @MergeComponent(Unit::class)
+        interface ComponentInterface1
+        
+        @MergeComponent(Int::class)
+        interface ComponentInterface2
+      """,
+      // Keep Dagger enabled, because it complained initially.
+      enableDaggerAnnotationProcessor = true
+    ) {
+      assertThat(componentInterface1 extends subcomponentInterface1.anyParentComponentInterface)
+      assertThat(
+        componentInterface1 extends
+          subcomponentInterface1.anvilComponent(componentInterface1).parentComponentInterface
+      )
+
+      assertThat(componentInterface2 extends subcomponentInterface2.anyParentComponentInterface)
+      assertThat(
+        componentInterface2 extends
+          subcomponentInterface2.anvilComponent(componentInterface2).parentComponentInterface
+      )
+
+      // Note that NOT subcomponentInterface2 extends these parent component interface, but its
+      // generated @MergeSubcomponent extends them.
+      assertThat(subcomponentInterface2 extends subcomponentInterface1.anyParentComponentInterface)
+      assertThat(
+        subcomponentInterface2.anvilComponent(componentInterface2) extends
+          subcomponentInterface1.anvilComponent(
+            subcomponentInterface2.anvilComponent(componentInterface2)
+          ).parentComponentInterface
+      )
+    }
+  }
+
   @Test fun `contributed subcomponents can be excluded with @MergeComponent`() {
     compile(
       """