Repository: square/anvil
Issue: ContributesSubcomponent issue in testing

Hey, I love the `ContributesSubcomponent` feature, now I can move the component definition to a library, and remove some BS abstractions.

Bit of context, I have a monorepo with 3 apps. Each app uses the same scopes, however each app is not feature identical.
When creating integration tests with real dagger component, I had created TestAppComponent, TestUserComponent, in each app, with excluded modules which depend on android and replaced them with fakes

However, now as I moved to ContributesSubcomponent tests fail to compile with

```
com.squareup.anvil.compiler.api.AnvilCompilationException: Back-end (JVM) Internal error: There are multiple contributed bindings with the same bound type. The bound type is sk.o2.radost.auth.di.UserComponent.Factory. The contributed binding classes are: [anvil.component.sk.o2.radost.testcomponent.testappcomponent.UserComponentA.SubcomponentFactory, anvil.component.sk.o2.radost.di.appcomponent.UserComponentA.SubcomponentFactory]
File is unknown

	at com.squareup.anvil.compiler.codegen.BindingModuleGeneratorKt.findHighestPriorityBinding(BindingModuleGenerator.kt:375)
	at com.squareup.anvil.compiler.codegen.BindingModuleGeneratorKt.access$findHighestPriorityBinding(BindingModuleGenerator.kt:1)
	at com.squareup.anvil.compiler.codegen.BindingModuleGenerator.flush$lambda-13$getContributedBindingClasses(BindingModuleGenerator.kt:248)
	at com.squareup.anvil.compiler.codegen.BindingModuleGenerator.flush(BindingModuleGenerator.kt:256)
	at com.squareup.anvil.compiler.codegen.CodeGenerationExtension.analysisCompleted$flush(CodeGenerationExtension.kt:99)
	at com.squareup.anvil.compiler.codegen.CodeGenerationExtension.analysisCompleted(CodeGenerationExtension.kt:110)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:112)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:122)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:86)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:252)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:243)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:113)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:243)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:90)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:56)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:169)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:92)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1574)
	at jdk.internal.reflect.GeneratedMethodAccessor105.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
```

Real 

```
@UserScope
@OptIn(ExperimentalAnvilApi::class)
@ContributesSubcomponent(
    scope = UserScope::class,
    parentScope = AppScope::class
)
interface UserComponent {

    @ContributesTo(AppScope::class)
    interface ParentComponent {
        val userComponentFactory: Factory
    }

    @ContributesSubcomponent.Factory
    interface Factory {
        fun create(): UserComponent
    }
}
```

The test component definition is as such

```
@UserScope
@MergeSubcomponent(UserScope::class)
interface TestUserComponent {

    @ContributesTo(AppScope::class, replaces = [UserComponent.ParentComponent::class])
    interface ParentComponent {
        val testUserComponentFactory: Factory
    }

    @Subcomponent.Factory
    interface Factory {
        fun create(): TestUserComponent
    }
}

@Module
@ContributesTo(
    scope = UserScope::class,
    replaces = [
        UserImageSaverModule::class
    ]
)
object FakeImageSaverModule {
    @UserScope
    @JvmStatic
    @Provides
    fun imageSaver(): ImageSaver {
        return FakeImageSaver()
    }
}
```

This used to work because I removed the ParentComponent which attached the Factory to the real UserComponent onto AppComponent.

Now there seems the plugin does some more work and the factory to the real thing is there anyways, and it should not

// I noticed your tweet https://twitter.com/vRallev/status/1463627818730864642 but unsure how to do that. I see the exclude parameter on the annotation, however I dont know what to exclude at library level, as the user module doesnt know all the feature it will have in the final app

If there is more idiomatic way, I'm all ears

Changed files:
compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerIr.kt
compiler/src/main/java/com/squareup/anvil/compiler/IrUtils.kt
compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt
compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
compiler/src/main/java/com/squareup/anvil/compiler/ModuleMergerIr.kt
compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt
compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGeneratorTest.kt
compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt

diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerIr.kt b/compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerIr.kt
index 677df3c..0758e62 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerIr.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerIr.kt
@@ -15,7 +15,7 @@ internal fun ClassScanner.findContributedClasses(
   moduleFragment: IrModuleFragment,
   packageName: String,
   annotation: FqName,
-  scope: FqName
+  scope: FqName?
 ): Sequence<IrClassSymbol> {
   return findContributedClasses(moduleFragment.descriptor, packageName, annotation, scope)
     .map {
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/IrUtils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/IrUtils.kt
index d3e5e69..ee24ce6 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/IrUtils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/IrUtils.kt
@@ -17,9 +17,11 @@ import org.jetbrains.kotlin.ir.types.IrType
 import org.jetbrains.kotlin.ir.types.classifierOrNull
 import org.jetbrains.kotlin.ir.types.getClass
 import org.jetbrains.kotlin.ir.types.typeOrNull
+import org.jetbrains.kotlin.ir.util.classId
 import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
 import org.jetbrains.kotlin.ir.util.getAnnotation
 import org.jetbrains.kotlin.ir.util.getArgumentsWithIr
+import org.jetbrains.kotlin.name.ClassId
 import org.jetbrains.kotlin.name.FqName
 
 internal fun IrPluginContext.requireReferenceClass(fqName: FqName): IrClassSymbol {
@@ -28,6 +30,21 @@ internal fun IrPluginContext.requireReferenceClass(fqName: FqName): IrClassSymbo
   )
 }
 
+internal fun ClassId.irClass(context: IrPluginContext): IrClassSymbol =
+  context.requireReferenceClass(asSingleFqName())
+
+internal fun ClassId.irClassOrNull(context: IrPluginContext): IrClassSymbol? =
+  context.referenceClass(asSingleFqName())
+
+internal fun IrClass.requireClassId(): ClassId {
+  return classId ?: throw AnvilCompilationException(
+    element = this,
+    message = "Couldn't find a ClassId for $fqName."
+  )
+}
+
+internal fun IrClassSymbol.requireClassId(): ClassId = owner.requireClassId()
+
 internal fun IrAnnotationContainer.annotationOrNull(
   annotationFqName: FqName,
   scope: FqName? = null
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt b/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt
index 678e3e5..964bc20 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt
@@ -5,12 +5,15 @@ import com.squareup.anvil.annotations.MergeComponent
 import com.squareup.anvil.annotations.MergeSubcomponent
 import com.squareup.anvil.annotations.compat.MergeModules
 import com.squareup.anvil.compiler.api.AnvilCompilationException
+import com.squareup.anvil.compiler.codegen.generatedAnvilSubcomponent
 import com.squareup.anvil.compiler.internal.annotation
 import com.squareup.anvil.compiler.internal.annotationOrNull
 import com.squareup.anvil.compiler.internal.argumentType
+import com.squareup.anvil.compiler.internal.classDescriptorOrNull
 import com.squareup.anvil.compiler.internal.getAnnotationValue
 import com.squareup.anvil.compiler.internal.parentScope
 import com.squareup.anvil.compiler.internal.requireClassDescriptor
+import com.squareup.anvil.compiler.internal.requireClassId
 import com.squareup.anvil.compiler.internal.safePackageString
 import com.squareup.anvil.compiler.internal.scope
 import com.squareup.anvil.compiler.internal.toType
@@ -27,6 +30,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
 import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
 import org.jetbrains.kotlin.descriptors.effectiveVisibility
 import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.resolve.DescriptorUtils
 import org.jetbrains.kotlin.resolve.constants.ArrayValue
 import org.jetbrains.kotlin.resolve.constants.ConstantValue
@@ -47,18 +51,19 @@ internal class ModuleMerger(
 ) : ExpressionCodegenExtension {
 
   override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
-    val holder = AnnotationHolder.create(codegen.descriptor) ?: return
+    val thisDescriptor = codegen.descriptor
+    val holder = AnnotationHolder.create(thisDescriptor) ?: return
     val (mergeAnnotation, annotationClass, daggerClass, daggerFqName, modulesKeyword) = holder
 
-    if (codegen.descriptor.annotationOrNull(daggerFqName) != null) {
+    if (thisDescriptor.annotationOrNull(daggerFqName) != null) {
       throw AnvilCompilationException(
-        codegen.descriptor,
+        thisDescriptor,
         "When using @${annotationClass.simpleName} it's not allowed to annotate the same " +
           "class with @${daggerClass.simpleName}. The Dagger annotation will be generated."
       )
     }
 
-    val module = codegen.descriptor.module
+    val module = thisDescriptor.module
     val scope = mergeAnnotation.scope(module)
     val scopeFqName = scope.fqNameSafe
 
@@ -67,7 +72,7 @@ internal class ModuleMerger(
         ?.value
         ?.map { it.toType(codegen) }
 
-    val anvilModuleName = createAnvilModuleName(codegen.descriptor)
+    val anvilModuleName = createAnvilModuleName(thisDescriptor)
 
     val modules = classScanner
       .findContributedClasses(
@@ -144,15 +149,15 @@ internal class ModuleMerger(
           ?: contributesMultibindingAnnotation?.scope(module)
           ?: contributesSubcomponentAnnotation?.parentScope(module)
           ?: throw AnvilCompilationException(
-            codegen.descriptor,
+            thisDescriptor,
             "Could not determine the scope of the excluded class " +
               "${classDescriptorForExclusion.fqNameSafe}."
           )
 
         if (scopeOfExclusion.fqNameSafe != scopeFqName) {
           throw AnvilCompilationException(
-            codegen.descriptor,
-            "${codegen.descriptor.fqNameSafe} with scope $scopeFqName wants to exclude " +
+            thisDescriptor,
+            "${thisDescriptor.fqNameSafe} with scope $scopeFqName wants to exclude " +
               "${classDescriptorForExclusion.fqNameSafe} with scope " +
               "${scopeOfExclusion.fqNameSafe}. The exclusion must use the same scope."
           )
@@ -234,13 +239,17 @@ internal class ModuleMerger(
       val intersect = predefinedModules.intersect(excludedModules.toSet())
       if (intersect.isNotEmpty()) {
         throw AnvilCompilationException(
-          codegen.descriptor,
-          "${codegen.descriptor.name} includes and excludes modules " +
+          thisDescriptor,
+          "${thisDescriptor.name} includes and excludes modules " +
             "at the same time: ${intersect.joinToString { it.className }}"
         )
       }
     }
 
+    val contributedSubcomponentModules =
+      findContributedSubcomponentModules(thisDescriptor, scopeFqName, module)
+        .map { codegen.typeMapper.mapType(it) }
+
     val contributedModules = modules
       .asSequence()
       .map { codegen.typeMapper.mapType(it.first) }
@@ -248,6 +257,7 @@ internal class ModuleMerger(
       .minus(replacedModulesByContributedBindings.toSet())
       .minus(replacedModulesByContributedMultibindings.toSet())
       .minus(excludedModules.toSet())
+      .plus(contributedSubcomponentModules)
       .distinct()
 
     codegen.v
@@ -340,6 +350,29 @@ internal class ModuleMerger(
       )
     }
   }
+
+  private fun findContributedSubcomponentModules(
+    descriptor: ClassDescriptor,
+    scope: FqName,
+    module: ModuleDescriptor
+  ): Sequence<ClassDescriptor> {
+    return classScanner
+      .findContributedClasses(
+        module = module,
+        packageName = HINT_SUBCOMPONENTS_PACKAGE_PREFIX,
+        annotation = contributesSubcomponentFqName,
+        scope = null
+      )
+      .filter {
+        it.annotation(contributesSubcomponentFqName).parentScope(module).fqNameSafe == scope
+      }
+      .mapNotNull { contributedSubcomponent ->
+        contributedSubcomponent.requireClassId()
+          .generatedAnvilSubcomponent(descriptor.requireClassId())
+          .createNestedClassId(Name.identifier(SUBCOMPONENT_MODULE))
+          .classDescriptorOrNull(module)
+      }
+  }
 }
 
 @Suppress("DataClassPrivateConstructor")
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMergerIr.kt b/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMergerIr.kt
index f57d971..56e1d5a 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMergerIr.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMergerIr.kt
@@ -2,6 +2,7 @@ package com.squareup.anvil.compiler
 
 import com.squareup.anvil.annotations.ContributesTo
 import com.squareup.anvil.compiler.api.AnvilCompilationException
+import com.squareup.anvil.compiler.codegen.generatedAnvilSubcomponent
 import com.squareup.anvil.compiler.internal.safePackageString
 import dagger.Module
 import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
@@ -24,6 +25,7 @@ import org.jetbrains.kotlin.ir.util.isInterface
 import org.jetbrains.kotlin.ir.util.packageFqName
 import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
 import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.types.Variance.INVARIANT
 
 internal class ModuleMergerIr(
@@ -223,6 +225,9 @@ internal class ModuleMergerIr(
       }
     }
 
+    val contributedSubcomponentModules =
+      findContributedSubcomponentModules(declaration, scope, pluginContext, moduleFragment)
+
     val contributedModules = modules
       .asSequence()
       .map { it.first.owner }
@@ -231,6 +236,7 @@ internal class ModuleMergerIr(
       .minus(replacedModulesByContributedMultibindings.toSet())
       .minus(excludedModules.toSet())
       .plus(predefinedModules)
+      .plus(contributedSubcomponentModules)
       .distinct()
 
     val annotationConstructorCall = IrConstructorCallImpl
@@ -330,6 +336,32 @@ internal class ModuleMergerIr(
       )
     }
   }
+
+  private fun findContributedSubcomponentModules(
+    declaration: IrClass,
+    scope: FqName,
+    pluginContext: IrPluginContext,
+    moduleFragment: IrModuleFragment
+  ): Sequence<IrClass> {
+    return classScanner
+      .findContributedClasses(
+        pluginContext = pluginContext,
+        moduleFragment = moduleFragment,
+        packageName = HINT_SUBCOMPONENTS_PACKAGE_PREFIX,
+        annotation = contributesSubcomponentFqName,
+        scope = null
+      )
+      .filter {
+        it.owner.annotation(contributesSubcomponentFqName).parentScope() == scope
+      }
+      .mapNotNull { contributedSubcomponent ->
+        contributedSubcomponent.requireClassId()
+          .generatedAnvilSubcomponent(declaration.requireClassId())
+          .createNestedClassId(Name.identifier(SUBCOMPONENT_MODULE))
+          .irClassOrNull(pluginContext)
+          ?.owner
+      }
+  }
 }
 
 @Suppress("DataClassPrivateConstructor")
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 0fc2b28..53f608c 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
@@ -76,6 +76,7 @@ internal const val ANVIL_MODULE_SUFFIX = "AnvilModule"
 internal const val ANVIL_SUBCOMPONENT_SUFFIX = "A"
 internal const val PARENT_COMPONENT = "ParentComponent"
 internal const val SUBCOMPONENT_FACTORY = "SubcomponentFactory"
+internal const val SUBCOMPONENT_MODULE = "SubcomponentModule"
 
 internal const val REFERENCE_SUFFIX = "_reference"
 internal const val SCOPE_SUFFIX = "_scope"
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt
index 7a0702d..f11457c 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt
@@ -7,6 +7,7 @@ import com.squareup.anvil.compiler.HINT_BINDING_PACKAGE_PREFIX
 import com.squareup.anvil.compiler.HINT_CONTRIBUTES_PACKAGE_PREFIX
 import com.squareup.anvil.compiler.HINT_MULTIBINDING_PACKAGE_PREFIX
 import com.squareup.anvil.compiler.MODULE_PACKAGE_PREFIX
+import com.squareup.anvil.compiler.REFERENCE_SUFFIX
 import com.squareup.anvil.compiler.api.AnvilCompilationException
 import com.squareup.anvil.compiler.api.AnvilContext
 import com.squareup.anvil.compiler.api.GeneratedFile
@@ -290,6 +291,9 @@ internal class BindingModuleGenerator(
         it.findChildrenByClass(KtProperty::class.java).toList()
       }
       .mapNotNull { ktProperty ->
+        // We can safely ignore scopes, we only care about the reference classes.
+        if (ktProperty.name?.endsWith(REFERENCE_SUFFIX) != true) return@mapNotNull null
+
         (ktProperty.initializer as? KtClassLiteralExpression)
           ?.firstChild
           ?.requireFqName(module)
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 e1464c1..8f79f88 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
@@ -1,6 +1,5 @@
 package com.squareup.anvil.compiler.codegen
 
-import com.squareup.anvil.annotations.ContributesBinding
 import com.squareup.anvil.annotations.MergeSubcomponent
 import com.squareup.anvil.compiler.ANVIL_SUBCOMPONENT_SUFFIX
 import com.squareup.anvil.compiler.COMPONENT_PACKAGE_PREFIX
@@ -8,6 +7,7 @@ import com.squareup.anvil.compiler.ClassScanner
 import com.squareup.anvil.compiler.HINT_SUBCOMPONENTS_PACKAGE_PREFIX
 import com.squareup.anvil.compiler.PARENT_COMPONENT
 import com.squareup.anvil.compiler.SUBCOMPONENT_FACTORY
+import com.squareup.anvil.compiler.SUBCOMPONENT_MODULE
 import com.squareup.anvil.compiler.api.AnvilCompilationException
 import com.squareup.anvil.compiler.api.AnvilContext
 import com.squareup.anvil.compiler.api.CodeGenerator
@@ -45,6 +45,8 @@ import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier.ABSTRACT
 import com.squareup.kotlinpoet.KModifier.OVERRIDE
 import com.squareup.kotlinpoet.TypeSpec
+import dagger.Binds
+import dagger.Module
 import dagger.Subcomponent
 import org.jetbrains.kotlin.descriptors.ClassDescriptor
 import org.jetbrains.kotlin.descriptors.DescriptorVisibilities.PUBLIC
@@ -235,7 +237,8 @@ internal class ContributesSubcomponentHandlerGenerator(
             .addAnnotations(contribution.classReference.daggerScopes(module))
             .apply {
               if (factoryClass != null) {
-                addType(generateFactory(factoryClass.originalDescriptor, contribution, module))
+                addType(generateFactory(factoryClass.originalDescriptor))
+                addType(generateDaggerModule(factoryClass.originalDescriptor))
               }
             }
             .addType(
@@ -258,9 +261,7 @@ internal class ContributesSubcomponentHandlerGenerator(
   }
 
   private fun generateFactory(
-    factoryDescriptor: ClassDescriptor,
-    contribution: Contribution,
-    module: ModuleDescriptor
+    factoryDescriptor: ClassDescriptor
   ): TypeSpec {
     val superclass = factoryDescriptor.asClassName()
 
@@ -277,12 +278,24 @@ internal class ContributesSubcomponentHandlerGenerator(
 
     return builder
       .addAnnotation(Subcomponent.Factory::class)
-      .addAnnotation(
-        // This will allow injecting the factory instance.
-        AnnotationSpec
-          .builder(ContributesBinding::class)
-          .addMember("scope = %T::class", contribution.parentScope.asClassName(module))
-          .addMember("boundType = %T::class", superclass)
+      .build()
+  }
+
+  private fun generateDaggerModule(
+    factoryDescriptor: ClassDescriptor
+  ): TypeSpec {
+    // This Dagger module will allow injecting the factory instance.
+    return TypeSpec
+      .classBuilder(SUBCOMPONENT_MODULE)
+      .addModifiers(ABSTRACT)
+      .addAnnotation(Module::class)
+      .addFunction(
+        FunSpec
+          .builder("bindSubcomponentFactory")
+          .addAnnotation(Binds::class.java)
+          .addModifiers(ABSTRACT)
+          .addParameter("factory", ClassName.bestGuess(SUBCOMPONENT_FACTORY))
+          .returns(factoryDescriptor.asClassName())
           .build()
       )
       .build()
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 3d8ee83..6994be5 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
@@ -6,6 +6,7 @@ import com.squareup.anvil.compiler.ANVIL_SUBCOMPONENT_SUFFIX
 import com.squareup.anvil.compiler.COMPONENT_PACKAGE_PREFIX
 import com.squareup.anvil.compiler.PARENT_COMPONENT
 import com.squareup.anvil.compiler.SUBCOMPONENT_FACTORY
+import com.squareup.anvil.compiler.SUBCOMPONENT_MODULE
 import com.squareup.anvil.compiler.compile
 import com.squareup.anvil.compiler.componentInterface
 import com.squareup.anvil.compiler.contributingInterface
@@ -18,6 +19,7 @@ import com.squareup.anvil.compiler.secondContributingInterface
 import com.squareup.anvil.compiler.subcomponentInterface
 import com.tschuchort.compiletesting.KotlinCompilation.ExitCode.OK
 import com.tschuchort.compiletesting.KotlinCompilation.Result
+import dagger.Component
 import org.junit.Test
 import javax.inject.Singleton
 import kotlin.test.assertFailsWith
@@ -1048,6 +1050,268 @@ class ContributesSubcomponentHandlerGeneratorTest {
     }
   }
 
+  @Test
+  fun `the generated factory can be injected in a nested subcomponent`() {
+    compile(
+      """
+        package com.squareup.test
+  
+        import com.squareup.anvil.annotations.ContributesSubcomponent
+        import com.squareup.anvil.annotations.ContributesSubcomponent.Factory
+        import com.squareup.anvil.annotations.MergeComponent
+        import dagger.BindsInstance
+        import javax.inject.Inject 
+
+        @ContributesSubcomponent(Any::class, parentScope = Unit::class)
+        interface SubcomponentInterface {
+          @Factory
+          interface ComponentFactory {
+            fun createComponent(
+              @BindsInstance integer: Int
+            ): SubcomponentInterface
+          }
+          
+          fun integer(): Int
+        } 
+
+        @ContributesSubcomponent(Unit::class, parentScope = Int::class)
+        interface ComponentInterface1 {
+          fun testClass(): TestClass
+        }
+        
+        @MergeComponent(Int::class)
+        interface ComponentInterface2
+
+        class TestClass @Inject constructor(val factory: SubcomponentInterface.ComponentFactory)
+      """,
+      enableDaggerAnnotationProcessor = true
+    ) {
+      val daggerComponent = componentInterface2.daggerComponent.declaredMethods
+        .single { it.name == "create" }
+        .invoke(null)
+
+      val subcomponent1 = componentInterface2.methods
+        .single()
+        .invoke(daggerComponent)
+
+      val testClassInstance = componentInterface1.declaredMethods
+        .single { it.name == "testClass" }
+        .invoke(subcomponent1)
+
+      val factory = testClassInstance::class.java.declaredMethods
+        .single { it.name == "getFactory" }
+        .invoke(testClassInstance)
+
+      val subcomponent2 = factory::class.java.declaredMethods
+        .single { it.returnType == subcomponentInterface }
+        .use { it.invoke(factory, 5) }
+
+      val int = subcomponent2::class.java.declaredMethods
+        .single { it.name == "integer" }
+        .use { it.invoke(subcomponent2) as Int }
+
+      assertThat(int).isEqualTo(5)
+    }
+  }
+
+  @Test
+  fun `the generated factory can be injected with multiple compilations`() {
+    val firstCompilationResult = compile(
+      """
+        package com.squareup.test
+  
+        import com.squareup.anvil.annotations.ContributesSubcomponent
+        import com.squareup.anvil.annotations.ContributesSubcomponent.Factory
+        import com.squareup.anvil.annotations.ContributesTo
+        import com.squareup.anvil.annotations.MergeComponent
+        import dagger.BindsInstance
+
+        @ContributesSubcomponent(
+          scope = Any::class, 
+          parentScope = Unit::class
+        )
+        interface SubcomponentInterface {
+          fun integer(): Int
+
+          @Factory
+          interface ComponentFactory {
+            fun createComponent(@BindsInstance integer: Int): SubcomponentInterface
+          }
+
+          @ContributesTo(Unit::class)
+          interface AnyParentComponent {
+            fun createFactory(): ComponentFactory
+          }
+        }
+
+        @MergeComponent(Unit::class)
+        interface ComponentInterface1
+      """.trimIndent(),
+      enableDaggerAnnotationProcessor = true
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+
+      assertThat(
+        componentInterface1 extends subcomponentInterface.anyParentComponentInterface
+      ).isTrue()
+    }
+
+    compile(
+      """
+        package com.squareup.test
+  
+        import com.squareup.anvil.annotations.MergeComponent
+
+        @MergeComponent(Unit::class)
+        interface ComponentInterface2
+      """.trimIndent(),
+      previousCompilationResult = firstCompilationResult,
+      enableDaggerAnnotationProcessor = true
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+
+      val daggerComponent = componentInterface2.daggerComponent.declaredMethods
+        .single { it.name == "create" }
+        .invoke(null)
+
+      val factory = componentInterface2.methods
+        .first { it.name == "createFactory" }
+        .invoke(daggerComponent)
+
+      val subcomponent = factory::class.java.declaredMethods
+        .single { it.returnType == subcomponentInterface }
+        .use { it.invoke(factory, 5) }
+
+      val int = subcomponent::class.java.declaredMethods
+        .single { it.name == "integer" }
+        .use { it.invoke(subcomponent) as Int }
+
+      assertThat(int).isEqualTo(5)
+    }
+  }
+
+  @Test
+  fun `the correct generated factory is bound`() {
+    compile(
+      """
+        package com.squareup.test
+  
+        import com.squareup.anvil.annotations.ContributesSubcomponent
+        import com.squareup.anvil.annotations.ContributesSubcomponent.Factory
+        import com.squareup.anvil.annotations.MergeComponent
+        import dagger.BindsInstance
+        import javax.inject.Inject 
+
+        @ContributesSubcomponent(Any::class, parentScope = Unit::class)
+        interface SubcomponentInterface {
+          @Factory
+          interface ComponentFactory {
+            fun createComponent(
+              @BindsInstance integer: Int
+            ): SubcomponentInterface
+          }
+        }
+        
+        @MergeComponent(Unit::class)
+        interface ComponentInterface1
+        
+        @MergeComponent(Unit::class)
+        interface ComponentInterface2
+      """
+    ) {
+      val modules1 = componentInterface1.getAnnotation(Component::class.java).modules.toList()
+      val modules2 = componentInterface2.getAnnotation(Component::class.java).modules.toList()
+
+      val subcomponentModule1 = subcomponentInterface
+        .anvilComponent(componentInterface1)
+        .declaredClasses
+        .single { it.simpleName == SUBCOMPONENT_MODULE }
+        .kotlin
+
+      val subcomponentModule2 = subcomponentInterface
+        .anvilComponent(componentInterface2)
+        .declaredClasses
+        .single { it.simpleName == SUBCOMPONENT_MODULE }
+        .kotlin
+
+      assertThat(modules1).contains(subcomponentModule1)
+      assertThat(modules1).doesNotContain(subcomponentModule2)
+
+      assertThat(modules2).contains(subcomponentModule2)
+      assertThat(modules2).doesNotContain(subcomponentModule1)
+    }
+  }
+
+  @Test
+  fun `the correct generated factory is bound - with Dagger`() {
+    compile(
+      """
+        package com.squareup.test
+  
+        import com.squareup.anvil.annotations.ContributesSubcomponent
+        import com.squareup.anvil.annotations.ContributesSubcomponent.Factory
+        import com.squareup.anvil.annotations.MergeComponent
+        import dagger.BindsInstance
+        import javax.inject.Inject 
+
+        @ContributesSubcomponent(Any::class, parentScope = Unit::class)
+        interface SubcomponentInterface {
+          @Factory
+          interface ComponentFactory {
+            fun createComponent(
+              @BindsInstance integer: Int
+            ): SubcomponentInterface
+          }
+          
+          fun integer(): Int
+        }
+        
+        @MergeComponent(Unit::class)
+        interface ComponentInterface1 {
+          fun testClass(): TestClass
+        }
+        
+        @MergeComponent(Unit::class)
+        interface ComponentInterface2 {
+          fun testClass(): TestClass
+        }
+
+        class TestClass @Inject constructor(val factory: SubcomponentInterface.ComponentFactory)
+      """,
+      enableDaggerAnnotationProcessor = true
+    ) {
+      val daggerComponent1 = componentInterface1.daggerComponent.declaredMethods
+        .single { it.name == "create" }
+        .invoke(null)
+
+      val testClassInstance1 = componentInterface1.declaredMethods
+        .single { it.name == "testClass" }
+        .invoke(daggerComponent1)
+
+      val factory1 = testClassInstance1::class.java.declaredMethods
+        .single { it.name == "getFactory" }
+        .invoke(testClassInstance1)
+
+      assertThat(factory1::class.java.name)
+        .isEqualTo("com.squareup.test.DaggerComponentInterface1\\$SubcomponentInterfaceAFactory")
+
+      val daggerComponent2 = componentInterface2.daggerComponent.declaredMethods
+        .single { it.name == "create" }
+        .invoke(null)
+
+      val testClassInstance2 = componentInterface2.declaredMethods
+        .single { it.name == "testClass" }
+        .invoke(daggerComponent2)
+
+      val factory2 = testClassInstance2::class.java.declaredMethods
+        .single { it.name == "getFactory" }
+        .invoke(testClassInstance2)
+
+      assertThat(factory2::class.java.name)
+        .isEqualTo("com.squareup.test.DaggerComponentInterface2\\$SubcomponentInterfaceAFactory")
+    }
+  }
+
   @Test fun `the generated subcomponent contains the same scope annotation`() {
     compile(
       """