ContributesSubcomponent issue in testing

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(
       """

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

package com.squareup.anvil.annotations

import com.squareup.anvil.annotations.ContributesBinding.Priority.NORMAL
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Generate a Dagger binding method for an annotated class and contributes this binding method to
 * the given [scope]. Imagine this example:
 * ```
 * interface Authenticator
 *
 * class RealAuthenticator @Inject constructor() : Authenticator
 *
 * @Module
 * @ContributesTo(AppScope::class)
 * abstract class AuthenticatorModule {
 *   @Binds abstract fun bindRealAuthenticator(authenticator: RealAuthenticator): Authenticator
 * }
 * ```
 * This is a lot of boilerplate if you always want to use `RealAuthenticator` when injecting
 * `Authenticator`. You can replace this entire module with the [ContributesBinding] annotation.
 * The equivalent would be:
 * ```
 * interface Authenticator
 *
 * @ContributesBinding(AppScope::class)
 * class RealAuthenticator @Inject constructor() : Authenticator
 * ```
 * Notice that it's optional to specify [boundType], if there is only exactly one super type. If
 * there are multiple super types, then it's required to specify the parameter:
 * ```
 * @ContributesBinding(
 *     scope = AppScope::class,
 *     boundType = Authenticator::class
 * )
 * class RealAuthenticator @Inject constructor() : AbstractTokenProvider(), Authenticator
 * ```
 *
 * [ContributesBinding] supports qualifiers. If you annotate the class additionally with a
 * qualifier, then the generated binding method will be annotated with the same qualifier, e.g.
 * ```
 * @ContributesBinding(AppScope::class)
 * @Named("Prod")
 * class RealAuthenticator @Inject constructor() : Authenticator
 *
 * // Will generate this binding method.
 * @Binds @Named("Prod")
 * abstract fun bindRealAuthenticator(authenticator: RealAuthenticator): Authenticator
 * ```
 *
 * [ContributesMultibinding] allows you to generate multibinding methods. Both annotations can be
 * used in conjunction. If a qualifier is only meant for one of the annotations, then you can set
 * [ignoreQualifier] to `true` and the qualifier won't be added to the generated binding method.
 * ```
 * @ContributesBinding(AppScope::class, ignoreQualifier = true)
 * @ContributesMultibinding(AppScope::class)
 * @Named("Prod")
 * object MainListener : Listener
 * ```
 *
 * [ContributesBinding] is a convenience for a very simple but the most common scenario. Multiple
 * bound types or generic types are not supported. In these cases it's still required to write
 * a Dagger module.
 *
 * Contributed bindings can replace other contributed modules and bindings with the [replaces]
 * parameter. This is especially helpful for different bindings in instrumentation tests.
 * ```
 * @ContributesBinding(
 *     scope = AppScope::class,
 *     replaces = [RealAuthenticator::class]
 * )
 * class FakeAuthenticator @Inject constructor() : Authenticator
 * ```
 * If you don't have access to the class of another contributed binding that you want to replace,
 * then you can change the [priority] of the bindings to avoid duplicate bindings. The contributed
 * binding with the higher priority will be used.
 *
 * [ContributesBinding] supports Kotlin objects, e.g.
 * ```
 * @ContributesBinding(AppScope::class)
 * object RealAuthenticator : Authenticator
 * ```
 * In this scenario instead of generating a `@Binds` method Anvil will generate a `@Provides`
 * method returning [boundType].
 */
@Target(CLASS)
@Retention(RUNTIME)
public annotation class ContributesBinding(
  /**
   * The scope in which to include this contributed binding.
   */
  val scope: KClass<*>,
  /**
   * The type that this class is bound to. When injecting [boundType] the concrete class will be
   * this annotated class.
   */
  val boundType: KClass<*> = Unit::class,
  /**
   * This contributed binding will replace these contributed classes. The array is allowed to
   * include other contributed bindings or contributed Dagger modules. All replaced classes must
   * use the same scope.
   */
  val replaces: Array<KClass<*>> = [],
  /**
   * The priority of this contributed binding. The priority should be changed only if you don't
   * have access to the contributed binding class that you want to replace at compile time. If
   * you have access and can reference the other class, then it's highly suggested to
   * use [replaces] instead.
   *
   * In case of a duplicate binding for multiple contributed bindings the binding with the highest
   * priority will be used and replace other contributed bindings for the same type with a lower
   * priority. If duplicate contributed bindings use the same priority, then there will be an
   * error for duplicate bindings.
   *
   * Note that [replaces] takes precedence. If you explicitly replace a binding it won't be
   * considered no matter what its priority is.
   *
   * All contributed bindings have a [NORMAL] priority by default.
   */
  val priority: Priority = NORMAL,
  /**
   * Whether the qualifier for this class should be included in the generated binding method. This
   * parameter is only necessary to use when [ContributesBinding] and [ContributesMultibinding]
   * are used together for the same class. If not, simply remove the qualifier from the class
   * and don't use this parameter.
   */
  val ignoreQualifier: Boolean = false
) {
  /**
   * The priority of a contributed binding.
   */
  @Suppress("unused")
  public enum class Priority {
    NORMAL, HIGH, HIGHEST
  }
}

package com.squareup.anvil.annotations

import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Generate a Dagger multibinding method for an annotated class and contributes this multibinding
 * method to the given [scope]. Imagine this example:
 * ```
 * interface Listener
 *
 * class MainListener @Inject constructor() : Listener
 *
 * @Module
 * @ContributesTo(AppScope::class)
 * abstract class MainListenerModule {
 *   @Binds @IntoSet
 *   abstract fun bindMainListener(listener: MainListener): Listener
 * }
 * ```
 * This is a lot of boilerplate. You can replace this entire module with the
 * [ContributesMultibinding] annotation. The equivalent would be:
 * ```
 * interface Listener
 *
 * @ContributesMultibinding(AppScope::class)
 * class MainListener @Inject constructor() : Listener
 * ```
 * Notice that it's optional to specify [boundType], if there is only exactly one super type. If
 * there are multiple super types, then it's required to specify the parameter:
 * ```
 * @ContributesMultibinding(
 *   scope = AppScope::class,
 *   boundType = Listener::class
 * )
 * class MainListener @Inject constructor() : Activity(), Listener
 * ```
 *
 * [ContributesMultibinding] supports qualifiers. If you annotate the class additionally with a
 * qualifier, then the generated multibinding method will be annotated with the same qualifier,
 * e.g.
 * ```
 * @ContributesMultibinding(AppScope::class)
 * @Named("Prod")
 * class MainListener @Inject constructor() : Listener
 *
 * // Will generate this binding method.
 * @Binds @IntoSet @Named("Prod")
 * abstract fun bindMainListener(listener: MainListener): Listener
 * ```
 *
 * To generate a Map multibindings method you need to annotate the class with the map key. Anvil
 * will use the map key as hint to generate a binding method for a map instead of a set:
 * ```
 * @MapKey
 * annotation class BindingKey(val value: String)
 *
 * @ContributesMultibinding(AppScope::class)
 * @BindingKey("abc")
 * class MainListener @Inject constructor() : Listener
 *
 * // Will generate this binding method.
 * @Binds @IntoMap @BindingKey("abc")
 * abstract fun bindMainListener(listener: MainListener): Listener
 * ```
 * Note that map keys must allow classes as target. Dagger's built-in keys like `@StringKey` and
 * `@ClassKey` only allow methods as target and therefore cannot be used with this annotation. It's
 * recommended to create your own map key annotation class like in the snippet above. Furthermore,
 * Dagger allows using map keys that are not known at compile time. Anvil doesn't support them
 * either and it's recommended to contribute a normal Dagger module to the graph instead of using
 * [ContributesMultibinding].
 *
 * Contributed multibindings can replace other contributed modules and contributed multibindings
 * with the [replaces] parameter. This is especially helpful for different multibindings in
 * tests.
 * ```
 * @ContributesMultibinding(
 *   scope = AppScope::class,
 *   replaces = [MainListener::class]
 * )
 * class FakeListener @Inject constructor() : Listener
 * ```
 * [ContributesMultibinding] supports Kotlin objects, e.g.
 * ```
 * @ContributesMultibinding(AppScope::class)
 * object MainListener : Listener
 * ```
 * In this scenario instead of generating a `@Binds @IntoSet` method Anvil will generate a
 * `@Provides @IntoSet` method returning [boundType].
 *
 * [ContributesMultibinding] can be used in conjunction with [ContributesBinding]. Each annotation
 * will generate the respective binding or multibindings method. If a qualifier is only meant for
 * one of the annotations, then you can set [ignoreQualifier] to `true` and the qualifier won't
 * be added to the generated multibinding method.
 * ```
 * @ContributesBinding(AppScope::class)
 * @ContributesMultibinding(AppScope::class, ignoreQualifier = true)
 * @Named("Prod")
 * object MainListener : Listener
 * object MainListener : Listener
 * ```
 */
@Target(CLASS)
@Retention(RUNTIME)
public annotation class ContributesMultibinding(
  /**
   * The scope in which to include this contributed multibinding.
   */
  val scope: KClass<*>,
  /**
   * The type that this class is bound to. This class will be included in the collection for
   * [boundType].
   */
  val boundType: KClass<*> = Unit::class,
  /**
   * This contributed multibinding will replace these contributed classes. The array is allowed to
   * include other contributed multibindings or contributed Dagger modules. All replaced classes
   * must use the same scope.
   */
  val replaces: Array<KClass<*>> = [],
  /**
   * Whether the qualifier for this class should be included in the generated multibinding method.
   * This parameter is only necessary to use when [ContributesBinding] and [ContributesMultibinding]
   * are used together for the same class. If not, simply remove the qualifier from the class and
   * don't use this parameter.
   */
  val ignoreQualifier: Boolean = false
)

package com.squareup.anvil.annotations

import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Similar to [MergeSubcomponent], but with the main difference that the subcomponent is generated
 * and modules, bindings and component interfaces are merged whenever the component with
 * [parentScope] is processed.
 *
 * Imagine this module dependency tree:
 * ```
 *        :app
 *       /     \
 *      v       v
 *   :lib-a   :lib-b
 * ```
 * `:app` creates the component with `@MergeComponent`, `:lib-a` creates a subcomponent with
 * `@MergeSubcomponent` and `:lib-b` contributes a module to the scope of the subcomponent. This
 * module won't be included in the subcomponent without adding the dependency `:lib-a -> :lib-b`.
 * See [MergeSubcomponent] for further explanation.
 *
 * On the other hand, if `:lib-a` uses `@ContributesSubcomponent` with a parent scope of the main
 * component from `:app`, then the actual subcomponent will be generated in the `:app` module and
 * the contributed module from `:lib-b` will be picked up.
 *
 * ```
 * @ContributesSubcomponent(
 *   scope = ActivityScope::class,
 *   parentScope = AppScope::class
 * )
 * interface ActivitySubcomponent
 * ```
 *
 * [parentScope] can be the scope of a [MergeComponent], [MergeSubcomponent] or another
 * [ContributesSubcomponent]. A chain of [ContributesSubcomponent]s is supported.
 *
 * It's possible to exclude any automatically added Dagger module or component interface with the
 * [exclude] parameter if needed.
 * ```
 * @ContributesSubcomponent(
 *   scope = ActivityScope::class,
 *   parentScope = AppScope::class,
 *   exclude = [
 *     DaggerModule::class,
 *     ComponentInterface::class
 *   ]
 * )
 * interface ActivitySubcomponent
 * ```
 *
 * It's recommended to define a parent component interface as inner class of the contributed
 * subcomponent that is contributed to the parent scope. Anvil will always generate a parent
 * component interface for the final generated subcomponent with an abstract factory method for
 * the subcomponent. If you create your own parent component interface, then the one generated by
 * Anvil will extend your interface. E.g.
 * ```
 * @ContributesSubcomponent(
 *   scope = ActivityScope::class,
 *   parentScope = AppScope::class
 * )
 * interface ActivitySubcomponent {
 *
 *   @ContributesTo(AppScope::class)
 *   interface ParentComponent {
 *     fun createActivitySubcomponent(): ActivitySubcomponent
 *   }
 * }
 * ```
 */
@Target(CLASS)
@Retention(RUNTIME)
@ExperimentalAnvilApi
public annotation class ContributesSubcomponent(
  /**
   * The scope used to find all contributed bindings, multibindings, modules and component
   * interfaces, which should be included in this subcomponent.
   */
  val scope: KClass<*>,

  /**
   * The actual subcomponent for this contributed subcomponent will be generated when a
   * [MergeComponent], [MergeSubcomponent] or another [ContributesSubcomponent] annotated component
   * with the same scope as `parentScope` is merged.
   */
  val parentScope: KClass<*>,
  /**
   * List of Dagger modules that should be manually included in the subcomponent and aren't
   * automatically contributed.
   */
  val modules: Array<KClass<*>> = [],
  /**
   * List of bindings, multibindings, modules and component interfaces that are contributed to the
   * same scope, but should be excluded from the subcomponent.
   */
  val exclude: Array<KClass<*>> = []
) {
  /**
   * A factory for a contributed subcomponent.
   *
   * This follows all the rules of `Subcomponent.Factory`, except it must appear in classes
   * annotated with `@ContributesSubcomponent` instead of `@Subcomponent`.
   *
   * It's strongly recommended creating a parent component interface next to the factory that is
   * contributed to the parent scope. The parent component interface should have one method that
   * returns the factory type, e.g.
   * ```
   * @ContributesSubcomponent(
   *   scope = ActivityScope::class,
   *   parentScope = AppScope::class
   * )
   * interface ActivitySubcomponent {
   *
   *   @ContributesSubcomponent.Factory
   *   interface Factory {
   *     fun createActivitySubcomponent(
   *       @BindsInstance int: Int
   *     ): ActivitySubcomponent
   *   }
   *
   *   @ContributesTo(AppScope::class)
   *   interface ParentComponent {
   *     fun createActivitySubcomponentFactory(): ActivitySubcomponent.Factory
   *   }
   * }
   * ```
   * If no parent component interface but a factory is present, then the automatically generated
   * parent component interface will return a type of the factory and not the subcomponent
   * directly.
   */
  public annotation class Factory
}

package com.squareup.anvil.annotations

import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Marks a Dagger module or component interface to be included in the Dagger dependency graph in the
 * given [scope]. Anvil automatically adds the module to the list of included modules in the
 * Dagger component marked with [MergeComponent]. For component interfaces it will make the Dagger
 * component extend the interface.
 *
 * ```
 * @Module
 * @ContributesTo(AppScope::class)
 * class DaggerModule { .. }
 *
 * @ContributesTo(LoggedInScope::class)
 * interface ComponentInterface {
 *   fun provideSomething()
 * }
 * ```
 *
 * Modules and component interfaces can replace other modules and component interfaces with the
 * [replaces] parameter. This is especially helpful for modules providing different bindings in
 * instrumentation tests.
 * ```
 * @Module
 * @ContributesTo(
 *     AppScope::class,
 *     replaces = [DevelopmentApplicationModule::class]
 * )
 * object DevelopmentApplicationTestModule {
 *   @Provides
 *   fun provideEndpointSelector(): EndpointSelector = TestingEndpointSelector
 * }
 * ```
 */
@Target(CLASS)
@Retention(RUNTIME)
public annotation class ContributesTo(
  /**
   * The scope in which to include this module.
   */
  val scope: KClass<*>,
  /**
   * This contributed module will replace these contributed classes. The array is allowed to
   * include other contributed bindings, multibindings and Dagger modules. All replaced classes
   * must use the same scope.
   */
  val replaces: Array<KClass<*>> = []
)

package com.squareup.anvil.annotations

import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY

/** Indicates that the annotated Anvil API is experimental and subject to change. */
@RequiresOptIn
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, FUNCTION, PROPERTY)
public annotation class ExperimentalAnvilApi

package com.squareup.anvil.annotations

import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Dagger components that should automatically include Dagger modules and component interfaces for the
 * given [scope] require this annotation instead of `@Component`. The Kotlin compiler plugin will
 * add the `@Component` annotation to this interface. The parameters [modules] and [dependencies] are
 * preserved in the `@Component` annotation.
 *
 * ```
 * @MergeComponent(AppScope::class)
 * interface AppComponent
 * ```
 *
 * It's possible to exclude any automatically added Dagger module or component interface with the
 * [exclude] parameter if needed.
 *
 * ```
 * @MergeComponent(
 *     scope = AppScope::class,
 *     exclude = [
 *       DaggerModule::class,
 *       ComponentInterface::class
 *     ]
 * )
 * interface AppComponent
 * ```
 */
@Target(CLASS)
@Retention(RUNTIME)
public annotation class MergeComponent(
  /**
   * The scope used to find all contributed bindings, multibindings, modules and component
   * interfaces, which should be included in this component.
   */
  val scope: KClass<*>,
  /**
   * List of Dagger modules that should be manually included in the component and aren't
   * automatically contributed.
   */
  val modules: Array<KClass<*>> = [],
  /**
   * List of types that are to be used as component dependencies.
   */
  val dependencies: Array<KClass<*>> = [],
  /**
   * List of bindings, multibindings, modules and component interfaces that are contributed to the
   * same scope, but should be excluded from the component.
   */
  val exclude: Array<KClass<*>> = []
)

package com.squareup.anvil.annotations

import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Dagger subcomponents that should automatically include Dagger modules and component interfaces for
 * the given [scope] require this annotation instead of `@Subcomponent`. The Kotlin compiler plugin
 * will add the `@Subcomponent` annotation to this interface. The [modules] parameter is
 * preserved in the `@Subcomponent` annotation.
 *
 *  Imagine this module dependency tree:
 * ```
 *          :app
 *        /   |
 *       v    |
 *   :lib-a   |
 *       \    |
 *        v   v
 *        :lib-b
 * ```
 *
 * `:app` creates the component with `@MergeComponent`, `:lib-a` creates a subcomponent with
 * `@MergeSubcomponent` and `:lib-b` contributes a module to the scope of the subcomponent. This
 * module will be included in the subcomponent because `:lib-a` depends on `:lib-b`.
 *
 * `:lib-a` must depend on `:lib-b` because the subcomponent will only be able to pick up
 * contributions within the scope of the containing Gradle module and is based on the module's
 * compile classpath. This means the module must have a dependency on any modules that want to
 * contribute to the subcomponent.
 *
 * See [ContributesSubcomponent] for an alternative that eliminates the need for `:lib-a` to depend
 * on `:lib-b`
 *
 * ```
 * @MergeSubcomponent(ActivityScope::class)
 * interface ActivitySubcomponent
 * ```
 *
 * It's possible to exclude any automatically added Dagger module or component interface with the
 * [exclude] parameter if needed.
 *
 * ```
 * @MergeSubcomponent(
 *     scope = ActivityScope::class,
 *     exclude = [
 *       DaggerModule::class,
 *       ComponentInterface::class
 *     ]
 * )
 * interface ActivitySubcomponent
 * ```
 */

@Target(CLASS)
@Retention(RUNTIME)
public annotation class MergeSubcomponent(
  /**
   * The scope used to find all contributed bindings, multibindings, modules and component
   * interfaces, which should be included in this subcomponent.
   */
  val scope: KClass<*>,
  /**
   * List of Dagger modules that should be manually included in the subcomponent and aren't
   * automatically contributed.
   */
  val modules: Array<KClass<*>> = [],
  /**
   * List of bindings, multibindings, modules and component interfaces that are contributed to the
   * same scope, but should be excluded from the subcomponent.
   */
  val exclude: Array<KClass<*>> = []
)

package com.squareup.anvil.compiler

import com.google.auto.service.AutoService
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey

internal const val srcGenDirName = "src-gen-dir"
internal val srcGenDirKey = CompilerConfigurationKey.create<String>("anvil $srcGenDirName")

internal const val generateDaggerFactoriesName = "generate-dagger-factories"
internal val generateDaggerFactoriesKey =
  CompilerConfigurationKey.create<Boolean>("anvil $generateDaggerFactoriesName")

internal const val generateDaggerFactoriesOnlyName = "generate-dagger-factories-only"
internal val generateDaggerFactoriesOnlyKey =
  CompilerConfigurationKey.create<Boolean>("anvil $generateDaggerFactoriesOnlyName")

internal const val disableComponentMergingName = "disable-component-merging"
internal val disableComponentMergingKey =
  CompilerConfigurationKey.create<Boolean>("anvil $disableComponentMergingName")

/**
 * Parses arguments from the Gradle plugin for the compiler plugin.
 */
@AutoService(CommandLineProcessor::class)
class AnvilCommandLineProcessor : CommandLineProcessor {
  override val pluginId: String = "com.squareup.anvil.compiler"

  override val pluginOptions: Collection<AbstractCliOption> = listOf(
    CliOption(
      optionName = srcGenDirName,
      valueDescription = "<file-path>",
      description = "Path to directory in which Anvil specific code should be generated",
      required = true,
      allowMultipleOccurrences = false
    ),
    CliOption(
      optionName = generateDaggerFactoriesName,
      valueDescription = "<true|false>",
      description = "Whether Anvil should generate Factory classes that the Dagger " +
        "annotation processor would generate for @Provides methods and @Inject " +
        "constructors.",
      required = false,
      allowMultipleOccurrences = false
    ),
    CliOption(
      optionName = generateDaggerFactoriesOnlyName,
      valueDescription = "<true|false>",
      description = "Whether Anvil should generate Factory classes only and no code for " +
        "contributed code.",
      required = false,
      allowMultipleOccurrences = false
    ),
    CliOption(
      optionName = disableComponentMergingName,
      valueDescription = "<true|false>",
      description = "Whether Anvil should generate code only and not transform code for " +
        "@MergeComponent or @MergeSubcomponent.",
      required = false,
      allowMultipleOccurrences = false
    )
  )

  override fun processOption(
    option: AbstractCliOption,
    value: String,
    configuration: CompilerConfiguration
  ) {
    when (option.optionName) {
      srcGenDirName -> configuration.put(srcGenDirKey, value)
      generateDaggerFactoriesName ->
        configuration.put(generateDaggerFactoriesKey, value.toBoolean())
      generateDaggerFactoriesOnlyName ->
        configuration.put(generateDaggerFactoriesOnlyKey, value.toBoolean())
      disableComponentMergingName ->
        configuration.put(disableComponentMergingKey, value.toBoolean())
    }
  }
}

package com.squareup.anvil.compiler

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.codegen.BindingModuleGenerator
import com.squareup.anvil.compiler.codegen.CodeGenerationExtension
import com.squareup.anvil.compiler.codegen.ContributesSubcomponentHandlerGenerator
import com.squareup.anvil.compiler.codegen.RealAnvilContext
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.com.intellij.mock.MockProject
import org.jetbrains.kotlin.com.intellij.openapi.extensions.LoadingOrder
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import java.io.File
import java.util.ServiceLoader

/**
 * Entry point for the Anvil Kotlin compiler plugin. It registers several callbacks for the
 * various compilation phases.
 */
@AutoService(ComponentRegistrar::class)
class AnvilComponentRegistrar : ComponentRegistrar {
  override fun registerProjectComponents(
    project: MockProject,
    configuration: CompilerConfiguration
  ) {
    val sourceGenFolder = File(configuration.getNotNull(srcGenDirKey))
    val scanner = ClassScanner()

    val generateDaggerFactories = configuration.get(generateDaggerFactoriesKey, false)
    val generateDaggerFactoriesOnly = configuration.get(generateDaggerFactoriesOnlyKey, false)
    val disableComponentMerging = configuration.get(disableComponentMergingKey, false)
    val context = RealAnvilContext(
      generateDaggerFactories,
      generateDaggerFactoriesOnly,
      disableComponentMerging
    )
    val codeGenerators = loadCodeGenerators()
      .filter { it.isApplicable(context) }
      .toMutableList()

    if (!generateDaggerFactoriesOnly) {
      // We special case these due to the ClassScanner requirement.
      codeGenerators += BindingModuleGenerator(scanner)
      codeGenerators += ContributesSubcomponentHandlerGenerator(scanner)
    }

    // It's important to register our extension at the first position. The compiler calls each
    // extension one by one. If an extension returns a result, then the compiler won't call any
    // other extension. That usually happens with Kapt in the stub generating task.
    //
    // It's not dangerous for our extension to run first, because we generate code, restart the
    // analysis phase and then don't return a result anymore. That means the next extension can
    // take over. If we wouldn't do this and any other extension won't let ours run, then we
    // couldn't generate any code.
    AnalysisHandlerExtension.registerExtensionFirst(
      project,
      CodeGenerationExtension(
        codeGenDir = sourceGenFolder,
        codeGenerators = codeGenerators
      )
    )

    if (!generateDaggerFactoriesOnly && !disableComponentMerging) {
      SyntheticResolveExtension.registerExtension(
        project,
        InterfaceMerger(scanner)
      )
      ExpressionCodegenExtension.registerExtension(
        project,
        ModuleMerger(scanner)
      )

      IrGenerationExtension.registerExtension(
        project,
        ModuleMergerIr(scanner)
      )
    }
  }

  private fun AnalysisHandlerExtension.Companion.registerExtensionFirst(
    project: MockProject,
    extension: AnalysisHandlerExtension
  ) {
    project.extensionArea
      .getExtensionPoint(AnalysisHandlerExtension.extensionPointName)
      .registerExtension(extension, LoadingOrder.FIRST, project)
  }

  private fun loadCodeGenerators(): List<CodeGenerator> {
    return ServiceLoader.load(CodeGenerator::class.java, CodeGenerator::class.java.classLoader)
      .toList()
  }
}

package com.squareup.anvil.compiler

import com.squareup.anvil.compiler.internal.annotationOrNull
import com.squareup.anvil.compiler.internal.argumentType
import com.squareup.anvil.compiler.internal.requireClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import kotlin.LazyThreadSafetyMode.NONE

internal class ClassScanner {

  /**
   * Returns a sequence of contributed classes from the dependency graph. Note that the result
   * includes inner classes already.
   */
  fun findContributedClasses(
    module: ModuleDescriptor,
    packageName: String,
    annotation: FqName,
    scope: FqName?
  ): Sequence<ClassDescriptor> {
    val packageDescriptor = module.getPackage(FqName(packageName))
    return generateSequence(listOf(packageDescriptor)) { subPackages ->
      subPackages
        .flatMap { it.subPackages() }
        .ifEmpty { null }
    }
      .flatMap { it.asSequence() }
      .flatMap {
        it.memberScope.getContributedDescriptors(DescriptorKindFilter.VALUES)
          .asSequence()
      }
      .filterIsInstance<PropertyDescriptor>()
      .groupBy { property ->
        // For each contributed hint there are several properties, e.g. the reference itself
        // and the scope. Group them by their common name without the suffix.
        val name = property.name.asString()
        val suffix = propertySuffixes.firstOrNull { name.endsWith(it) } ?: return@groupBy name
        name.substringBeforeLast(suffix)
      }
      .values
      .asSequence()
      .filter { properties ->
        // Double check that the number of properties matches how many suffixes we have and how
        // many properties we expect.
        properties.size == propertySuffixes.size
      }
      .map { ContributedHint(it) }
      .let { sequence ->
        if (scope != null) {
          sequence
            .filter { hint ->
              // The scope must match what we're looking for.
              hint.scope.fqNameSafe == scope
            }
        } else {
          sequence
        }
      }
      .map { hint -> hint.reference }
      .filter {
        // Check that the annotation really is present. It should always be the case, but it's
        // a safetynet in case the generated properties are out of sync.
        it.annotationOrNull(annotation, scope) != null
      }
  }
}

private fun PackageViewDescriptor.subPackages(): List<PackageViewDescriptor> = memberScope
  .getContributedDescriptors(DescriptorKindFilter.PACKAGES)
  .filterIsInstance<PackageViewDescriptor>()

private class ContributedHint(properties: List<PropertyDescriptor>) {
  val reference by lazy(NONE) {
    properties
      .bySuffix(REFERENCE_SUFFIX)
      .toClassDescriptor()
  }

  val scope by lazy(NONE) {
    properties
      .bySuffix(SCOPE_SUFFIX)
      .toClassDescriptor()
  }

  private fun List<PropertyDescriptor>.bySuffix(suffix: String): PropertyDescriptor = first {
    it.name.asString()
      .endsWith(suffix)
  }

  private fun PropertyDescriptor.toClassDescriptor(): ClassDescriptor =
    type.argumentType().requireClassDescriptor()
}

package com.squareup.anvil.compiler

import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe

/**
 * Returns a sequence of contributed classes from the dependency graph. Note that the result
 * includes inner classes already.
 */
internal fun ClassScanner.findContributedClasses(
  pluginContext: IrPluginContext,
  moduleFragment: IrModuleFragment,
  packageName: String,
  annotation: FqName,
  scope: FqName
): Sequence<IrClassSymbol> {
  return findContributedClasses(moduleFragment.descriptor, packageName, annotation, scope)
    .map {
      pluginContext.requireReferenceClass(it.fqNameSafe)
    }
}

package com.squareup.anvil.compiler

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.getAllSuperTypes
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.scope
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.EffectiveVisibility.Public
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
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.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.types.KotlinType

/**
 * Finds all contributed component interfaces and adds them as super types to Dagger components
 * annotated with `@MergeComponent` or `@MergeSubcomponent`.
 */
internal class InterfaceMerger(
  private val classScanner: ClassScanner
) : SyntheticResolveExtension {
  override fun addSyntheticSupertypes(
    thisDescriptor: ClassDescriptor,
    supertypes: MutableList<KotlinType>
  ) {
    val mergeAnnotation = thisDescriptor.annotationOrNull(mergeComponentFqName)
      ?: thisDescriptor.annotationOrNull(mergeSubcomponentFqName)
      ?: thisDescriptor.annotationOrNull(mergeInterfacesFqName)

    if (mergeAnnotation == null) {
      super.addSyntheticSupertypes(thisDescriptor, supertypes)
      return
    }

    val module = thisDescriptor.module

    val scope = mergeAnnotation.scope(module)
    val scopeFqName = scope.fqNameSafe

    if (!DescriptorUtils.isInterface(thisDescriptor)) {
      throw AnvilCompilationException(thisDescriptor, "Dagger components must be interfaces.")
    }

    val classes = classScanner
      .findContributedClasses(
        module = module,
        packageName = HINT_CONTRIBUTES_PACKAGE_PREFIX,
        annotation = contributesToFqName,
        scope = scopeFqName
      )
      .filter {
        DescriptorUtils.isInterface(it) && it.annotationOrNull(daggerModuleFqName) == null
      }
      .mapNotNull {
        val contributeAnnotation =
          it.annotationOrNull(contributesToFqName, scope = scopeFqName)
            ?: return@mapNotNull null
        it to contributeAnnotation
      }
      .onEach { (classDescriptor, _) ->
        if (classDescriptor.effectiveVisibility() !is Public) {
          throw AnvilCompilationException(
            classDescriptor,
            "${classDescriptor.fqNameSafe} is contributed to the Dagger graph, but the " +
              "interface is not public. Only public interfaces are supported."
          )
        }
      }
      // Convert the sequence to a list to avoid iterating it twice. We use the result twice
      // for replaced classes and the final result.
      .toList()

    val replacedClasses = classes
      .flatMap { (classDescriptor, contributeAnnotation) ->
        contributeAnnotation.replaces(classDescriptor.module)
          .onEach { classDescriptorForReplacement ->
            // Verify the other class is an interface. It doesn't make sense for a contributed
            // interface to replace a class that is not an interface.
            if (!DescriptorUtils.isInterface(classDescriptorForReplacement)) {
              throw AnvilCompilationException(
                classDescriptor,
                "${classDescriptor.fqNameSafe} wants to replace " +
                  "${classDescriptorForReplacement.fqNameSafe}, but the class being " +
                  "replaced is not an interface."
              )
            }

            val contributesToAnnotation = classDescriptorForReplacement
              .annotationOrNull(contributesToFqName)
            val contributesBindingAnnotation = classDescriptorForReplacement
              .annotationOrNull(contributesBindingFqName)
            val contributesMultibindingAnnotation = classDescriptorForReplacement
              .annotationOrNull(contributesMultibindingFqName)

            // Verify that the replaced classes use the same scope.
            val scopeOfReplacement = contributesToAnnotation?.scope(module)
              ?: contributesBindingAnnotation?.scope(module)
              ?: contributesMultibindingAnnotation?.scope(module)
              ?: throw AnvilCompilationException(
                classDescriptor,
                "Could not determine the scope of the replaced class " +
                  "${classDescriptorForReplacement.fqNameSafe}."
              )

            if (scopeOfReplacement.fqNameSafe != scopeFqName) {
              throw AnvilCompilationException(
                classDescriptor,
                "${classDescriptor.fqNameSafe} with scope $scopeFqName wants to replace " +
                  "${classDescriptorForReplacement.fqNameSafe} with scope " +
                  "${scopeOfReplacement.fqNameSafe}. The replacement must use the same " +
                  "scope."
              )
            }
          }
          .map { it.fqNameSafe }
      }
      .toSet()

    val excludedClasses = (mergeAnnotation.getAnnotationValue("exclude") as? ArrayValue)
      ?.value
      ?.map { it.argumentType(module).requireClassDescriptor() }
      ?.filter { DescriptorUtils.isInterface(it) }
      ?.map { classDescriptorForExclusion ->
        val contributesToAnnotation = classDescriptorForExclusion
          .annotationOrNull(contributesToFqName)
        val contributesBindingAnnotation = classDescriptorForExclusion
          .annotationOrNull(contributesBindingFqName)
        val contributesMultibindingAnnotation = classDescriptorForExclusion
          .annotationOrNull(contributesMultibindingFqName)
        val contributesSubcomponentAnnotation = classDescriptorForExclusion
          .annotationOrNull(contributesSubcomponentFqName)

        // Verify that the replaced classes use the same scope.
        val scopeOfExclusion = contributesToAnnotation?.scope(module)
          ?: contributesBindingAnnotation?.scope(module)
          ?: contributesMultibindingAnnotation?.scope(module)
          ?: contributesSubcomponentAnnotation?.parentScope(module)
          ?: throw AnvilCompilationException(
            thisDescriptor,
            "Could not determine the scope of the excluded class " +
              "${classDescriptorForExclusion.fqNameSafe}."
          )

        if (scopeOfExclusion.fqNameSafe != scopeFqName) {
          throw AnvilCompilationException(
            thisDescriptor,
            "${thisDescriptor.fqNameSafe} with scope $scopeFqName wants to exclude " +
              "${classDescriptorForExclusion.fqNameSafe} with scope " +
              "${scopeOfExclusion.fqNameSafe}. The exclusion must use the same scope."
          )
        }

        classDescriptorForExclusion.fqNameSafe
      }
      ?: emptyList()

    if (excludedClasses.isNotEmpty()) {
      val intersect = supertypes.getAllSuperTypes()
        .toList()
        .intersect(excludedClasses.toSet())

      if (intersect.isNotEmpty()) {
        throw AnvilCompilationException(
          classDescriptor = thisDescriptor,
          message = "${thisDescriptor.name} excludes types that it implements or extends. " +
            "These types cannot be excluded. Look at all the super types to find these " +
            "classes: ${intersect.joinToString()}"
        )
      }
    }

    @Suppress("ConvertCallChainIntoSequence")
    val contributedClasses = classes
      .map { it.first }
      .filterNot {
        val fqName = it.fqNameSafe
        replacedClasses.contains(fqName) || excludedClasses.contains(fqName)
      }
      .plus(findContributedSubcomponentParentInterfaces(thisDescriptor, scopeFqName, module))
      // Avoids an error for repeated interfaces.
      .distinctBy { it.fqNameSafe }
      .map { it.defaultType }

    supertypes += contributedClasses
    super.addSyntheticSupertypes(thisDescriptor, supertypes)
  }

  private fun findContributedSubcomponentParentInterfaces(
    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(PARENT_COMPONENT))
          .classDescriptorOrNull(module)
      }
  }
}

package com.squareup.anvil.compiler

import com.squareup.anvil.compiler.api.AnvilCompilationException
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.types.IrSimpleType
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.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.getAnnotation
import org.jetbrains.kotlin.ir.util.getArgumentsWithIr
import org.jetbrains.kotlin.name.FqName

internal fun IrPluginContext.requireReferenceClass(fqName: FqName): IrClassSymbol {
  return referenceClass(fqName) ?: throw AnvilCompilationException(
    message = "Couldn't resolve reference for $fqName"
  )
}

internal fun IrAnnotationContainer.annotationOrNull(
  annotationFqName: FqName,
  scope: FqName? = null
): IrConstructorCall? {
  return getAnnotation(annotationFqName)?.takeIf { annotation ->
    scope == null || annotation.scope() == scope
  }
}

internal fun IrAnnotationContainer.annotation(
  annotationFqName: FqName,
  scope: FqName? = null
): IrConstructorCall = annotationOrNull(annotationFqName, scope)
  ?: throw AnvilCompilationException(
    message = "Couldn't find $annotationFqName with scope $scope.",
    element = (this as? IrType)?.getClass()
  )

internal val IrExpression.kclassUnwrapped: IrClassifierSymbol
  get() = (type as? IrSimpleType)?.arguments?.get(0)?.typeOrNull?.classifierOrNull
    ?: throw AnvilCompilationException(
      message = "Couldn't resolve wrapped class.",
      element = this
    )

internal fun IrConstructorCall.scope(): FqName {
  val expression = argument("scope")?.second
    ?: throw AnvilCompilationException(
      message = "Couldn't find scope annotation.",
      element = this
    )

  val signature = expression.kclassUnwrapped.signature?.asPublic()
    ?: throw AnvilCompilationException(
      message = "Couldn't resolve scope signature.",
      element = this
    )

  return FqName("${signature.packageFqName}.${signature.shortName}")
}

internal fun IrConstructorCall.parentScope(): FqName {
  val expression = argument("parentScope")?.second
    ?: throw AnvilCompilationException(
      message = "Couldn't find parent scope annotation.",
      element = this
    )

  val signature = expression.kclassUnwrapped.signature?.asPublic()
    ?: throw AnvilCompilationException(
      message = "Couldn't resolve parent scope signature.",
      element = this
    )

  return FqName("${signature.packageFqName}.${signature.shortName}")
}

internal val IrDeclarationWithName.fqName: FqName
  get() = fqNameWhenAvailable ?: throw AnvilCompilationException(
    message = "Couldn't find FqName for $name",
    element = this
  )

internal val IrClassSymbol.fqName: FqName get() = owner.fqName

internal fun IrMemberAccessExpression<*>.argument(
  name: String
): Pair<IrValueParameter, IrExpression>? {
  return getArgumentsWithIr()
    .singleOrNull {
      it.first.name.asString() == name
    }
}

internal fun IrConstructorCall.argumentClassArray(
  name: String
): List<IrClass> {
  val vararg = argument(name)?.second as? IrVararg ?: return emptyList()

  return vararg.elements
    .filterIsInstance<IrExpression>()
    .map { it.kclassUnwrapped.owner as IrClass }
}

internal fun IrConstructorCall.replaces(): List<IrClass> = argumentClassArray("replaces")

internal fun IrConstructorCall.exclude(): List<IrClass> = argumentClassArray("exclude")

package com.squareup.anvil.compiler

import com.squareup.anvil.annotations.ContributesTo
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.internal.annotation
import com.squareup.anvil.compiler.internal.annotationOrNull
import com.squareup.anvil.compiler.internal.argumentType
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.safePackageString
import com.squareup.anvil.compiler.internal.scope
import com.squareup.anvil.compiler.internal.toType
import dagger.Component
import dagger.Module
import dagger.Subcomponent
import org.jetbrains.kotlin.backend.common.serialization.findPackage
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.codegen.asmType
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.EffectiveVisibility.Public
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.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
import org.jetbrains.org.objectweb.asm.Type
import kotlin.reflect.KClass

/**
 * Finds all contributed Dagger modules and adds them to Dagger components annotated with
 * `@MergeComponent` or `@MergeSubcomponent`. This class is responsible for generating the
 * `@Component` and `@Subcomponent` annotation required for Dagger.
 */
internal class ModuleMerger(
  private val classScanner: ClassScanner
) : ExpressionCodegenExtension {

  override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
    val holder = AnnotationHolder.create(codegen.descriptor) ?: return
    val (mergeAnnotation, annotationClass, daggerClass, daggerFqName, modulesKeyword) = holder

    if (codegen.descriptor.annotationOrNull(daggerFqName) != null) {
      throw AnvilCompilationException(
        codegen.descriptor,
        "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 scope = mergeAnnotation.scope(module)
    val scopeFqName = scope.fqNameSafe

    val predefinedModules =
      (mergeAnnotation.getAnnotationValue(modulesKeyword) as? ArrayValue)
        ?.value
        ?.map { it.toType(codegen) }

    val anvilModuleName = createAnvilModuleName(codegen.descriptor)

    val modules = classScanner
      .findContributedClasses(
        module = module,
        packageName = HINT_CONTRIBUTES_PACKAGE_PREFIX,
        annotation = contributesToFqName,
        scope = scopeFqName
      )
      .filter {
        // We generate a Dagger module for each merged component. We use Anvil itself to
        // contribute this generated module. It's possible that there are multiple components
        // merging the same scope or the same scope is merged in different Gradle modules which
        // depend on each other. This would cause duplicate bindings, because the generated
        // modules contain the same bindings and are contributed to the same scope. To avoid this
        // issue we filter all generated Anvil modules except for the one that was generated for
        // this specific class.
        val fqName = it.fqNameSafe
        !fqName.isAnvilModule() || fqName == anvilModuleName
      }
      .mapNotNull {
        val contributesAnnotation =
          it.annotationOrNull(contributesToFqName, scope = scopeFqName)
            ?: return@mapNotNull null
        it to contributesAnnotation
      }
      .filter { (classDescriptor, _) ->
        val moduleAnnotation = classDescriptor.annotationOrNull(daggerModuleFqName)
        val mergeModulesAnnotation = classDescriptor.annotationOrNull(mergeModulesFqName)
        if (!DescriptorUtils.isInterface(classDescriptor) &&
          moduleAnnotation == null &&
          mergeModulesAnnotation == null
        ) {
          throw AnvilCompilationException(
            classDescriptor,
            "${classDescriptor.fqNameSafe} is annotated with " +
              "@${ContributesTo::class.simpleName}, but this class is neither an interface " +
              "nor a Dagger module. Did you forget to add @${Module::class.simpleName}?"
          )
        }

        moduleAnnotation != null || mergeModulesAnnotation != null
      }
      .onEach { (classDescriptor, _) ->
        if (classDescriptor.effectiveVisibility() !is Public) {
          throw AnvilCompilationException(
            classDescriptor,
            "${classDescriptor.fqNameSafe} is contributed to the Dagger graph, but the " +
              "module is not public. Only public modules are supported."
          )
        }
      }
      // Convert the sequence to a list to avoid iterating it twice. We use the result twice
      // for replaced classes and the final result.
      .toList()

    val excludedModules = (mergeAnnotation.getAnnotationValue("exclude") as? ArrayValue)
      ?.value
      ?.map {
        val argumentType = it.argumentType(module)
        val classDescriptorForExclusion = argumentType.requireClassDescriptor()

        val contributesToAnnotation = classDescriptorForExclusion
          .annotationOrNull(contributesToFqName)
        val contributesBindingAnnotation = classDescriptorForExclusion
          .annotationOrNull(contributesBindingFqName)
        val contributesMultibindingAnnotation = classDescriptorForExclusion
          .annotationOrNull(contributesMultibindingFqName)
        val contributesSubcomponentAnnotation = classDescriptorForExclusion
          .annotationOrNull(contributesSubcomponentFqName)

        // Verify that the replaced classes use the same scope.
        val scopeOfExclusion = contributesToAnnotation?.scope(module)
          ?: contributesBindingAnnotation?.scope(module)
          ?: contributesMultibindingAnnotation?.scope(module)
          ?: contributesSubcomponentAnnotation?.parentScope(module)
          ?: throw AnvilCompilationException(
            codegen.descriptor,
            "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 " +
              "${classDescriptorForExclusion.fqNameSafe} with scope " +
              "${scopeOfExclusion.fqNameSafe}. The exclusion must use the same scope."
          )
        }

        argumentType.asmType(codegen.typeMapper)
      }
      ?: emptyList()

    val replacedModules = modules
      .filter { (classDescriptor, _) ->
        // Ignore replaced modules or bindings specified by excluded modules.
        classDescriptor.defaultType.asmType(codegen.typeMapper) !in excludedModules
      }
      .flatMap { (classDescriptor, contributeAnnotation) ->
        contributeAnnotation.replaces(module)
          .map { classDescriptorForReplacement ->
            // Verify has @Module annotation. It doesn't make sense for a Dagger module to
            // replace a non-Dagger module.
            if (classDescriptorForReplacement.annotationOrNull(daggerModuleFqName) == null &&
              classDescriptorForReplacement.annotationOrNull(contributesBindingFqName) == null &&
              classDescriptorForReplacement.annotationOrNull(contributesMultibindingFqName) == null
            ) {
              throw AnvilCompilationException(
                classDescriptor,
                "${classDescriptor.fqNameSafe} wants to replace " +
                  "${classDescriptorForReplacement.fqNameSafe}, but the class being " +
                  "replaced is not a Dagger module."
              )
            }

            checkSameScope(classDescriptor, classDescriptorForReplacement, module, scopeFqName)

            classDescriptorForReplacement.defaultType.asmType(codegen.typeMapper)
          }
      }

    fun replacedModulesByContributedBinding(
      annotationFqName: FqName,
      hintPackagePrefix: String
    ): Sequence<Type> {
      return classScanner
        .findContributedClasses(
          module = module,
          packageName = hintPackagePrefix,
          annotation = annotationFqName,
          scope = scopeFqName
        )
        .flatMap { contributedClass ->
          val annotation = contributedClass.annotation(annotationFqName)
          if (scopeFqName == annotation.scope(module).fqNameSafe) {
            annotation.replaces(module)
              .map { classDescriptorForReplacement ->
                checkSameScope(
                  contributedClass,
                  classDescriptorForReplacement,
                  module,
                  scopeFqName
                )
                classDescriptorForReplacement.defaultType.asmType(codegen.typeMapper)
              }
          } else {
            emptyList()
          }
        }
    }

    val replacedModulesByContributedBindings = replacedModulesByContributedBinding(
      annotationFqName = contributesBindingFqName,
      hintPackagePrefix = HINT_BINDING_PACKAGE_PREFIX
    )

    val replacedModulesByContributedMultibindings = replacedModulesByContributedBinding(
      annotationFqName = contributesMultibindingFqName,
      hintPackagePrefix = HINT_MULTIBINDING_PACKAGE_PREFIX
    )

    if (predefinedModules != null) {
      val intersect = predefinedModules.intersect(excludedModules.toSet())
      if (intersect.isNotEmpty()) {
        throw AnvilCompilationException(
          codegen.descriptor,
          "${codegen.descriptor.name} includes and excludes modules " +
            "at the same time: ${intersect.joinToString { it.className }}"
        )
      }
    }

    val contributedModules = modules
      .asSequence()
      .map { codegen.typeMapper.mapType(it.first) }
      .minus(replacedModules.toSet())
      .minus(replacedModulesByContributedBindings.toSet())
      .minus(replacedModulesByContributedMultibindings.toSet())
      .minus(excludedModules.toSet())
      .distinct()

    codegen.v
      .newAnnotation("L${daggerClass.java.canonicalName.replace('.', '/')};", true)
      .use {
        visitArray(modulesKeyword)
          .use {
            predefinedModules?.forEach { visit(modulesKeyword, it) }
            contributedModules.forEach { visit(modulesKeyword, it) }
          }

        if (holder.isComponent) {
          copyArrayValue(codegen, mergeAnnotation, "dependencies")
        }

        if (holder.isModule) {
          copyArrayValue(codegen, mergeAnnotation, "subcomponents")
        }
      }
  }

  private fun AnnotationVisitor.copyArrayValue(
    codegen: ImplementationBodyCodegen,
    annotation: AnnotationDescriptor,
    name: String
  ) {
    val predefinedValues =
      (annotation.getAnnotationValue(name) as? ArrayValue)
        ?.value
        ?.map { it.toType(codegen) }

    visitArray(name).use {
      predefinedValues?.forEach { visit(name, it) }
    }
  }

  private fun ConstantValue<*>.toType(codegen: ImplementationBodyCodegen): Type {
    return toType(codegen.descriptor.module, codegen.typeMapper)
  }

  private inline fun AnnotationVisitor.use(block: AnnotationVisitor.() -> Unit) {
    block(this)
    visitEnd()
  }

  private fun createAnvilModuleName(classDescriptor: ClassDescriptor): FqName {
    val name = "$MODULE_PACKAGE_PREFIX." +
      classDescriptor.findPackage().fqName.safePackageString() +
      classDescriptor.parentsWithSelf
        .filterIsInstance<ClassDescriptor>()
        .toList()
        .reversed()
        .joinToString(separator = "", postfix = ANVIL_MODULE_SUFFIX) {
          it.fqNameSafe
            .shortName()
            .asString()
        }
    return FqName(name)
  }

  private fun checkSameScope(
    contributedClass: ClassDescriptor,
    classDescriptorForReplacement: ClassDescriptor,
    module: ModuleDescriptor,
    scopeFqName: FqName
  ) {
    val contributesToAnnotation = classDescriptorForReplacement
      .annotationOrNull(contributesToFqName)
    val contributesBindingAnnotation = classDescriptorForReplacement
      .annotationOrNull(contributesBindingFqName)
    val contributesMultibindingAnnotation = classDescriptorForReplacement
      .annotationOrNull(contributesMultibindingFqName)

    // Verify that the replaced classes use the same scope.
    val scopeOfReplacement = contributesToAnnotation?.scope(module)
      ?: contributesBindingAnnotation?.scope(module)
      ?: contributesMultibindingAnnotation?.scope(module)
      ?: throw AnvilCompilationException(
        contributedClass,
        "Could not determine the scope of the replaced class " +
          "${classDescriptorForReplacement.fqNameSafe}."
      )

    if (scopeOfReplacement.fqNameSafe != scopeFqName) {
      throw AnvilCompilationException(
        contributedClass,
        "${contributedClass.fqNameSafe} with scope $scopeFqName wants to replace " +
          "${classDescriptorForReplacement.fqNameSafe} with scope " +
          "${scopeOfReplacement.fqNameSafe}. The replacement must use the same scope."
      )
    }
  }
}

@Suppress("DataClassPrivateConstructor")
private data class AnnotationHolder private constructor(
  val annotationDescriptor: AnnotationDescriptor,
  val annotationClass: KClass<*>,
  val daggerClass: KClass<*>,
  val daggerFqName: FqName,
  val modulesKeyword: String
) {
  val isComponent = annotationClass == MergeComponent::class
  val isModule = annotationClass == MergeModules::class

  companion object {
    fun create(descriptor: ClassDescriptor): AnnotationHolder? {
      descriptor.annotationOrNull(mergeComponentFqName)
        ?.let {
          return AnnotationHolder(
            it,
            MergeComponent::class,
            Component::class,
            daggerComponentFqName,
            "modules"
          )
        }
      descriptor.annotationOrNull(mergeSubcomponentFqName)
        ?.let {
          return AnnotationHolder(
            it,
            MergeSubcomponent::class,
            Subcomponent::class,
            daggerSubcomponentFqName,
            "modules"
          )
        }
      descriptor.annotationOrNull(mergeModulesFqName)
        ?.let {
          return AnnotationHolder(
            it,
            MergeModules::class,
            Module::class,
            daggerModuleFqName,
            "includes"
          )
        }
      return null
    }
  }
}

package com.squareup.anvil.compiler

import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.safePackageString
import dagger.Module
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.impl.IrDynamicTypeImpl
import org.jetbrains.kotlin.ir.util.defaultType
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.types.Variance.INVARIANT

internal class ModuleMergerIr(
  private val classScanner: ClassScanner
) : IrGenerationExtension {
  override fun generate(
    moduleFragment: IrModuleFragment,
    pluginContext: IrPluginContext
  ) {
    moduleFragment.transform(
      object : IrElementTransformerVoid() {
        override fun visitClass(declaration: IrClass): IrStatement {
          val annotationContext = AnnotationContext.create(declaration)
            ?: return super.visitClass(declaration)

          annotationContext.generateDaggerAnnotation(moduleFragment, pluginContext, declaration)
          return super.visitClass(declaration)
        }
      },
      null
    )
  }

  private fun AnnotationContext.generateDaggerAnnotation(
    moduleFragment: IrModuleFragment,
    pluginContext: IrPluginContext,
    declaration: IrClass
  ) {
    if (declaration.annotationOrNull(daggerFqName) != null) {
      throw AnvilCompilationException(
        message = "When using @${annotationFqName.shortName()} it's not allowed to annotate " +
          "the same class with @${daggerFqName.shortName()}. The Dagger annotation will " +
          "be generated.",
        element = declaration
      )
    }

    val scope = annotationConstructorCall.scope()
    val predefinedModules = annotationConstructorCall.argumentClassArray(modulesKeyword)

    val anvilModuleName = createAnvilModuleName(declaration)

    val modules = classScanner
      .findContributedClasses(
        pluginContext = pluginContext,
        moduleFragment = moduleFragment,
        packageName = HINT_CONTRIBUTES_PACKAGE_PREFIX,
        annotation = contributesToFqName,
        scope = scope
      )
      .filter {
        // We generate a Dagger module for each merged component. We use Anvil itself to
        // contribute this generated module. It's possible that there are multiple components
        // merging the same scope or the same scope is merged in different Gradle modules which
        // depend on each other. This would cause duplicate bindings, because the generated
        // modules contain the same bindings and are contributed to the same scope. To avoid this
        // issue we filter all generated Anvil modules except for the one that was generated for
        // this specific class.
        val fqName = it.fqName
        !fqName.isAnvilModule() || fqName == anvilModuleName
      }
      .mapNotNull {
        val contributesAnnotation =
          it.owner.annotationOrNull(contributesToFqName, scope = scope)
            ?: return@mapNotNull null
        it to contributesAnnotation
      }
      .filter { (classSymbol, _) ->
        val moduleAnnotation = classSymbol.owner.annotationOrNull(daggerModuleFqName)
        val mergeModulesAnnotation = classSymbol.owner.annotationOrNull(mergeModulesFqName)
        if (!classSymbol.owner.isInterface &&
          moduleAnnotation == null &&
          mergeModulesAnnotation == null
        ) {
          throw AnvilCompilationException(
            message = "${classSymbol.fqName} is annotated with " +
              "@${ContributesTo::class.simpleName}, but this class is neither an interface " +
              "nor a Dagger module. Did you forget to add @${Module::class.simpleName}?",
            element = classSymbol
          )
        }

        moduleAnnotation != null || mergeModulesAnnotation != null
      }
      .onEach { (classSymbol, _) ->
        if (classSymbol.owner.visibility != DescriptorVisibilities.PUBLIC) {
          throw AnvilCompilationException(
            message = "${classSymbol.fqName} is contributed to the Dagger graph, but the " +
              "module is not public. Only public modules are supported.",
            element = classSymbol
          )
        }
      }
      // Convert the sequence to a list to avoid iterating it twice. We use the result twice
      // for replaced classes and the final result.
      .toList()

    val excludedModules = annotationConstructorCall.exclude()
      .onEach { excludedClass ->
        val contributesToAnnotation = excludedClass
          .annotationOrNull(contributesToFqName)
        val contributesBindingAnnotation = excludedClass
          .annotationOrNull(contributesBindingFqName)
        val contributesMultibindingAnnotation = excludedClass
          .annotationOrNull(contributesMultibindingFqName)
        val contributesSubcomponentAnnotation = excludedClass
          .annotationOrNull(contributesSubcomponentFqName)

        // Verify that the replaced classes use the same scope.
        val scopeOfExclusion = contributesToAnnotation?.scope()
          ?: contributesBindingAnnotation?.scope()
          ?: contributesMultibindingAnnotation?.scope()
          ?: contributesSubcomponentAnnotation?.parentScope()
          ?: throw AnvilCompilationException(
            message = "Could not determine the scope of the excluded class " +
              "${excludedClass.fqName}.",
            element = declaration
          )

        if (scopeOfExclusion != scope) {
          throw AnvilCompilationException(
            message = "${declaration.fqName} with scope $scope wants to exclude " +
              "${excludedClass.fqName} with scope $scopeOfExclusion. The exclusion must " +
              "use the same scope.",
            element = declaration
          )
        }
      }

    val replacedModules = modules
      // Ignore replaced modules or bindings specified by excluded modules.
      .filter { (classSymbol, _) -> classSymbol.owner !in excludedModules }
      .flatMap { (classSymbol, contributesAnnotation) ->
        contributesAnnotation.replaces()
          .onEach { irClassForReplacement ->
            // Verify has @Module annotation. It doesn't make sense for a Dagger module to
            // replace a non-Dagger module.
            if (irClassForReplacement.annotationOrNull(daggerModuleFqName) == null &&
              irClassForReplacement.annotationOrNull(contributesBindingFqName) == null &&
              irClassForReplacement.annotationOrNull(contributesMultibindingFqName) == null
            ) {
              throw AnvilCompilationException(
                message = "${classSymbol.fqName} wants to replace " +
                  "${irClassForReplacement.fqName}, but the class being " +
                  "replaced is not a Dagger module.",
                element = classSymbol
              )
            }

            checkSameScope(classSymbol, irClassForReplacement, scope)
          }
      }

    fun replacedModulesByContributedBinding(
      annotationFqName: FqName,
      hintPackagePrefix: String
    ): Sequence<IrClass> {
      return classScanner
        .findContributedClasses(
          pluginContext = pluginContext,
          moduleFragment = moduleFragment,
          packageName = hintPackagePrefix,
          annotation = annotationFqName,
          scope = scope
        )
        .flatMap { classSymbol ->
          val annotation = classSymbol.owner.annotation(annotationFqName)
          if (scope == annotation.scope()) {
            annotation.replaces()
              .onEach { classDescriptorForReplacement ->
                checkSameScope(classSymbol, classDescriptorForReplacement, scope)
              }
          } else {
            emptyList()
          }
        }
    }

    val replacedModulesByContributedBindings = replacedModulesByContributedBinding(
      annotationFqName = contributesBindingFqName,
      hintPackagePrefix = HINT_BINDING_PACKAGE_PREFIX
    )

    val replacedModulesByContributedMultibindings = replacedModulesByContributedBinding(
      annotationFqName = contributesMultibindingFqName,
      hintPackagePrefix = HINT_MULTIBINDING_PACKAGE_PREFIX
    )

    if (predefinedModules.isNotEmpty()) {
      val intersect = predefinedModules.intersect(excludedModules.toSet())
      if (intersect.isNotEmpty()) {
        throw AnvilCompilationException(
          message = "${declaration.fqName} includes and excludes modules " +
            "at the same time: ${intersect.joinToString { it.fqName.asString() }}",
          element = declaration
        )
      }
    }

    val contributedModules = modules
      .asSequence()
      .map { it.first.owner }
      .minus(replacedModules.toSet())
      .minus(replacedModulesByContributedBindings.toSet())
      .minus(replacedModulesByContributedMultibindings.toSet())
      .minus(excludedModules.toSet())
      .plus(predefinedModules)
      .distinct()

    val annotationConstructorCall = IrConstructorCallImpl
      .fromSymbolOwner(
        startOffset = UNDEFINED_OFFSET,
        endOffset = UNDEFINED_OFFSET,
        type = pluginContext.requireReferenceClass(daggerFqName).defaultType,
        constructorSymbol = pluginContext
          .referenceConstructors(daggerFqName)
          .single { it.owner.isPrimary }
      )
      .apply {
        putValueArgument(
          index = 0,
          valueArgument = IrVarargImpl(
            startOffset = UNDEFINED_OFFSET,
            endOffset = UNDEFINED_OFFSET,
            type = pluginContext.symbols.array.defaultType,
            varargElementType = IrDynamicTypeImpl(null, emptyList(), INVARIANT),
            elements = contributedModules
              .map {
                IrClassReferenceImpl(
                  startOffset = UNDEFINED_OFFSET,
                  endOffset = UNDEFINED_OFFSET,
                  type = it.defaultType,
                  symbol = it.symbol,
                  classType = it.defaultType
                )
              }
              .toList()
          )
        )

        fun copyArrayValue(name: String) {
          declaration.annotation(annotationFqName, scope)
            .argument(name)
            ?.second
            ?.let { expression ->
              putValueArgument(1, expression)
            }
        }

        if (isComponent) {
          copyArrayValue("dependencies")
        }

        if (isModule) {
          copyArrayValue("subcomponents")
        }
      }

    declaration.annotations += annotationConstructorCall
  }

  private fun createAnvilModuleName(declaration: IrClass): FqName {
    val packageName = declaration.packageFqName?.safePackageString() ?: ""

    val name = "$MODULE_PACKAGE_PREFIX.$packageName" +
      declaration.parentsWithSelf
        .filterIsInstance<IrClass>()
        .toList()
        .reversed()
        .joinToString(separator = "", postfix = ANVIL_MODULE_SUFFIX) {
          it.name.asString()
        }
    return FqName(name)
  }

  private fun checkSameScope(
    contributedClass: IrClassSymbol,
    classDescriptorForReplacement: IrClass,
    scopeFqName: FqName
  ) {
    val contributesToAnnotation = classDescriptorForReplacement
      .annotationOrNull(contributesToFqName)
    val contributesBindingAnnotation = classDescriptorForReplacement
      .annotationOrNull(contributesBindingFqName)
    val contributesMultibindingAnnotation = classDescriptorForReplacement
      .annotationOrNull(contributesMultibindingFqName)

    // Verify that the replaced classes use the same scope.
    val scopeOfReplacement = contributesToAnnotation?.scope()
      ?: contributesBindingAnnotation?.scope()
      ?: contributesMultibindingAnnotation?.scope()
      ?: throw AnvilCompilationException(
        message = "Could not determine the scope of the replaced class " +
          "${classDescriptorForReplacement.fqName}.",
        element = contributedClass
      )

    if (scopeOfReplacement != scopeFqName) {
      throw AnvilCompilationException(
        message = "${contributedClass.fqName} with scope $scopeFqName wants to replace " +
          "${classDescriptorForReplacement.fqName} with scope $scopeOfReplacement. The " +
          "replacement must use the same scope.",
        element = contributedClass
      )
    }
  }
}

@Suppress("DataClassPrivateConstructor")
private data class AnnotationContext private constructor(
  val annotationConstructorCall: IrConstructorCall,
  val annotationFqName: FqName,
  val daggerFqName: FqName,
  val modulesKeyword: String
) {
  val isComponent = annotationFqName == mergeComponentFqName
  val isModule = annotationFqName == mergeModulesFqName

  companion object {
    fun create(declaration: IrClass): AnnotationContext? {
      declaration.annotationOrNull(mergeComponentFqName)
        ?.let {
          return AnnotationContext(
            it,
            mergeComponentFqName,
            daggerComponentFqName,
            "modules"
          )
        }
      declaration.annotationOrNull(mergeSubcomponentFqName)
        ?.let {
          return AnnotationContext(
            it,
            mergeSubcomponentFqName,
            daggerSubcomponentFqName,
            "modules"
          )
        }
      declaration.annotationOrNull(mergeModulesFqName)
        ?.let {
          return AnnotationContext(
            it,
            mergeModulesFqName,
            daggerModuleFqName,
            "includes"
          )
        }
      return null
    }
  }
}

package com.squareup.anvil.compiler

import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesBinding.Priority.NORMAL
import com.squareup.anvil.annotations.ContributesMultibinding
import com.squareup.anvil.annotations.ContributesSubcomponent
import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.annotations.MergeComponent
import com.squareup.anvil.annotations.MergeSubcomponent
import com.squareup.anvil.annotations.compat.MergeInterfaces
import com.squareup.anvil.annotations.compat.MergeModules
import com.squareup.anvil.compiler.internal.argumentType
import com.squareup.anvil.compiler.internal.fqName
import com.squareup.anvil.compiler.internal.getAnnotationValue
import com.squareup.anvil.compiler.internal.requireClassDescriptor
import dagger.Component
import dagger.Lazy
import dagger.MapKey
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.internal.DoubleCheck
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.BooleanValue
import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Qualifier

internal val mergeComponentFqName = MergeComponent::class.fqName
internal val mergeSubcomponentFqName = MergeSubcomponent::class.fqName
internal val mergeInterfacesFqName = MergeInterfaces::class.fqName
internal val mergeModulesFqName = MergeModules::class.fqName
internal val contributesToFqName = ContributesTo::class.fqName
internal val contributesBindingFqName = ContributesBinding::class.fqName
internal val contributesMultibindingFqName = ContributesMultibinding::class.fqName
internal val contributesSubcomponentFqName = ContributesSubcomponent::class.fqName
internal val contributesSubcomponentFactoryFqName = ContributesSubcomponent.Factory::class.fqName
internal val daggerComponentFqName = Component::class.fqName
internal val daggerSubcomponentFqName = Subcomponent::class.fqName
internal val daggerSubcomponentFactoryFqName = Subcomponent.Factory::class.fqName
internal val daggerSubcomponentBuilderFqName = Subcomponent.Builder::class.fqName
internal val daggerModuleFqName = Module::class.fqName
internal val daggerProvidesFqName = Provides::class.fqName
internal val daggerLazyFqName = Lazy::class.fqName
internal val injectFqName = Inject::class.fqName
internal val qualifierFqName = Qualifier::class.fqName
internal val mapKeyFqName = MapKey::class.fqName
internal val assistedFqName = Assisted::class.fqName
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 publishedApiFqName = PublishedApi::class.fqName

internal val daggerDoubleCheckFqNameString = DoubleCheck::class.java.canonicalName

internal const val HINT_CONTRIBUTES_PACKAGE_PREFIX = "anvil.hint.merge"
internal const val HINT_BINDING_PACKAGE_PREFIX = "anvil.hint.binding"
internal const val HINT_MULTIBINDING_PACKAGE_PREFIX = "anvil.hint.multibinding"
internal const val HINT_SUBCOMPONENTS_PACKAGE_PREFIX = "anvil.hint.subcomponent"
internal const val MODULE_PACKAGE_PREFIX = "anvil.module"
internal const val COMPONENT_PACKAGE_PREFIX = "anvil.component"

internal const val ANVIL_MODULE_SUFFIX = "AnvilModule"
// The suffix is a letter by design. Class names for subcomponents must be kept short.
internal const val ANVIL_SUBCOMPONENT_SUFFIX = "A"
internal const val PARENT_COMPONENT = "ParentComponent"
internal const val SUBCOMPONENT_FACTORY = "SubcomponentFactory"

internal const val REFERENCE_SUFFIX = "_reference"
internal const val SCOPE_SUFFIX = "_scope"
internal val propertySuffixes = arrayOf(REFERENCE_SUFFIX, SCOPE_SUFFIX)

internal fun FqName.isAnvilModule(): Boolean {
  val name = asString()
  return name.startsWith(MODULE_PACKAGE_PREFIX) && name.endsWith(ANVIL_MODULE_SUFFIX)
}

internal fun AnnotationDescriptor.boundTypeOrNull(module: ModuleDescriptor): ClassDescriptor? {
  return (getAnnotationValue("boundType") as? KClassValue)
    ?.argumentType(module)
    ?.requireClassDescriptor()
}

internal fun AnnotationDescriptor.replaces(module: ModuleDescriptor): List<ClassDescriptor> {
  return (getAnnotationValue("replaces") as? ArrayValue)
    ?.value
    ?.map { it.argumentType(module).requireClassDescriptor() }
    ?: emptyList()
}

internal fun AnnotationDescriptor.priority(): ContributesBinding.Priority {
  val enumValue = getAnnotationValue("priority") as? EnumValue ?: return NORMAL
  return ContributesBinding.Priority.valueOf(enumValue.enumEntryName.asString())
}

internal fun AnnotationDescriptor.ignoreQualifier(): Boolean {
  return (getAnnotationValue("ignoreQualifier") as? BooleanValue)?.value ?: false
}

internal fun AnnotationDescriptor.isMapKey(): Boolean {
  return annotationClass?.annotations?.hasAnnotation(mapKeyFqName) ?: false
}

package com.squareup.anvil.plugin

import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import javax.inject.Inject
import kotlin.DeprecationLevel.HIDDEN

public abstract class AnvilExtension @Inject constructor(objects: ObjectFactory) {
  /**
   * Allows you to use Anvil to generate Factory classes that usually the Dagger annotation
   * processor would generate for @Provides methods, @Inject constructors and @Inject fields.
   *
   * The benefit of this feature is that you don't need to enable the Dagger annotation processor
   * in this module. That often means you can skip KAPT and the stub generating task. In addition
   * Anvil generates Kotlin instead of Java code, which allows Gradle to skip the Java compilation
   * task. The result is faster builds.
   *
   * This feature can only be enabled in Gradle modules that don't compile any Dagger component.
   * Since Anvil only processes Kotlin code, you shouldn't enable it in modules with mixed Kotlin /
   * Java sources either.
   *
   * By default this feature is disabled.
   */
  public val generateDaggerFactories: Property<Boolean> = objects.property(Boolean::class.java)
    .convention(false)

  /**
   * There are occasions where consumers of Anvil are only interested in generating Dagger
   * factories to speed up build times and don't want to make use of the other features. With this
   * flag Anvil will only generate Dagger factories and skip all other steps. If this flag is set
   * to `true`, then also [generateDaggerFactories] must be set to `true`.
   */
  public val generateDaggerFactoriesOnly: Property<Boolean> = objects.property(Boolean::class.java)
    .convention(false)

  /**
   * Enabling this indicates that only code generation should run and no component merging should
   * run. This is useful for cases where you want to use `@ContributesTo`, `@ContributesBinding`,
   * or similar annotations but _not_ `@MergeComponent` or `@MergeSubcomponent` functionality.
   * This allows for anvil use in projects with kapt enabled but _not_ require disabling
   * incremental compilation in kapt stub generation tasks.
   */
  public val disableComponentMerging: Property<Boolean> = objects.property(Boolean::class.java)
    .convention(false)

  /**
   * Add Anvil generated source directories to sourceSets in Gradle for indexing visibility in the
   * IDE. This can be useful in debugging and is disabled by default.
   */
  public val syncGeneratedSources: Property<Boolean> = objects.property(Boolean::class.java)
    .convention(false)

  @Suppress("PropertyName")
  internal var _variantFilter: Action<VariantFilter>? = null

  /**
   * Configures each variant of this project. For Android projects these are the respective
   * Android variants, for JVM projects these are usually the main and test variant.
   */
  public fun variantFilter(action: Action<VariantFilter>) {
    _variantFilter = action
  }

  /*
   * The below properties are legacy former properties. We do a bit of Kotlin sugar to preserve
   * binary compatibility but not Kotlin source compatibility.
   *
   * @get:JvmName("getGenerateDaggerFactories") preserves the get signature with the old name. Even
   * though this has the same name as the new property, it returns a Boolean instead of a Property.
   * This is not legal in source but it _is_ legal in the JVM at runtime, so they are
   * binary-compatible.
   *
   * @set:JvmName("setGenerateDaggerFactories") preserves the set signature with the old name. Since
   * there is no setter for the new property, this has no overload ambiguity. We do hide it anyway
   * with JvmSynthetic just to keep the API lean and simple.
   *
   * @JvmSynthetic hides this from non-Kotlin callers to avoid overload ambiguity.
   *
   * DeprecationLevel.HIDDEN further hides this from Kotlin users (since it's a source-breaking
   * change anyway). Kotlin previously compiled against this will still work at runtime, but new
   * sources will now point to the new property and require using `.set()` and `.get()`
   */

  @get:[JvmName("getGenerateDaggerFactories") JvmSynthetic]
  @set:[JvmName("setGenerateDaggerFactories") JvmSynthetic]
  public var generateDaggerFactoriesLegacy: Boolean
    @Deprecated(
      "Use generateDaggerFactories property",
      ReplaceWith("generateDaggerFactories.get()"),
      level = HIDDEN
    )
    get() = generateDaggerFactories.get()
    @Deprecated(
      "Use generateDaggerFactories property",
      ReplaceWith("generateDaggerFactories.set(value)"),
      level = HIDDEN
    )
    set(value) = generateDaggerFactories.set(value)

  @get:[JvmName("getDaggerFactoriesOnly") JvmSynthetic]
  @set:[JvmName("setDaggerFactoriesOnly") JvmSynthetic]
  public var generateDaggerFactoriesOnlyLegacy: Boolean
    @Deprecated(
      "Use generateDaggerFactoriesOnly property",
      ReplaceWith("generateDaggerFactoriesOnly.get()"),
      level = HIDDEN
    )
    get() = generateDaggerFactoriesOnly.get()
    @Deprecated(
      "Use generateDaggerFactoriesOnly property",
      ReplaceWith("generateDaggerFactoriesOnly.set(value)"),
      level = HIDDEN
    )
    set(value) = generateDaggerFactoriesOnly.set(value)

  @get:[JvmName("getDisableComponentMerging") JvmSynthetic]
  @set:[JvmName("setDisableComponentMerging") JvmSynthetic]
  public var disableComponentMergingLegacy: Boolean
    @Deprecated(
      "Use disableComponentMerging property",
      ReplaceWith("disableComponentMerging.get()"),
      level = HIDDEN
    )
    get() = disableComponentMerging.get()
    @Deprecated(
      "Use disableComponentMerging property",
      ReplaceWith("disableComponentMerging.set(value)"),
      level = HIDDEN
    )
    set(value) = disableComponentMerging.set(value)
}

@file:Suppress("UnstableApiUsage")

package com.squareup.anvil.plugin

import com.android.build.api.dsl.AndroidSourceSet
import com.android.build.gradle.AppExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.TestExtension
import com.android.build.gradle.TestedExtension
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.api.TestVariant
import com.android.build.gradle.api.UnitTestVariant
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.UnknownTaskException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
import org.jetbrains.kotlin.gradle.plugin.FilesSubpluginOption
import org.jetbrains.kotlin.gradle.plugin.KaptExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.androidJvm
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm
import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import java.util.concurrent.ConcurrentHashMap

@Suppress("unused")
internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {

  private val variantCache = ConcurrentHashMap<String, Variant>()

  override fun apply(target: Project) {
    target.extensions.create("anvil", AnvilExtension::class.java)

    // Create a configuration for collecting CodeGenerator dependencies. We need to create all
    // configurations eagerly and cannot wait for applyToCompilation(..) below, because this
    // function is called in an afterEvaluate block by the Kotlin Gradle Plugin. That's too late
    // to register the configurations.
    val commonConfiguration = getConfiguration(target, buildType = "")

    // anvilTest is the common test configuration similar to other configurations like kaptTest.
    // Android build type specific unit test variants will extend this variant below. For the JVM
    // this single variant is enough.
    val testConfiguration = getConfiguration(target, buildType = "test").apply {
      extendsFrom(commonConfiguration)
    }

    agpPlugins.forEach { agpPlugin ->
      target.pluginManager.withPlugin(agpPlugin) {
        // This is the common android test variant, similar to anvilTest above.
        val androidTestVariant = getConfiguration(target, buildType = "androidTest").apply {
          extendsFrom(commonConfiguration)
        }

        target.androidVariantsConfigure { variant ->
          // E.g. "anvilDebug", "anvilTestRelease", ...
          val configuration = getConfiguration(target, buildType = variant.name)

          when (variant) {
            is UnitTestVariant -> configuration.extendsFrom(testConfiguration)
            is TestVariant -> configuration.extendsFrom(androidTestVariant)
            // non-test variants like "debug" extend the main config
            else -> configuration.extendsFrom(commonConfiguration)
          }
        }
      }
    }

    jvmPlugins.forEach { javaPlugin ->
      target.pluginManager.withPlugin(javaPlugin) {
        // Without this connection anvil(..) dependencies won't be picked up in the main build
        // for JVM modules. We already do this for the test configuration above, which is shared
        // between JVM and Android. The main configuration is specific to the JVM.
        getConfiguration(target, "main").extendsFrom(commonConfiguration)
      }
    }
  }

  override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
    return when (kotlinCompilation.platformType) {
      // If the variant is ignored, then don't apply the compiler plugin.
      androidJvm, jvm -> !getVariant(kotlinCompilation).variantFilter.ignore
      else -> false
    }
  }

  override fun applyToCompilation(
    kotlinCompilation: KotlinCompilation<*>
  ): Provider<List<SubpluginOption>> {
    val variant = getVariant(kotlinCompilation)
    val project = variant.project

    if (!variant.variantFilter.generateDaggerFactories &&
      variant.variantFilter.generateDaggerFactoriesOnly
    ) {
      throw GradleException(
        "You cannot set generateDaggerFactories to false and generateDaggerFactoriesOnly " +
          "to true at the same time for variant ${variant.name}."
      )
    }

    // Make the kotlin compiler classpath extend our configurations to pick up our extra
    // generators.
    project.configurations.getByName(variant.compilerPluginClasspathName)
      .extendsFrom(getConfiguration(project, variant.name))

    disableIncrementalKotlinCompilation(variant)
    disablePreciseJavaTracking(variant)

    if (!variant.variantFilter.generateDaggerFactoriesOnly) {
      disableCorrectErrorTypes(variant)

      kotlinCompilation.dependencies {
        implementation("$GROUP:annotations:$VERSION")
      }
    }

    // Notice that we use the name of the variant as a directory name. Generated code
    // for this specific compile task will be included in the task output. The output of different
    // compile tasks shouldn't be mixed.
    val srcGenDir = File(
      project.buildDir,
      "anvil${File.separator}src-gen-${variant.name}"
    )

    if (variant.variantFilter.syncGeneratedSources) {
      val isIdeSyncProvider = project.providers
        .systemProperty("idea.sync.active")
        .forUseAtConfigurationTime()

      if (isIdeSyncProvider.getOrElse("false").toBoolean()) {
        // Only add source sets during the IDE sync. Don't add them for compilation, otherwise
        // we'll see weird compile errors especially with incremental compilation. For a longer
        // explanation why this is a bad idea, see here:
        // https://github.com/square/anvil/pull/207#issuecomment-850768750
        kotlinCompilation.defaultSourceSet {
          kotlin.srcDir(srcGenDir)
        }

        // For Android and AGP the above code doesn't work for some reason. This is the workaround.
        variant.androidSourceSets?.forEach { sourceSet ->
          sourceSet.java.srcDir(srcGenDir)
        }
      }
    }

    return project.provider {
      listOf(
        FilesSubpluginOption(
          key = "src-gen-dir",
          files = listOf(srcGenDir)
        ),
        SubpluginOption(
          key = "generate-dagger-factories",
          lazy { variant.variantFilter.generateDaggerFactories.toString() }
        ),
        SubpluginOption(
          key = "generate-dagger-factories-only",
          lazy { variant.variantFilter.generateDaggerFactoriesOnly.toString() }
        ),
        SubpluginOption(
          key = "disable-component-merging",
          lazy { variant.variantFilter.disableComponentMerging.toString() }
        )
      )
    }
  }

  override fun getCompilerPluginId(): String = "com.squareup.anvil.compiler"

  override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
    groupId = GROUP,
    artifactId = "compiler",
    version = VERSION
  )

  private fun disablePreciseJavaTracking(variant: Variant) {
    val configureAction: (KotlinCompile) -> Unit = { compileTask ->
      val result = CheckMixedSourceSet.preparePreciseJavaTrackingCheck(variant)

      compileTask.doFirstCompat {
        // Disable precise java tracking if needed. Note that the doFirst() action only runs
        // if the task is not up to date. That's ideal, because if nothing needs to be
        // compiled, then we don't need to disable the flag.
        //
        // We also use the doFirst block to walk through the file system at execution time
        // and minimize the IO at configuration time.
        CheckMixedSourceSet.disablePreciseJavaTrackingIfNeeded(compileTask, result)

        compileTask.log(
          "Anvil: Use precise java tracking: ${compileTask.usePreciseJavaTracking}"
        )
      }
    }

    variant.compileTaskProvider.configure(configureAction)

    variant.project.pluginManager.withPlugin(KAPT_PLUGIN_ID) {
      variant.project
        .namedLazy<KaptGenerateStubsTask>(variant.stubsTaskName) { stubsTaskProvider ->
          stubsTaskProvider.configure(configureAction)
        }
    }
  }

  private fun disableCorrectErrorTypes(variant: Variant) {
    variant.project.pluginManager.withPlugin(KAPT_PLUGIN_ID) {
      // This needs to be disabled, otherwise compiler plugins fail in weird ways when
      // generating stubs, e.g.:
      //
      // /anvil/sample/app/build/generated/source/kapt/debug/com/squareup/anvil
      // /sample/DaggerAppComponent.java:13: error: DaggerAppComponent is not abstract and does
      // not override abstract method string() in RandomComponent
      // public final class DaggerAppComponent implements AppComponent {
      //              ^
      // 1 error
      variant.project.extensions.findByType(KaptExtension::class.java)?.correctErrorTypes = false
    }
  }

  private fun disableIncrementalKotlinCompilation(variant: Variant) {
    // Use this signal to share state between DisableIncrementalCompilationTask and the Kotlin
    // compile task. If the plugin classpath changed, then DisableIncrementalCompilationTask sets
    // the signal to false.
    @Suppress("UnstableApiUsage")
    val incrementalSignal = IncrementalSignal.registerIfAbsent(variant.project)

    disableIncrementalCompilationWhenTheCompilerClasspathChanges(
      variant, incrementalSignal, variant.compileTaskProvider
    )

    variant.project.pluginManager.withPlugin(KAPT_PLUGIN_ID) {
      variant.project
        .namedLazy<KaptGenerateStubsTask>(variant.stubsTaskName) { stubsTaskProvider ->
          if (variant.variantFilter.generateDaggerFactoriesOnly ||
            variant.variantFilter.disableComponentMerging
          ) {
            // We don't need to disable the incremental compilation for the stub generating task
            // every time, when we only generate Dagger factories or contribute modules. That's only
            // needed for merging Dagger modules.
            //
            // However, we still need to update the generated code when the compiler plugin classpath
            // changes.
            disableIncrementalCompilationWhenTheCompilerClasspathChanges(
              variant, incrementalSignal, stubsTaskProvider
            )
          } else {
            stubsTaskProvider.configure { stubsTask ->
              // Disable incremental compilation for the stub generating task. Trigger the compiler
              // plugin if any dependencies in the compile classpath have changed. This will make sure
              // that we pick up any change from a dependency when merging all the classes. Without
              // this workaround we could make changes in any library, but these changes wouldn't be
              // contributed to the Dagger graph, because incremental compilation tricked us.
              stubsTask.doFirstCompat {
                stubsTask.incremental = false
                stubsTask.log(
                  "Anvil: Incremental compilation enabled: ${stubsTask.incremental} (stub)"
                )
              }
            }
          }
        }
    }
  }

  private fun disableIncrementalCompilationWhenTheCompilerClasspathChanges(
    variant: Variant,
    incrementalSignal: Provider<IncrementalSignal>,
    compileTaskProvider: TaskProvider<out KotlinCompile>
  ) {
    val compileTaskName = compileTaskProvider.name

    // Disable incremental compilation, if the compiler plugin dependency isn't up-to-date.
    // This will trigger a full compilation of a module using Anvil even though its
    // source files might not have changed. This workaround is necessary, otherwise
    // incremental builds are broken. See https://youtrack.jetbrains.com/issue/KT-38570
    val disableIncrementalCompilationTaskProvider = variant.project.tasks.register(
      compileTaskName + "CheckIncrementalCompilationAnvil",
      DisableIncrementalCompilationTask::class.java
    ) { task ->
      task.pluginClasspath.from(
        variant.project.configurations.getByName(variant.compilerPluginClasspathName)
      )
      task.incrementalSignal.set(incrementalSignal)
    }

    compileTaskProvider.configure {
      it.dependsOn(disableIncrementalCompilationTaskProvider)
    }

    // We avoid a reference to the project in the doFirst.
    val projectPath = variant.project.path

    compileTaskProvider.configure { compileTask ->
      compileTask.doFirstCompat {
        // If the signal is set, then the plugin classpath changed. Apply the setting that
        // DisableIncrementalCompilationTask requested.
        val incremental = incrementalSignal.get().isIncremental(projectPath)
        if (incremental != null) {
          compileTask.incremental = incremental
        }

        compileTask.log(
          "Anvil: Incremental compilation enabled: ${compileTask.incremental} (compile)"
        )
      }
    }
  }

  private fun getConfiguration(
    project: Project,
    buildType: String
  ): Configuration {
    val name =
      if (buildType.isEmpty()) "anvil" else "anvil${buildType.replaceFirstChar(Char::uppercase)}"
    return project.configurations.maybeCreate(name).apply {
      description = "This configuration is used for dependencies with Anvil CodeGenerator " +
        "implementations."
    }
  }

  private fun getVariant(kotlinCompilation: KotlinCompilation<*>): Variant {
    return variantCache.computeIfAbsent(kotlinCompilation.name) {
      val variant = Variant(kotlinCompilation)

      // The cache makes sure that we execute this filter action only once.
      variant.project.extensions.getByType(AnvilExtension::class.java)
        ._variantFilter
        ?.execute(variant.variantFilter)

      variant
    }
  }
}

/*
 * Don't convert this to a lambda to avoid this error:
 *
 * The task was not up-to-date because of the following reasons:
 * Additional action for task ':sample:library:compileKotlin': was implemented by the Java
 * lambda 'com.squareup.anvil.plugin.AnvilPlugin$$Lambda$7578/0x0000000801769440'. Using Java
 * lambdas is not supported, use an (anonymous) inner class instead.
 *
 * Sample build scan: https://scans.gradle.com/s/ppwb2psllveks/timeline?details=j62m2xx52wwxy
 *
 * More context is here: https://github.com/gradle/gradle/issues/5510#issuecomment-416860213
 */
@Suppress("ObjectLiteralToLambda")
private fun <T : Task> T.doFirstCompat(block: (T) -> Unit) {
  doFirst(object : Action<Task> {
    override fun execute(task: Task) {
      @Suppress("UNCHECKED_CAST")
      block(task as T)
    }
  })
}

/**
 * Similar to [TaskContainer.named], but waits until the task is registered if it doesn't exist,
 * yet. If the task is never registered, then this method will throw an error after the
 * configuration phase.
 */
private inline fun <reified T : Task> Project.namedLazy(
  name: String,
  crossinline action: (TaskProvider<T>) -> Unit
) {
  try {
    action(tasks.named(name, T::class.java))
    return
  } catch (ignored: UnknownTaskException) {
  }

  var didRun = false

  tasks.withType(T::class.java) { task ->
    if (task.name == name) {
      action(tasks.named(name, T::class.java))
      didRun = true
    }
  }

  afterEvaluate {
    if (!didRun) {
      throw GradleException("Didn't find task $name with type ${T::class}.")
    }
  }
}

/**
 * Runs the given [action] for each Android variant including androidTest and unit test variants.
 */
private fun Project.androidVariantsConfigure(action: (BaseVariant) -> Unit) {
  val androidExtension = extensions.findByName("android")

  when (androidExtension) {
    is AppExtension -> androidExtension.applicationVariants.configureEach(action)
    is LibraryExtension -> androidExtension.libraryVariants.configureEach(action)
    is TestExtension -> androidExtension.applicationVariants.configureEach(action)
  }

  if (androidExtension is TestedExtension) {
    androidExtension.unitTestVariants.configureEach(action)
    androidExtension.testVariants.configureEach(action)
  }
}

// I considered extending this list with 'java-library' and 'application', but they both apply
// the 'java' plugin implicitly. One could also apply the 'java' plugin alone without the
// application or library plugin, so 'java' must be included in this list.
private val jvmPlugins = listOf(
  "java"
)

private val agpPlugins = listOf(
  "com.android.library",
  "com.android.application",
  "com.android.test",
  "com.android.dynamic-feature",
)

private const val KAPT_PLUGIN_ID = "org.jetbrains.kotlin.kapt"

internal class Variant private constructor(
  val name: String,
  val project: Project,
  val compileTaskProvider: TaskProvider<KotlinCompile>,
  val androidVariant: BaseVariant?,
  val androidSourceSets: List<AndroidSourceSet>?,
  val compilerPluginClasspathName: String,
  val variantFilter: VariantFilter,
) {
  // E.g. compileKotlin, compileKotlinJvm, compileDebugKotlin.
  private val taskSuffix = compileTaskProvider.name.substringAfter("compile")
  val stubsTaskName = "kaptGenerateStubs$taskSuffix"

  companion object {
    operator fun invoke(kotlinCompilation: KotlinCompilation<*>): Variant {
      // Sanity check.
      require(
        kotlinCompilation.platformType != androidJvm ||
          kotlinCompilation is KotlinJvmAndroidCompilation
      ) {
        "The KotlinCompilation is KotlinJvmAndroidCompilation, but the platform type " +
          "is different."
      }

      val project = kotlinCompilation.target.project
      val extension = project.extensions.getByType(AnvilExtension::class.java)
      val androidVariant = (kotlinCompilation as? KotlinJvmAndroidCompilation)?.androidVariant

      val androidSourceSets = if (androidVariant != null) {
        val sourceSetsByName = project.extensions.getByType(BaseExtension::class.java)
          .sourceSets
          .associateBy { it.name }

        androidVariant.sourceSets.mapNotNull { sourceSetsByName[it.name] }
      } else {
        null
      }

      val commonFilter = CommonFilter(kotlinCompilation.name, extension)
      val variantFilter = if (androidVariant != null) {
        AndroidVariantFilter(commonFilter, androidVariant)
      } else {
        JvmVariantFilter(commonFilter)
      }

      @Suppress("UNCHECKED_CAST")
      return Variant(
        name = kotlinCompilation.name,
        project = project,
        compileTaskProvider = kotlinCompilation.compileKotlinTaskProvider as
          TaskProvider<KotlinCompile>,
        androidVariant = androidVariant,
        androidSourceSets = androidSourceSets,
        compilerPluginClasspathName = PLUGIN_CLASSPATH_CONFIGURATION_NAME +
          kotlinCompilation.target.targetName.replaceFirstChar(Char::uppercase) +
          kotlinCompilation.name.replaceFirstChar(Char::uppercase),
        variantFilter = variantFilter
      ).also {
        // Sanity check.
        check(it.compileTaskProvider.name.startsWith("compile"))
      }
    }
  }
}

package com.squareup.anvil.plugin

import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Property
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

/**
 * In a mixed Kotlin / Java source set the Kotlin compiler might crash with an error like this:
 *
 * java.lang.AssertionError: Duplicated JavaClassDescriptor a/b/c/Hello reported to IC.
 *
 * That's a known bug: https://youtrack.jetbrains.com/issue/KT-38576
 *
 * The workaround for now is to set the kotlin.incremental.usePreciseJavaTracking flag to false for
 * these module using this task.
 */
internal object CheckMixedSourceSet {

  fun preparePreciseJavaTrackingCheck(variant: Variant): Input =
    Input(
      sources = getAndroidSourceDirs(variant) ?: getJvmSourceDirs(variant),
      isKaptApplied = variant.project.isKaptApplied()
    )

  fun disablePreciseJavaTrackingIfNeeded(
    compileTask: KotlinCompile,
    input: Input
  ) {
    val hasJavaFile = input.sources
      ?.asSequence()
      ?.flatMap { it.walk() }
      ?.filter { it.isFile }
      ?.any { it.extension == "java" }
      ?: false

    // If there is Java file, then disable precise Java tracking.
    //
    // If Kapt is enabled then it usually generates Java code making Kotlin compilation use a
    // mixed source set.
    if (hasJavaFile || input.isKaptApplied.get()) {
      compileTask.usePreciseJavaTracking = false
    }
  }

  private fun getAndroidSourceDirs(variant: Variant): FileCollection? {
    return variant.androidVariant
      ?.sourceSets
      ?.flatMap { it.javaDirectories }
      ?.let { variant.project.files(it) }
  }

  private fun getJvmSourceDirs(variant: Variant): FileCollection? {
    return variant.project.extensions.findByType(JavaPluginExtension::class.java)
      ?.sourceSets
      ?.single { it.name == variant.name }
      ?.allJava
  }

  private fun Project.isKaptApplied(): Property<Boolean> =
    objects
      .property(Boolean::class.java)
      .also {
        it.set(plugins.hasPlugin(Kapt3GradleSubplugin::class.java))
      }

  class Input(
    val sources: FileCollection?,
    val isKaptApplied: Property<Boolean>
  )
}

@file:Suppress("UnstableApiUsage")

package com.squareup.anvil.plugin

import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File

// Workaround for https://youtrack.jetbrains.com/issue/KT-38570
internal abstract class DisableIncrementalCompilationTask : DefaultTask() {

  @Suppress("unused")
  @get:Classpath
  @get:InputFiles
  abstract val pluginClasspath: ConfigurableFileCollection

  @Suppress("unused")
  @get:OutputFile @get:Optional
  val someFile = File(project.buildDir, "not-existing-file-because-gradle-needs-an-output")

  @get:Internal
  abstract val incrementalSignal: Property<IncrementalSignal>

  private val projectPath = project.path

  @TaskAction fun toggleFlag() {
    // Disable incremental compilation if something in the classpath changed. If nothing has changed
    // in the classpath, then this task wouldn't run at all and be skipped.
    incrementalSignal.get().setIncremental(projectPath, false)
  }
}

package com.squareup.anvil.plugin

import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters.None
import kotlin.random.Random

/*
 * The key is important. Gradle uses different class loaders for different modules when the plugin
 * is applied through the new plugins { } syntax. If we'd use the same key, then the
 * IncrementalSignal class would be different for the same entry and Gradle would throw an error.
 *
 * By using a random key per class loader we avoid this issue. Since this service isn't shared
 * across modules and only within a module it's not causing any problems tasks only use the service
 * for their project.
 *
 * We could also create one service per module, but that seems wasteful. When the Gradle plugin is
 * added to classpath of the root project, then the key is always the same and all modules share
 * a single build service and a single cache.
 */
private val KEY = "incrementalSignal-" + Random.nextLong()

/** This signal is used to share state between the task above and Kotlin compile tasks. */
@Suppress("UnstableApiUsage")
internal abstract class IncrementalSignal : BuildService<None> {
  private val incremental = mutableMapOf<String, Boolean?>()

  @Synchronized fun setIncremental(
    projectPath: String,
    incremental: Boolean
  ) {
    this.incremental[projectPath] = incremental
  }

  @Synchronized fun isIncremental(projectPath: String): Boolean? = incremental[projectPath]

  companion object {
    fun registerIfAbsent(project: Project): Provider<IncrementalSignal> =
      project.gradle.sharedServices.registerIfAbsent(KEY, IncrementalSignal::class.java) { }
  }
}

package com.squareup.anvil.plugin

import com.android.build.gradle.api.BaseVariant
import org.gradle.api.Named

public interface VariantFilter : Named {

  /**
   * Indicate whether or not to ignore Anvil for this particular variant. Default is false.
   */
  public var ignore: Boolean

  /**
   * Indicate whether Dagger factory generation for this variant should be enabled. The default
   * value comes from the [AnvilExtension]. See [AnvilExtension.generateDaggerFactories] for more
   * details.
   */
  public var generateDaggerFactories: Boolean

  /**
   * Indicate whether only Dagger factories for this variant should be generated. The default
   * value comes from the [AnvilExtension]. See [AnvilExtension.generateDaggerFactoriesOnly] for
   * more details.
   */
  public var generateDaggerFactoriesOnly: Boolean

  /**
   * Indicate whether component merging for this variant should be disabled. The default
   * value comes from the [AnvilExtension]. See [AnvilExtension.disableComponentMerging] for more
   * details.
   */
  public var disableComponentMerging: Boolean

  /**
   * Add Anvil generated source directories to sourceSets in Gradle for indexing visibility in the
   * IDE. This can be useful in debugging and is disabled by default.
   */
  public var syncGeneratedSources: Boolean
}

internal class CommonFilter(
  private val name: String,
  private val extension: AnvilExtension
) : VariantFilter {
  override fun getName(): String = name
  override var ignore: Boolean = false

  private var generateDaggerFactoriesOverride: Boolean? = null
  override var generateDaggerFactories: Boolean
    get() = generateDaggerFactoriesOverride ?: extension.generateDaggerFactories.get()
    set(value) {
      generateDaggerFactoriesOverride = value
    }

  private var generateDaggerFactoriesOnlyOverride: Boolean? = null
  override var generateDaggerFactoriesOnly: Boolean
    get() = generateDaggerFactoriesOnlyOverride ?: extension.generateDaggerFactoriesOnly.get()
    set(value) {
      generateDaggerFactoriesOnlyOverride = value
    }

  private var disableComponentMergingOverride: Boolean? = null
  override var disableComponentMerging: Boolean
    get() = disableComponentMergingOverride ?: extension.disableComponentMerging.get()
    set(value) {
      disableComponentMergingOverride = value
    }

  private var syncGeneratedSourcesOverride: Boolean? = null
  override var syncGeneratedSources: Boolean
    get() = syncGeneratedSourcesOverride ?: extension.syncGeneratedSources.get()
    set(value) {
      syncGeneratedSourcesOverride = value
    }
}

public class JvmVariantFilter internal constructor(
  commonFilter: CommonFilter
) : VariantFilter by commonFilter

public class AndroidVariantFilter internal constructor(
  commonFilter: CommonFilter,
  public val androidVariant: BaseVariant
) : VariantFilter by commonFilter

package com.squareup.scopes

object ComponentHolder {
  val components = mutableSetOf<Any>()

  inline fun <reified T> component(): T = components
    .filterIsInstance<T>()
    .single()
}

package com.squareup.anvil.annotations.compat

import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Marks an interface to merge all contributed component interfaces for the given [scope]. This is a
 * subset of what [MergeComponent] and [MergeSubcomponent] are doing.
 *
 * This annotation is useful if you can't use [MergeComponent] and [MergeSubcomponent] directly for
 * your Dagger component. The interface annotated with this annotation serves as a composite
 * component interface.
 *
 * ```
 * @MergeInterfaces(AppScope::class)
 * interface CompositeAppComponent
 * ```
 *
 * One common scenario where this annotation is used is when you want to use javac and not KAPT for
 * compiling your Dagger components. The recommendation there is to add an extra Gradle module that
 * merges interfaces. Your Dagger component must manually extend this interface using this annotation.
 *
 * ```
 * // Gradle Module A.
 * @MergeInterfaces(AppScope::class)
 * interface CompositeAppComponent
 *
 * // Gradle Module B, note that this is Java and not Kotlin.
 * @Component
 * public interface AppComponent extends CompositeAppComponent {}
 * ```
 *
 * It's possible to exclude any automatically added component interface with the [exclude] parameter
 * if needed.
 *
 * ```
 * @MergeInterfaces(
 *     scope = AppScope::class,
 *     exclude = [
 *       ComponentInterface::class
 *     ]
 * )
 * interface CompositeAppComponent
 * ```
 */

@Target(CLASS)
@Retention(RUNTIME)
public annotation class MergeInterfaces(
  /**
   * The scope used to find all contributed component interfaces, which should be included in this
   * merged interface.
   */
  val scope: KClass<*>,
  /**
   * List of component interfaces that are contributed to the same scope, but should be excluded
   * from the merged interface.
   */
  val exclude: Array<KClass<*>> = []
)

package com.squareup.anvil.annotations.compat

import com.squareup.anvil.annotations.MergeComponent
import com.squareup.anvil.annotations.MergeSubcomponent
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Marks a Dagger module to merge all contributed Dagger modules for the given [scope]. This is a
 * subset of what [MergeComponent] and [MergeSubcomponent] are doing. Use this annotation instead of
 * `@Module` in your module. The Kotlin compiler plugin will add the `@Module` annotation to this
 * class. The parameters [includes] and [subcomponents] are preserved in the `@Module` annotation.
 *
 * This annotation is useful if you can't use [MergeComponent] and [MergeSubcomponent] directly for
 * your Dagger component. The Dagger modules annotated with this annotation serve as a composite
 * module.
 *
 * ```
 * @MergeModules(AppScope::class)
 * abstract class CompositeAppModule
 * ```
 *
 * One common scenario where this annotation is used is when you want to use javac and not KAPT for
 * compiling your Dagger components. The recommendation there is to add an extra Gradle module that
 * merges modules. Your Dagger component must manually include this module using this annotation.
 *
 * ```
 * // Gradle Module A.
 * @MergeModules(AppScope::class)
 * abstract class CompositeAppModule
 *
 * // Gradle Module B, note that this is Java and not Kotlin.
 * @Component(modules = CompositeAppModule.class)
 * public interface AppComponent {}
 * ```
 *
 * It's possible to exclude any automatically added Dagger module with the [exclude] parameter
 * if needed.
 *
 * ```
 * @MergeModules(
 *     scope = AppScope::class,
 *     exclude = [
 *       DaggerModule::class
 *     ]
 * )
 * abstract class CompositeAppModule
 * ```
 */

@Target(CLASS)
@Retention(RUNTIME)
public annotation class MergeModules(
  /**
   * The scope used to find all contributed bindings, multibindings and modules, which should
   * be included in this merged module.
   */
  val scope: KClass<*>,
  /**
   * List of Dagger modules that should be manually included in the merged module and aren't
   * automatically contributed.
   */
  val includes: Array<KClass<*>> = [],
  /**
   * List of subcomponent annotated classes which should be children of the component in which
   * this module is installed.
   */
  val subcomponents: Array<KClass<*>> = [],
  /**
   * List of bindings, multibindings and modules that are contributed to the same scope, but
   * should be excluded from the merged module.
   */
  val exclude: Array<KClass<*>> = []
)

package com.squareup.anvil.compiler.api

import org.jetbrains.kotlin.codegen.CompilationException
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiNameIdentifierOwner
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.util.getExceptionMessage

/**
 * `AnvilCompilationException` is thrown when Anvil specific code can't be processed or an error
 * occurs while generating code.
 */
public class AnvilCompilationException(
  message: String,
  cause: Throwable? = null,
  element: PsiElement? = null
) : CompilationException(message, cause, element) {
  public companion object {
    public operator fun invoke(
      annotationDescriptor: AnnotationDescriptor,
      message: String,
      cause: Throwable? = null
    ): AnvilCompilationException {
      return AnvilCompilationException(
        message = message,
        cause = cause,
        element = annotationDescriptor.identifier
      )
    }

    public operator fun invoke(
      classDescriptor: ClassDescriptor,
      message: String,
      cause: Throwable? = null
    ): AnvilCompilationException {
      return AnvilCompilationException(
        message = message,
        cause = cause,
        element = classDescriptor.identifier
      )
    }

    public operator fun invoke(
      element: IrElement? = null,
      message: String,
      cause: Throwable? = null
    ): AnvilCompilationException {
      return AnvilCompilationException(
        message = getExceptionMessage(
          subsystemName = "Anvil",
          message = message,
          cause = cause,
          location = element?.render()
        ),
        cause = cause,
        element = element?.psi
      ).apply {
        if (element != null) {
          withAttachment("element.kt", element.render())
        }
      }
    }

    public operator fun invoke(
      element: IrSymbol? = null,
      message: String,
      cause: Throwable? = null,
    ): AnvilCompilationException {
      return AnvilCompilationException(
        message = message,
        cause = cause,
        element = element?.owner
      )
    }
  }
}

private val ClassDescriptor.identifier: PsiElement?
  get() = (findPsi() as? PsiNameIdentifierOwner)?.identifyingElement

private val AnnotationDescriptor.identifier: PsiElement?
  get() = (source as? KotlinSourceElement)?.psi

private val IrElement.psi: PsiElement?
  get() = when (this) {
    is IrClass -> (this.source.getPsi() as? PsiNameIdentifierOwner)?.identifyingElement
    else -> null
  }

package com.squareup.anvil.compiler.api

/**
 * Contains context about the given Anvil compiler invocation. Considered a read-only API.
 *
 * These loosely correspond to the Anvil Gradle plugin's options, but we may add other information
 * in the future.
 */
public interface AnvilContext {
  /**
   * Allows you to use Anvil to generate Factory classes that usually the Dagger annotation
   * processor would generate for @Provides methods, @Inject constructors and @Inject fields.
   *
   * The benefit of this feature is that you don't need to enable the Dagger annotation processor
   * in this module. That often means you can skip KAPT and the stub generating task. In addition
   * Anvil generates Kotlin instead of Java code, which allows Gradle to skip the Java compilation
   * task. The result is faster builds.
   *
   * This feature can only be enabled in Gradle modules that don't compile any Dagger component.
   * Since Anvil only processes Kotlin code, you shouldn't enable it in modules with mixed Kotlin /
   * Java sources either.
   *
   * By default this feature is disabled.
   */
  public val generateFactories: Boolean

  /**
   * There are occasions where consumers of Anvil are only interested in generating Dagger
   * factories to speed up build times and don't want to make use of the other features. With this
   * flag Anvil will only generate Dagger factories and skip all other steps. If this flag is set
   * to `true`, then also [generateDaggerFactories] must be set to `true`.
   */
  public val generateFactoriesOnly: Boolean

  /**
   * Enabling this indicates that only code generation should run and no component merging should
   * run. This is useful for cases where you want to use `@ContributesTo`, `@ContributesBinding`,
   * or similar annotations but _not_ `@MergeComponent` or `@MergeSubcomponent` functionality.
   * This allows for anvil use in projects with kapt enabled but _not_ require disabling
   * incremental compilation in kapt stub generation tasks.
   */
  public val disableComponentMerging: Boolean
}

package com.squareup.anvil.compiler.api

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

/**
 * This interface allows for easy extension of Anvil's core compiler functionality for cases where
 * you need to generate extra code during compilation.
 *
 * This interface is loaded via [java.util.ServiceLoader] and you should package a service file for
 * your implementations accordingly. As such, order of execution is **not** guaranteed.
 *
 * A [CodeGenerator] can generate arbitrary Kotlin code, whether it be for Anvil or Dagger or both!
 * Note that the generated code _must_ be Kotlin code. Java, arbitrary resource files, etc are not
 * supported.
 */
@ExperimentalAnvilApi
public interface CodeGenerator {

  /**
   * Returns true if this code generator is applicable for the given [context] or false if not. This
   * will only be called _once_.
   */
  public fun isApplicable(context: AnvilContext): Boolean

  /**
   * Called multiple times in order to create new code. Note that instances should not rely on
   * being able to resolve [projectFiles] to descriptors. They should rather use the Psi APIs to
   * parse files.
   *
   * In the first round code generators are called with the files from the project. In following
   * rounds [projectFiles] represents files that were generated by code generators until no
   * code generator produces any files anymore.
   */
  public fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile>
}

/**
 * Write [content] into a new file for the given [packageName] and [fileName]. [fileName] usually
 * refers to the class name.
 */
@ExperimentalAnvilApi
@Suppress("unused")
public fun CodeGenerator.createGeneratedFile(
  codeGenDir: File,
  packageName: String,
  fileName: String,
  content: String
): GeneratedFile {
  val directory = File(codeGenDir, packageName.replace('.', File.separatorChar))
  val file = File(directory, "$fileName.kt")
  check(file.parentFile.exists() || file.parentFile.mkdirs()) {
    "Could not generate package directory: ${file.parentFile}"
  }
  file.writeText(content)

  return GeneratedFile(file, content)
}

package com.squareup.anvil.compiler.api

import java.io.File

/**
 * Represents a generated file that Anvil should eventually write.
 *
 * @property file the [File] to generate.
 * @property content the file contents to write to [file].
 */
public data class GeneratedFile(
  val file: File,
  val content: String
)

package com.squareup.anvil.compiler.internal

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile

@ExperimentalAnvilApi
public interface AnvilModuleDescriptor : ModuleDescriptor {
  public fun resolveClassIdOrNull(classId: ClassId): FqName?
  public fun getClassesAndInnerClasses(ktFile: KtFile): List<KtClassOrObject>
  public fun getKtClassOrObjectOrNull(fqName: FqName): KtClassOrObject?
}

@Suppress("NOTHING_TO_INLINE")
private inline fun ModuleDescriptor.asAnvilModuleDescriptor(): AnvilModuleDescriptor =
  this as AnvilModuleDescriptor

@ExperimentalAnvilApi
public fun ModuleDescriptor.resolveFqNameOrNull(
  fqName: FqName
): FqName? = asAnvilModuleDescriptor().resolveClassIdOrNull(fqName.classIdBestGuess())

@ExperimentalAnvilApi
public fun ModuleDescriptor.getKtClassOrObjectOrNull(
  fqName: FqName
): KtClassOrObject? = asAnvilModuleDescriptor().getKtClassOrObjectOrNull(fqName)

@ExperimentalAnvilApi
public fun ModuleDescriptor.requireKtClassOrObject(
  fqName: FqName
): KtClassOrObject = getKtClassOrObjectOrNull(fqName)
  ?: throw AnvilCompilationException("Couldn't resolve KtClassOrObject for ${fqName.asString()}")

@ExperimentalAnvilApi
public fun ModuleDescriptor.canResolveFqName(
  fqName: FqName
): Boolean = resolveFqNameOrNull(fqName) != null

@ExperimentalAnvilApi
public fun ModuleDescriptor.resolveFqNameOrNull(
  packageName: FqName,
  className: String
): FqName? = asAnvilModuleDescriptor()
  .resolveClassIdOrNull(ClassId(packageName, Name.identifier(className)))

@ExperimentalAnvilApi
public fun ModuleDescriptor.canResolveFqName(
  packageName: FqName,
  className: String
): Boolean = resolveFqNameOrNull(packageName, className) != null

@ExperimentalAnvilApi
public fun KtFile.classesAndInnerClasses(
  module: ModuleDescriptor
): List<KtClassOrObject> {
  return module.asAnvilModuleDescriptor().getClassesAndInnerClasses(this)
}

@ExperimentalAnvilApi
public fun Collection<KtFile>.classesAndInnerClass(
  module: ModuleDescriptor
): Sequence<KtClassOrObject> {
  return asSequence().flatMap { it.classesAndInnerClasses(module) }
}

package com.squareup.anvil.compiler.internal

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.ClassReference.Descriptor
import com.squareup.anvil.compiler.internal.ClassReference.Psi
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
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

/**
 * Used to create a common type between [KtClassOrObject] class references and [ClassDescriptor]
 * references, to streamline parsing.
 *
 * @see allSuperTypeClassReferences
 * @see requireClassReference
 */
@ExperimentalAnvilApi
public sealed class ClassReference {

  public abstract val classId: ClassId
  public abstract val fqName: FqName

  public class Psi internal constructor(
    public val clazz: KtClassOrObject,
    override val classId: ClassId
  ) : ClassReference() {
    override val fqName: FqName = classId.asSingleFqName()
  }

  public class Descriptor internal constructor(
    public val clazz: ClassDescriptor,
    override val classId: ClassId
  ) : ClassReference() {
    override val fqName: FqName = classId.asSingleFqName()
  }

  override fun toString(): String {
    return "${this::class.qualifiedName}($fqName)"
  }

  override fun equals(other: Any?): Boolean {
    if (this === other) return true
    if (other !is ClassReference) return false

    if (fqName != other.fqName) return false

    return true
  }

  override fun hashCode(): Int {
    return fqName.hashCode()
  }
}

/**
 * Attempts to resolve the [fqName] to a [ClassDescriptor] first, then falls back to a
 * [KtClassOrObject] if the descriptor resolution fails. This will happen if the code being parsed
 * was generated as part of the compilation round for this module.
 */
@ExperimentalAnvilApi
public fun ModuleDescriptor.classReferenceOrNull(fqName: FqName): ClassReference? {
  return fqName.classDescriptorOrNull(this)?.toClassReference()
    ?: getKtClassOrObjectOrNull(fqName)?.toClassReference()
}

@ExperimentalAnvilApi
public fun ClassDescriptor.toClassReference(): Descriptor {
  return Descriptor(this, this.requireClassId())
}

@ExperimentalAnvilApi
public fun KtClassOrObject.toClassReference(): Psi {
  return Psi(this, toClassId())
}

@ExperimentalAnvilApi
public fun ModuleDescriptor.requireClassReference(fqName: FqName): ClassReference {
  return classReferenceOrNull(fqName)
    ?: throw AnvilCompilationException("Couldn't resolve ClassReference for $fqName")
}

/**
 * This will return all super types as [ClassReference], whether they're parsed as [KtClassOrObject]
 * or [ClassDescriptor]. This will include generated code, assuming it has already been generated.
 * The returned sequence will be distinct by FqName, and Psi types are preferred over Descriptors.
 *
 * The first elements in the returned sequence represent the direct superclass to the receiver. The
 * last elements represent the types which are furthest up-stream.
 *
 * @param includeSelf If true, the receiver class is the first element of the sequence
 */
@ExperimentalAnvilApi
public fun ClassReference.allSuperTypeClassReferences(
  module: ModuleDescriptor,
  includeSelf: Boolean = false
): Sequence<ClassReference> {
  return generateSequence(sequenceOf(this)) { superTypes ->
    superTypes
      .map { classRef -> classRef.directSuperClassReferences(module) }
      .flatten()
      .takeIf { it.firstOrNull() != null }
  }
    .drop(if (includeSelf) 0 else 1)
    .flatten()
    .distinctBy { it.fqName }
}

@ExperimentalAnvilApi
public fun ClassReference.directSuperClassReferences(
  module: ModuleDescriptor
): Sequence<ClassReference> {
  return when (this) {
    is Descriptor -> clazz.directSuperClassAndInterfaces()
      .asSequence()
      .map { module.requireClassReference(it.fqNameSafe) }
    is Psi -> if (clazz is KtEnumEntry) {
      emptySequence()
    } else {
      clazz.superTypeListEntries
        .asSequence()
        .map { it.requireFqName(module) }
        .map { module.requireClassReference(it) }
    }
  }
}

/**
 * @param parameterName The name of the parameter to be found, not including any variance modifiers.
 * @return The 0-based index of a declared generic type.
 */
@ExperimentalAnvilApi
public fun ClassReference.indexOfTypeParameter(parameterName: String): Int {
  return when (this) {
    is Descriptor ->
      clazz.declaredTypeParameters
        .indexOfFirst { it.name.asString() == parameterName }
    is Psi ->
      clazz.typeParameters
        .indexOfFirst { it.identifyingElement?.text == parameterName }
  }
}

@ExperimentalAnvilApi
public fun ClassReference.scopeOrNull(
  module: ModuleDescriptor,
  annotationFqName: FqName
): FqName? {
  return when (this) {
    is Descriptor -> clazz.annotationOrNull(annotationFqName)
      ?.scope(module)
      ?.fqNameSafe
    is Psi -> clazz.scopeOrNull(annotationFqName, module)
  }
}

@ExperimentalAnvilApi
public fun ClassReference.qualifiers(
  module: ModuleDescriptor
): List<AnnotationSpec> {
  return when (this) {
    is Descriptor -> clazz.annotations.filter { it.isQualifier() }
      .map { it.toAnnotationSpec(module) }
    is Psi -> clazz.annotationEntries.filter { it.isQualifier(module) }
      .map { it.toAnnotationSpec(module) }
  }
}

@ExperimentalAnvilApi
public fun ClassReference.daggerScopes(
  module: ModuleDescriptor
): List<AnnotationSpec> = when (this) {
  is Descriptor -> clazz.annotations.filter { it.isDaggerScope() }
    .map { it.toAnnotationSpec(module) }
  is Psi -> clazz.annotationEntries.filter { it.isDaggerScope(module) }
    .map { it.toAnnotationSpec(module) }
}

@ExperimentalAnvilApi
public fun ClassReference.asClassName(): ClassName = classId.asClassName()

@ExperimentalAnvilApi
public fun ClassReference.isInterface(): Boolean = when (this) {
  is Descriptor -> clazz.kind == ClassKind.INTERFACE
  is Psi -> clazz.isInterface()
}

@file:Suppress("unused")

package com.squareup.anvil.compiler.internal

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import org.jetbrains.kotlin.codegen.asmType
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.has
import org.jetbrains.kotlin.platform.jvm.JvmPlatform
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.NormalClass
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.types.ErrorType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.org.objectweb.asm.Type

@ExperimentalAnvilApi
public fun ClassDescriptor.annotationOrNull(
  annotationFqName: FqName,
  scope: FqName? = null
): AnnotationDescriptor? {
  // Must be JVM, we don't support anything else.
  if (!module.platform.has<JvmPlatform>()) return null
  val annotationDescriptor = try {
    annotations.findAnnotation(annotationFqName)
  } catch (e: IllegalStateException) {
    // In some scenarios this exception is thrown. Throw a new exception with a better explanation.
    // Caused by: java.lang.IllegalStateException: Should not be called!
    // at org.jetbrains.kotlin.types.ErrorUtils$1.getPackage(ErrorUtils.java:95)
    throw AnvilCompilationException(
      this,
      message = "It seems like you tried to contribute an inner class to its outer class. This " +
        "is not supported and results in a compiler error.",
      cause = e
    )
  }
  return if (scope == null || annotationDescriptor == null) {
    annotationDescriptor
  } else {
    annotationDescriptor.takeIf { scope == it.scope(module).fqNameSafe }
  }
}

@ExperimentalAnvilApi
public fun ClassDescriptor.annotation(
  annotationFqName: FqName,
  scope: FqName? = null
): AnnotationDescriptor = requireNotNull(annotationOrNull(annotationFqName, scope)) {
  "Couldn't find $annotationFqName with scope $scope for $fqNameSafe."
}

/**
 * Returns only the super class (excluding [Any]) and implemented interfaces declared directly by
 * this class. This is different from [getAllSuperClassifiers] in that the latter returns the entire
 * hierarchy.
 */
public fun ClassDescriptor.directSuperClassAndInterfaces(): List<ClassDescriptor> {
  return listOfNotNull(getSuperClassNotAny())
    .plus(getSuperInterfaces())
}

@ExperimentalAnvilApi
public fun ConstantValue<*>.toType(
  module: ModuleDescriptor,
  typeMapper: KotlinTypeMapper
): Type {
  // This is a Kotlin class with the actual type as argument: KClass<OurType>
  return argumentType(module).asmType(typeMapper)
}

// When the Kotlin type is of the form: KClass<OurType>.
@ExperimentalAnvilApi
public fun KotlinType.argumentType(): KotlinType = arguments.first().type

@ExperimentalAnvilApi
public fun KotlinType.classDescriptorOrNull(): ClassDescriptor? {
  return TypeUtils.getClassDescriptor(this)
}

@ExperimentalAnvilApi
public fun KotlinType.requireClassDescriptor(): ClassDescriptor {
  return classDescriptorOrNull()
    ?: throw AnvilCompilationException(
      "Unable to resolve type for ${this.asTypeName()}"
    )
}

@ExperimentalAnvilApi
public fun AnnotationDescriptor.getAnnotationValue(key: String): ConstantValue<*>? =
  allValueArguments[Name.identifier(key)]

@ExperimentalAnvilApi
public fun AnnotationDescriptor.scope(module: ModuleDescriptor): ClassDescriptor {
  val annotationValue = getAnnotationValue("scope") as? KClassValue
    ?: throw AnvilCompilationException(
      annotationDescriptor = this,
      message = "Couldn't find scope for $fqName."
    )

  return annotationValue.argumentType(module).requireClassDescriptor()
}

@ExperimentalAnvilApi
public fun AnnotationDescriptor.parentScope(module: ModuleDescriptor): ClassDescriptor {
  val annotationValue = getAnnotationValue("parentScope") as? KClassValue
    ?: throw AnvilCompilationException(
      annotationDescriptor = this,
      message = "Couldn't find parentScope for $fqName."
    )

  return annotationValue.argumentType(module).requireClassDescriptor()
}

@ExperimentalAnvilApi
public fun ConstantValue<*>.argumentType(module: ModuleDescriptor): KotlinType {
  val argumentType = getType(module).argumentType()
  if (argumentType !is ErrorType) return argumentType

  // Handle inner classes explicitly. When resolving the Kotlin type of inner class from
  // dependencies the compiler might fail. It tries to load my.package.Class$Inner and fails
  // whereas is should load my.package.Class.Inner.
  val normalClass = this.value
  if (normalClass !is NormalClass) return argumentType

  val classId = normalClass.value.classId

  return module
    .findClassAcrossModuleDependencies(
      classId = ClassId(
        classId.packageFqName,
        FqName(classId.relativeClassName.asString().replace('$', '.')),
        false
      )
    )
    ?.defaultType
    ?: throw AnvilCompilationException(
      "Couldn't resolve class across module dependencies for class ID: $classId"
    )
}

@ExperimentalAnvilApi
public fun List<KotlinType>.getAllSuperTypes(): Sequence<FqName> =
  generateSequence(this) { kotlinTypes ->
    kotlinTypes.ifEmpty { null }?.flatMap { it.supertypes() }
  }
    .flatMap { it.asSequence() }
    .map { it.requireClassDescriptor().fqNameSafe }

@ExperimentalAnvilApi
public fun AnnotationDescriptor.isQualifier(): Boolean {
  return annotationClass?.annotations?.hasAnnotation(qualifierFqName) ?: false
}

@ExperimentalAnvilApi
public fun AnnotationDescriptor.isDaggerScope(): Boolean {
  return annotationClass?.annotations?.hasAnnotation(daggerScopeFqName) ?: false
}

@ExperimentalAnvilApi
public fun AnnotationDescriptor.requireClass(): ClassDescriptor {
  return annotationClass ?: throw AnvilCompilationException(
    message = "Couldn't find the annotation class for $fqName",
  )
}

@ExperimentalAnvilApi
public fun AnnotationDescriptor.requireFqName(): FqName {
  return fqName ?: throw AnvilCompilationException(
    message = "Couldn't find the fqName for $this",
  )
}

/**
 * This function should only be used for package names. If the FqName is the root (no package at
 * all), then this function returns an empty string whereas `toString()` would return "<root>". For
 * a more convenient string concatenation the returned result can be prefixed and suffixed with an
 * additional dot. The root package never will use a prefix or suffix.
 */
@ExperimentalAnvilApi
public fun FqName.safePackageString(
  dotPrefix: Boolean = false,
  dotSuffix: Boolean = true
): String =
  if (isRoot) {
    ""
  } else {
    val prefix = if (dotPrefix) "." else ""
    val suffix = if (dotSuffix) "." else ""
    "$prefix$this$suffix"
  }

@ExperimentalAnvilApi
public fun FqName.classIdBestGuess(): ClassId {
  val segments = pathSegments().map { it.asString() }
  val classNameIndex = segments.indexOfFirst { it[0].isUpperCase() }
  if (classNameIndex < 0) {
    return ClassId.topLevel(this)
  }

  val packageFqName = FqName.fromSegments(segments.subList(0, classNameIndex))
  val relativeClassName = FqName.fromSegments(segments.subList(classNameIndex, segments.size))
  return ClassId(packageFqName, relativeClassName, false)
}

@ExperimentalAnvilApi
public fun String.capitalize(): String = replaceFirstChar(Char::uppercaseChar)

@ExperimentalAnvilApi
public fun String.decapitalize(): String = replaceFirstChar(Char::lowercaseChar)

@ExperimentalAnvilApi
public fun FqName.generateClassName(
  separator: String = "_",
  suffix: String = ""
): ClassId = classIdBestGuess().generateClassName(separator = separator, suffix = suffix)

@ExperimentalAnvilApi
public fun ClassId.generateClassName(
  separator: String = "_",
  suffix: String = ""
): ClassId {
  val className = relativeClassName.asString().replace(".", separator)
  return ClassId(packageFqName, FqName(className + suffix), false)
}

@ExperimentalAnvilApi
public fun ClassDescriptor.requireClassId(): ClassId {
  return classId ?: throw AnvilCompilationException(
    classDescriptor = this,
    message = "Couldn't find the classId for $fqNameSafe."
  )
}

package com.squareup.anvil.compiler.internal

import dagger.Lazy
import dagger.MapKey
import dagger.Provides
import javax.inject.Inject
import javax.inject.Qualifier
import javax.inject.Scope

internal val jvmSuppressWildcardsFqName = JvmSuppressWildcards::class.fqName
internal val publishedApiFqName = PublishedApi::class.fqName
internal val qualifierFqName = Qualifier::class.fqName
internal val mapKeyFqName = MapKey::class.fqName
internal val daggerProvidesFqName = Provides::class.fqName
internal val daggerLazyFqName = Lazy::class.fqName
internal val daggerScopeFqName = Scope::class.fqName
internal val injectFqName = Inject::class.fqName

@file:Suppress("unused")

package com.squareup.anvil.compiler.internal

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.jvm.jvmSuppressWildcards
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.fir.lightTree.converter.nameAsSafeName
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunctionType
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtNullableType
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProjectionKind
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.psi.KtTypeElement
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance.INVARIANT
import org.jetbrains.kotlin.types.Variance.IN_VARIANCE
import org.jetbrains.kotlin.types.Variance.OUT_VARIANCE
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.io.ByteArrayOutputStream

@ExperimentalAnvilApi
public fun KtClassOrObject.asClassName(): ClassName =
  ClassName(
    packageName = containingKtFile.packageFqName.safePackageString(),
    simpleNames = parentsWithSelf
      .filterIsInstance<KtClassOrObject>()
      .map { it.nameAsSafeName.asString() }
      .toList()
      .reversed()
  )

@ExperimentalAnvilApi
public fun ClassDescriptor.asClassName(): ClassName =
  ClassName(
    packageName = parents.filterIsInstance<PackageFragmentDescriptor>().first()
      .fqName.safePackageString(),
    simpleNames = parentsWithSelf.filterIsInstance<ClassDescriptor>()
      .map { it.name.asString() }
      .toList()
      .reversed()
  )

@ExperimentalAnvilApi
public fun FqName.asClassName(module: ModuleDescriptor): ClassName {
  return asClassNameOrNull(module)
    ?: throw AnvilCompilationException("Couldn't parse ClassName for $this.")
}

private fun FqName.asClassNameOrNull(module: ModuleDescriptor): ClassName? {
  val segments = pathSegments().map { it.asString() }

  // If the first sentence case is not the last segment of the path it becomes ambiguous,
  // for example, com.Foo.Bar could be an inner class Bar or an unconventional package com.Foo.
  val canGuessClassName = segments.indexOfFirst { it[0].isUpperCase() } == segments.size - 1
  if (canGuessClassName) {
    return ClassName.bestGuess(asString())
  }

  for (index in (segments.size - 1) downTo 1) {
    val packageSegments = segments.subList(0, index)
    val classSegments = segments.subList(index, segments.size)

    val validFqName = module.canResolveFqName(
      packageName = FqName.fromSegments(packageSegments),
      className = classSegments.joinToString(separator = ".")
    )

    if (validFqName) {
      return ClassName(
        packageName = packageSegments.joinToString(separator = "."),
        simpleNames = classSegments
      )
    }
  }

  // No matching class found. It's either a package name only or doesn't exist.
  return null
}

@ExperimentalAnvilApi
public fun FqName.asMemberName(module: ModuleDescriptor): MemberName {
  val segments = pathSegments().map { it.asString() }
  val simpleName = segments.last()
  val prefixFqName = FqName.fromSegments(segments.dropLast(1))
  return prefixFqName.asClassNameOrNull(module)?.let {
    val classOrObj = module.getKtClassOrObjectOrNull(prefixFqName)
    val imported = if (classOrObj is KtClass) {
      // Must be in a companion, check its name
      // We do this because accessors could be just Foo.CONSTANT but to member import it we need
      // to include the companion path
      val companionName = classOrObj.companionObjects.first().name ?: "Companion"
      it.nestedClass(companionName)
    } else {
      it
    }
    MemberName(imported, simpleName)
  } ?: MemberName(prefixFqName.pathSegments().joinToString("."), simpleName)
}

@ExperimentalAnvilApi
public fun KtTypeReference.requireTypeName(
  module: ModuleDescriptor
): TypeName {
  fun PsiElement.fail(): Nothing = throw AnvilCompilationException(
    message = "Couldn't resolve type: $text",
    element = this
  )

  fun KtTypeElement.requireTypeName(): TypeName {
    return when (this) {
      is KtUserType -> {
        val className = fqNameOrNull(module)?.asClassName(module)
          ?: if (isTypeParameter()) {
            val bounds = findExtendsBound().map { it.asClassName(module) }
            return TypeVariableName(text, bounds)
          } else {
            throw AnvilCompilationException("Couldn't resolve fqName.", element = this)
          }

        val typeArgumentList = typeArgumentList
        if (typeArgumentList != null) {
          className.parameterizedBy(
            typeArgumentList.arguments.map { typeProjection ->
              if (typeProjection.projectionKind == KtProjectionKind.STAR) {
                STAR
              } else {
                val typeReference = typeProjection.typeReference ?: typeProjection.fail()
                typeReference
                  .requireTypeName(module)
                  .let { typeName ->
                    // Preserve annotations, e.g. List<@JvmSuppressWildcards Abc>.
                    if (typeReference.annotationEntries.isNotEmpty()) {
                      typeName.copy(
                        annotations = typeName.annotations + typeReference.annotationEntries
                          .map { annotationEntry ->
                            AnnotationSpec
                              .builder(
                                annotationEntry
                                  .requireFqName(module)
                                  .asClassName(module)
                              )
                              .build()
                          }
                      )
                    } else {
                      typeName
                    }
                  }
                  .let { typeName ->
                    val modifierList = typeProjection.modifierList
                    when {
                      modifierList == null -> typeName
                      modifierList.hasModifier(KtTokens.OUT_KEYWORD) ->
                        WildcardTypeName.producerOf(typeName)
                      modifierList.hasModifier(KtTokens.IN_KEYWORD) ->
                        WildcardTypeName.consumerOf(typeName)
                      else -> typeName
                    }
                  }
              }
            }
          )
        } else {
          className
        }
      }
      is KtFunctionType ->
        LambdaTypeName.get(
          receiver = receiver?.typeReference?.requireTypeName(module),
          parameters = parameterList
            ?.parameters
            ?.map { parameter ->
              val parameterReference = parameter.typeReference ?: parameter.fail()
              ParameterSpec.unnamed(parameterReference.requireTypeName(module))
            }
            ?: emptyList(),
          returnType = (returnTypeReference ?: fail())
            .requireTypeName(module)
        )
      is KtNullableType -> {
        (innerType ?: fail()).requireTypeName().copy(nullable = true)
      }
      else -> fail()
    }
  }

  return (typeElement ?: fail()).requireTypeName()
}

/**
 * Converts a lambda `TypeName` to a standard `kotlin.Function*` type.
 *
 * given the lambda type: `(kotlin.String) -> kotlin.Unit`
 * returns the function type: `kotlin.Function1<String, Unit>`
 */
@ExperimentalAnvilApi
public fun LambdaTypeName.asFunctionType(): ParameterizedTypeName {

  val allTypes = listOfNotNull(receiver)
    .plus(parameters.map { it.type })
    .plus(returnType)

  return ClassName("kotlin", "Function${allTypes.size - 1}")
    .parameterizedBy(allTypes)
}

@ExperimentalAnvilApi
public fun ClassId.asClassName(): ClassName {
  return ClassName(
    packageName = packageFqName.asString(),
    simpleNames = relativeClassName.pathSegments().map { it.asString() }
  )
}

@ExperimentalAnvilApi
public fun KotlinType.asTypeName(): TypeName {
  return asTypeNameOrNull { true }!!
}

/**
 * @param rawTypeFilter an optional raw type filter to allow for
 *                      short-circuiting this before attempting to
 *                      resolve type arguments.
 */
@ExperimentalAnvilApi
public fun KotlinType.asTypeNameOrNull(
  rawTypeFilter: (ClassName) -> Boolean = { true }
): TypeName? {
  if (isTypeParameter()) return TypeVariableName(toString())

  val className = requireClassDescriptor().asClassName()
  if (!rawTypeFilter(className)) {
    return null
  }
  if (arguments.isEmpty()) return className.copy(nullable = isMarkedNullable)

  val argumentTypeNames = arguments.map { typeProjection ->
    if (typeProjection.isStarProjection) {
      STAR
    } else {
      val typeName = typeProjection.type.asTypeName()
      when (typeProjection.projectionKind) {
        INVARIANT -> typeName
        OUT_VARIANCE -> WildcardTypeName.producerOf(typeName)
        IN_VARIANCE -> WildcardTypeName.consumerOf(typeName)
      }
    }
  }

  return className.parameterizedBy(argumentTypeNames).copy(nullable = isMarkedNullable)
}

@ExperimentalAnvilApi
public fun <T : KtCallableDeclaration> TypeName.withJvmSuppressWildcardsIfNeeded(
  callableDeclaration: T,
  module: ModuleDescriptor
): TypeName {
  // If the parameter is annotated with @JvmSuppressWildcards, then add the annotation
  // to our type so that this information is forwarded when our Factory is compiled.
  val hasJvmSuppressWildcards =
    callableDeclaration.typeReference?.hasAnnotation(jvmSuppressWildcardsFqName, module) ?: false

  // Add the @JvmSuppressWildcards annotation even for simple generic return types like
  // Set<String>. This avoids some edge cases where Dagger chokes.
  val isGenericType = callableDeclaration.typeReference?.isGenericType() ?: false

  // Same for functions.
  val isFunctionType = callableDeclaration.typeReference?.isFunctionType() ?: false

  return when {
    hasJvmSuppressWildcards || isGenericType -> this.jvmSuppressWildcards()
    isFunctionType -> this.jvmSuppressWildcards()
    else -> this
  }
}

@ExperimentalAnvilApi
public fun TypeName.withJvmSuppressWildcardsIfNeeded(
  callableMemberDescriptor: CallableMemberDescriptor
): TypeName {
  // If the parameter is annotated with @JvmSuppressWildcards, then add the annotation
  // to our type so that this information is forwarded when our Factory is compiled.
  val hasJvmSuppressWildcards = callableMemberDescriptor.annotations
    .hasAnnotation(jvmSuppressWildcardsFqName)

  // Add the @JvmSuppressWildcards annotation even for simple generic return types like
  // Set<String>. This avoids some edge cases where Dagger chokes.
  val isGenericType = callableMemberDescriptor.typeParameters.isNotEmpty()

  val type = callableMemberDescriptor.safeAs<PropertyDescriptor>()?.type
    ?: callableMemberDescriptor.valueParameters.first().type

  // Same for functions.
  val isFunctionType = type.isFunctionType

  return when {
    hasJvmSuppressWildcards || isGenericType -> this.jvmSuppressWildcards()
    isFunctionType -> this.jvmSuppressWildcards()
    else -> this
  }
}

private fun FileSpec.writeToString(): String {
  val stream = ByteArrayOutputStream()
  stream.writer().use {
    writeTo(it)
  }
  return stream.toString()
}

private fun FileSpec.Builder.suppressWarnings() {
  addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("\"DEPRECATION\"").build())
}

@ExperimentalAnvilApi
public fun FileSpec.Companion.buildFile(
  packageName: String,
  fileName: String,
  generatorComment: String = "Generated by Anvil.\nhttps://github.com/square/anvil",
  block: FileSpec.Builder.() -> Unit
): String =
  builder(packageName, fileName)
    .apply {
      // Suppress any deprecation warnings.
      suppressWarnings()

      block()
    }
    .addComment(generatorComment)
    .build()
    .writeToString()

@ExperimentalAnvilApi
public fun AnnotationDescriptor.toAnnotationSpec(module: ModuleDescriptor): AnnotationSpec {
  return AnnotationSpec
    .builder(requireClass().asClassName())
    .apply {
      allValueArguments.forEach { (name, value) ->
        when (value) {
          is KClassValue -> {
            val className = value.argumentType(module).requireClassDescriptor()
              .asClassName()
            addMember("${name.asString()} = %T::class", className)
          }
          is EnumValue -> {
            val enumMember = MemberName(
              enclosingClassName = value.enumClassId.asSingleFqName()
                .asClassName(module),
              simpleName = value.enumEntryName.asString()
            )
            addMember("${name.asString()} = %M", enumMember)
          }
          // String, int, long, ... other primitives.
          else -> addMember("${name.asString()} = $value")
        }
      }
    }
    .build()
}

/**
 * Returns a filtered list of [javax.inject.Qualifier]-annotated [entries][KtAnnotationEntry] as
 * a [AnnotationSpecs][AnnotationSpec].
 */
@ExperimentalAnvilApi
public fun List<KtAnnotationEntry>.qualifierAnnotationSpecs(
  module: ModuleDescriptor
): List<AnnotationSpec> = mapNotNull { annotationEntry ->
  if (!annotationEntry.isQualifier(module)) {
    null
  } else {
    annotationEntry.toAnnotationSpec(module)
  }
}

/** Returns an [AnnotationSpec] representation of this [KtAnnotationEntry]. */
@ExperimentalAnvilApi
public fun KtAnnotationEntry.toAnnotationSpec(
  module: ModuleDescriptor
): AnnotationSpec {
  return AnnotationSpec.builder(requireFqName(module).asClassName(module))
    .apply {
      valueArguments
        .filterIsInstance<KtValueArgument>()
        .mapNotNull { valueArgument ->
          valueArgument.getArgumentExpression()?.codeBlock(module)
        }.forEach {
          addMember(it)
        }
    }
    .build()
}

private fun KtExpression.codeBlock(module: ModuleDescriptor): CodeBlock {
  return when (this) {
    // MyClass::class
    is KtClassLiteralExpression -> {
      val className = requireFqName(module).asClassName(module)
      CodeBlock.of("%T::class", className)
    }
    // enums or qualified references
    is KtNameReferenceExpression, is KtQualifiedExpression -> {
      val fqName = try {
        requireFqName(module)
      } catch (e: AnvilCompilationException) {
        if (this is KtNameReferenceExpression) {
          null
        } else {
          throw e
        }
      }
      if (fqName != null) {
        if (module.canResolveFqName(fqName)) {
          CodeBlock.of("%T", fqName.asClassName(module))
        } else {
          CodeBlock.of("%M", fqName.asMemberName(module))
        }
      } else {
        // It's (hopefully) an in-scope constant, look up the hierarchy.
        val ref = (this as KtNameReferenceExpression).getReferencedName()
        findConstantDefinitionInScope(ref)
          ?.asMemberName(module)
          ?.let { memberRef ->
            CodeBlock.of("%M", memberRef)
          }
          // If we reach here, it's a top-level constant defined in the same package but a different
          // file. In this case, we can just do the same in our generated code because we're in the
          // same package and can play by the same rules.
          ?: CodeBlock.of("%L", ref)
      }
    }
    is KtCollectionLiteralExpression -> CodeBlock.of(
      getInnerExpressions()
        .map { it.codeBlock(module) }
        .joinToString(prefix = "[", postfix = "]")
    )
    // literals
    else -> CodeBlock.of("%L", text)
  }
}

private fun List<KtProperty>.containsConstPropertyWithName(name: String): Boolean {
  return any { property ->
    property.hasModifier(KtTokens.CONST_KEYWORD) && property.name == name
  }
}

private fun PsiElement.findConstantDefinitionInScope(name: String): FqName? {
  when (this) {
    is KtProperty, is KtNamedFunction -> {
      // Function or Property, traverse up
      return (this as KtElement).containingClass()?.findConstantDefinitionInScope(name)
        ?: containingFile.findConstantDefinitionInScope(name)
    }
    is KtObjectDeclaration -> {
      if (properties(includeCompanionObjects = false).containsConstPropertyWithName(name)) {
        return requireFqName().child(name.nameAsSafeName())
      } else if (isCompanion()) {
        // Nowhere else to look and don't try to traverse up because this is only looked at from a
        // class already.
        return null
      }
    }
    is KtFile -> {
      if (children.filterIsInstance<KtProperty>().containsConstPropertyWithName(name)) {
        return packageFqName.child(name.nameAsSafeName())
      }
    }
    is KtClass -> {
      // Look in companion object or traverse up
      return companionObjects.asSequence()
        .mapNotNull { it.findConstantDefinitionInScope(name) }
        .firstOrNull()
        ?: containingClass()?.findConstantDefinitionInScope(name)
        ?: containingKtFile.findConstantDefinitionInScope(name)
    }
  }

  return parent?.findConstantDefinitionInScope(name)
}

@file:Suppress("unused")

package com.squareup.anvil.compiler.internal

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.ClassReference.Psi
import com.squareup.kotlinpoet.TypeVariableName
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.findTypeAliasAcrossModuleDependencies
import org.jetbrains.kotlin.descriptors.resolveClassByFqName
import org.jetbrains.kotlin.incremental.KotlinLookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation.FROM_BACKEND
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassBody
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtFunctionType
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtNullableType
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPureElement
import org.jetbrains.kotlin.psi.KtSuperTypeListEntry
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtValueArgumentName
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.isTopLevelKtOrJavaMember
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.types.DefinitelyNotNullType
import org.jetbrains.kotlin.types.FlexibleType
import org.jetbrains.kotlin.types.KotlinType
import kotlin.reflect.KClass

private val kotlinAnnotations = listOf(jvmSuppressWildcardsFqName, publishedApiFqName)

/** Returns the computed [FqName] representation of this [KClass]. */
public val KClass<*>.fqName: FqName get() = FqName(java.canonicalName)

@ExperimentalAnvilApi
public fun KtNamedDeclaration.requireFqName(): FqName = requireNotNull(fqName) {
  "fqName was null for $this, $nameAsSafeName"
}

@ExperimentalAnvilApi
public fun KtClassOrObject.toClassId(): ClassId {
  val className = parentsWithSelf.filterIsInstance<KtClassOrObject>()
    .toList()
    .reversed()
    .joinToString(separator = ".") { it.nameAsSafeName.asString() }

  return ClassId(containingKtFile.packageFqName, FqName(className), false)
}

@ExperimentalAnvilApi
public fun KtAnnotated.isInterface(): Boolean = this is KtClass && this.isInterface()

@ExperimentalAnvilApi
public fun KtAnnotated.hasAnnotation(
  fqName: FqName,
  module: ModuleDescriptor
): Boolean {
  return findAnnotation(fqName, module) != null
}

@ExperimentalAnvilApi
public fun KtAnnotated.requireAnnotation(
  fqName: FqName,
  module: ModuleDescriptor
): KtAnnotationEntry {
  return findAnnotation(fqName, module) ?: throw AnvilCompilationException(
    element = this,
    message = "Couldn't find the annotation $fqName."
  )
}

@ExperimentalAnvilApi
public fun KtAnnotated.findAnnotation(
  fqName: FqName,
  module: ModuleDescriptor
): KtAnnotationEntry? {
  val annotationEntries = annotationEntries
  if (annotationEntries.isEmpty()) return null

  // Look first if it's a Kotlin annotation. These annotations are usually not imported and the
  // remaining checks would fail.
  if (fqName in kotlinAnnotations) {
    annotationEntries.firstOrNull { annotation ->
      val text = annotation.text
      text.startsWith("@${fqName.shortName()}") || text.startsWith("@$fqName")
    }?.let { return it }
  }

  // Check if the fully qualified name is used, e.g. `@dagger.Module`.
  val annotationEntry = annotationEntries.firstOrNull {
    it.text.startsWith("@${fqName.asString()}")
  }
  if (annotationEntry != null) return annotationEntry

  // Check if the simple name is used, e.g. `@Module`.
  val annotationEntryShort = annotationEntries
    .firstOrNull {
      it.shortName == fqName.shortName()
    }
    ?: return null

  val importPaths = containingKtFile.importDirectives.mapNotNull { it.importPath }

  // If the simple name is used, check that the annotation is imported.
  val hasImport = importPaths.any { it.fqName == fqName }
  if (hasImport) return annotationEntryShort

  // Look for star imports and make a guess.
  val hasStarImport = importPaths
    .filter { it.isAllUnder }
    .any {
      fqName.asString().startsWith(it.fqName.asString())
    }
  if (hasStarImport) return annotationEntryShort

  // At this point we know that the class is annotated with an annotation that has the same simple
  // name as fqName. We couldn't find any imports, so the annotation is likely part of the same
  // package or Kotlin namespace. Leverage our existing utility function and to find the FqName
  // and then compare the result.
  val fqNameOfShort = annotationEntryShort.fqNameOrNull(module)
  if (fqName == fqNameOfShort) {
    return annotationEntryShort
  }

  return null
}

@ExperimentalAnvilApi
public fun KtClassOrObject.scope(
  annotationFqName: FqName,
  module: ModuleDescriptor
): FqName {
  return scopeOrNull(annotationFqName, module)
    ?: throw AnvilCompilationException(
      "Couldn't find scope for $annotationFqName.",
      element = this
    )
}

@ExperimentalAnvilApi
public fun KtClassOrObject.scopeOrNull(
  annotationFqName: FqName,
  module: ModuleDescriptor
): FqName? {
  return findAnnotation(annotationFqName, module)
    ?.findAnnotationArgument<KtClassLiteralExpression>(name = "scope", index = 0)
    ?.requireFqName(module)
}

@ExperimentalAnvilApi
public fun KtClassOrObject.parentScope(
  annotationFqName: FqName,
  module: ModuleDescriptor
): FqName {
  return requireAnnotation(annotationFqName, module)
    .findAnnotationArgument<KtClassLiteralExpression>(name = "parentScope", index = 1)
    .let { classLiteralExpression ->
      if (classLiteralExpression == null) {
        throw AnvilCompilationException(
          "Couldn't find parentScope for $annotationFqName.",
          element = this
        )
      }

      classLiteralExpression.requireFqName(module)
    }
}

/**
 * Finds the argument in the given annotation. [name] refers to the parameter name
 * in the annotation and [index] to the position of the argument, e.g. if you look for the scope in
 * `@ContributesBinding(Int::class, boundType = Unit::class)`, then [name] would be "scope" and the
 * index 0. If you look for the bound type, then [name] would be "boundType" and the index 1.
 */
@ExperimentalAnvilApi
public inline fun <reified T> KtAnnotationEntry.findAnnotationArgument(
  name: String,
  index: Int
): T? {
  val annotationValues = valueArguments
    .asSequence()
    .filterIsInstance<KtValueArgument>()

  // First check if the is any named parameter. Named parameters allow a different order of
  // arguments.
  annotationValues
    .firstNotNullOfOrNull { valueArgument ->
      val children = valueArgument.children
      if (children.size == 2 && children[0] is KtValueArgumentName &&
        (children[0] as KtValueArgumentName).asName.asString() == name &&
        children[1] is T
      ) {
        children[1] as T
      } else {
        null
      }
    }
    ?.let { return it }

  // If there is no named argument, then take the first argument, which must be a class literal
  // expression, e.g. @ContributesTo(Unit::class)
  return annotationValues
    .elementAtOrNull(index)
    ?.let { valueArgument ->
      valueArgument.children.firstOrNull() as? T
    }
}

/**
 * This will return all super classes which are parsed as PSI, which means anything defined in this
 * module. This will include generated code, assuming it has already been generated. It **will not**
 * include any classes from other modules or external dependencies.
 *
 * The first element in the returned list represents the direct superclass to the receiver. The last
 * element represents the class which is furthest up-stream.
 *
 * If you have a class hierarchy which extends into dependencies, and you need to reference those
 * super classes, you'll need to take the last class in this list and resolve its `ClassDescriptor`,
 * then resolve its super-classes that way. Note that this will only work if the last class
 * in the list is not generated, so that it's parsed and part of the plain ModuleDescriptor.
 *
 * **Note** This function only returns classes. See [KtClassOrObject.allPsiSuperClasses] for a
 * version which also returns interfaces.
 */
@ExperimentalAnvilApi
public fun KtClassOrObject.allPsiSuperClasses(
  module: ModuleDescriptor
): List<KtClassOrObject> {

  if (this is KtEnumEntry) return emptyList()

  val superOrNull = superTypeListEntries
    .asSequence()
    .mapNotNull { it.typeReference?.fqNameOrNull(module) }
    .mapNotNull { module.getKtClassOrObjectOrNull(it) }
    .firstOrNull { !it.isInterface() }
    ?: return emptyList()

  return listOf(superOrNull) + superOrNull.allPsiSuperClasses(module)
}

@ExperimentalAnvilApi
public fun PsiElement.fqNameOrNull(
  module: ModuleDescriptor
): FqName? {
  // Usually it's the opposite way, the require*() method calls the nullable method. But in this
  // case we'd like to preserve the better error messages in case something goes wrong.
  return try {
    requireFqName(module)
  } catch (e: AnvilCompilationException) {
    null
  }
}

@ExperimentalAnvilApi
public fun PsiElement.requireFqName(
  module: ModuleDescriptor
): FqName {
  val containingKtFile = parentsWithSelf
    .filterIsInstance<KtPureElement>()
    .first()
    .containingKtFile

  fun failTypeHandling(): Nothing = throw AnvilCompilationException(
    "Don't know how to handle Psi element: $text",
    element = this
  )

  val classReference = when (this) {
    // If a fully qualified name is used, then we're done and don't need to do anything further.
    // An inner class reference like Abc.Inner is also considered a KtDotQualifiedExpression in
    // some cases.
    is KtDotQualifiedExpression -> {
      module.resolveFqNameOrNull(FqName(text))
        ?.let { return it }
        ?: text
    }
    is KtNameReferenceExpression -> getReferencedName()
    is KtUserType -> {
      val isGenericType = children.any { it is KtTypeArgumentList }
      if (isGenericType) {
        // For an expression like Lazy<Abc> the qualifier will be null. If the qualifier exists,
        // then it may refer to the package and the referencedName refers to the class name, e.g.
        // a KtUserType "abc.def.GenericType<String>" has three children: a qualifier "abc.def",
        // the referencedName "GenericType" and the KtTypeArgumentList.
        val qualifierText = qualifier?.text
        val className = referencedName

        if (qualifierText != null) {

          // The generic might be fully qualified. Try to resolve it and return early.
          module.resolveFqNameOrNull(FqName("$qualifierText.$className"))
            ?.let { return it }

          // If the name isn't fully qualified, then it's something like "Outer.Inner".
          // We can't use `text` here because that includes the type parameter(s).
          "$qualifierText.$className"
        } else {
          className ?: failTypeHandling()
        }
      } else {
        val text = text

        // Sometimes a KtUserType is a fully qualified name. Give it a try and return early.
        if (text.contains(".") && text[0].isLowerCase()) {
          module.resolveFqNameOrNull(FqName(text))
            ?.let { return it }
        }

        // We can't use referencedName here. For inner classes like "Outer.Inner" it would only
        // return "Inner", whereas text returns "Outer.Inner", what we expect.
        text
      }
    }
    is KtTypeReference -> {
      val children = children
      if (children.size == 1) {
        try {
          // Could be a KtNullableType or KtUserType.
          return children[0].requireFqName(module)
        } catch (e: AnvilCompilationException) {
          // Fallback to the text representation.
          text
        }
      } else {
        text
      }
    }
    is KtNullableType -> return innerType?.requireFqName(module) ?: failTypeHandling()
    is KtAnnotationEntry -> return typeReference?.requireFqName(module) ?: failTypeHandling()
    is KtClassLiteralExpression -> {
      // Returns "Abc" for "Abc::class".
      val element = children.singleOrNull() ?: throw AnvilCompilationException(
        "Expected a single child, but there were ${children.size} instead: $text",
        element = this
      )
      return element.requireFqName(module)
    }
    is KtSuperTypeListEntry -> return typeReference?.requireFqName(module) ?: failTypeHandling()
    else -> failTypeHandling()
  }

  // E.g. OuterClass.InnerClass
  val classReferenceOuter = classReference.substringBefore(".")

  val importPaths = containingKtFile.importDirectives.mapNotNull { it.importPath }

  // First look in the imports for the reference name. If the class is imported, then we know the
  // fully qualified name.
  importPaths
    .filter { it.alias == null && it.fqName.shortName().asString() == classReference }
    .also { matchingImportPaths ->
      when {
        matchingImportPaths.size == 1 ->
          return matchingImportPaths[0].fqName
        matchingImportPaths.size > 1 ->
          return matchingImportPaths.first { importPath ->
            module.canResolveFqName(importPath.fqName)
          }.fqName
      }
    }

  importPaths
    .filter { it.alias == null && it.fqName.shortName().asString() == classReferenceOuter }
    .also { matchingImportPaths ->
      when {
        matchingImportPaths.size == 1 ->
          return FqName("${matchingImportPaths[0].fqName.parent()}.$classReference")
        matchingImportPaths.size > 1 ->
          return matchingImportPaths.first { importPath ->
            module.canResolveFqName(importPath.fqName, classReference)
          }.fqName
      }
    }

  containingKtFile.importDirectives
    .asSequence()
    .filter { it.isAllUnder }
    .mapNotNull {
      // This fqName is everything in front of the star, e.g. for "import java.io.*" it
      // returns "java.io".
      it.importPath?.fqName
    }
    .forEach { importFqName ->
      if (importFqName.asString() == "java.util") {
        // If there's a star import for java.util.* and the import is a Collection type, then
        // the Kotlin compiler overrides these with Kotlin types.
        module
          .resolveFqNameOrNull(FqName("kotlin.collections.$classReference"))
          ?.let { return it }
      }

      module.resolveFqNameOrNull(importFqName, classReference)
        ?.let { return it }
    }

  // If there is no import, then try to resolve the class with the same package as this file.
  module.resolveFqNameOrNull(containingKtFile.packageFqName, classReference)
    ?.let { return it }

  // If the referenced type is declared within the same scope, it doesn't need to be imported.
  declarationsWithinScope()
    // Top-level declarations are already handled by "$packageName.$classReference", so skip those.
    .filterNot { it.isTopLevelKtOrJavaMember() }
    .mapNotNull { it.fqName }
    .filter { it.asString().endsWith(classReference) }
    .firstNotNullOfOrNull { module.resolveFqNameOrNull(it) }
    ?.let { return it }

  // If this doesn't work, then maybe a class from the Kotlin package is used.
  module.resolveFqNameOrNull(FqName("kotlin.$classReference"))
    ?.let { return it }

  // If this doesn't work, then maybe a class from the Kotlin collection package is used.
  module.resolveFqNameOrNull(FqName("kotlin.collections.$classReference"))
    ?.let { return it }

  // If this doesn't work, then maybe a class from the Kotlin jvm package is used.
  module.resolveFqNameOrNull(FqName("kotlin.jvm.$classReference"))
    ?.let { return it }

  // Or java.lang.
  module.resolveFqNameOrNull(FqName("java.lang.$classReference"))
    ?.let { return it }

  findFqNameInSuperTypes(module, classReference)
    ?.let { return it }

  // Check if it's a named import.
  containingKtFile.importDirectives
    .firstOrNull { classReference == it.importPath?.importedName?.asString() }
    ?.importedFqName
    ?.let { return it }

  // Everything else isn't supported.
  throw AnvilCompilationException(
    "Couldn't resolve FqName $classReference for Psi element: $text",
    element = this
  )
}

/**
 * Returns a sequence of all named declarations (anything with an fqName) which are declared at a
 * level between the receiver [PsiElement] and the top level of the file (inclusive).
 *
 * In practice, the returned sequence represents all declarations which can be referenced by the
 * receiver without needing an import statement or qualified name.
 */
@ExperimentalAnvilApi
public fun PsiElement.declarationsWithinScope(): Sequence<KtNamedDeclaration> {
  return parents.filterIsInstance<KtNamedDeclaration>()
    .flatMap { it.children.filterIsInstance<KtNamedDeclaration>() + it }
    .distinct()
}

private fun PsiElement.findFqNameInSuperTypes(
  module: ModuleDescriptor,
  classReference: String
): FqName? {
  fun tryToResolveClassFqName(outerClass: FqName): FqName? =
    module.resolveFqNameOrNull(FqName("$outerClass.$classReference"))

  return parents.filterIsInstance<KtClassOrObject>()
    .flatMap { clazz ->
      tryToResolveClassFqName(clazz.requireFqName())?.let { return@flatMap sequenceOf(it) }

      // At this point we can't work with Psi APIs anymore. We need to resolve the super types
      // and try to find inner class in them.
      val descriptor = clazz.requireClassDescriptor(module)
      listOf(descriptor.defaultType).getAllSuperTypes()
        .mapNotNull { tryToResolveClassFqName(it) }
    }
    .firstOrNull()
}

@ExperimentalAnvilApi
public fun KtClassOrObject.typeVariableNames(
  module: ModuleDescriptor
): List<TypeVariableName> {
  // Any type which is constrained in a `where` clause is also defined as a type parameter.
  // It's also technically possible to have one constraint in the type parameter spot, like this:
  // class MyClass<T : Any> where T : Set<*>, T : MutableCollection<*>
  // Merge both groups of type parameters in order to get the full list of bounds.
  val boundsByVariableName = typeParameterList
    ?.parameters
    ?.filter { it.fqNameOrNull(module) == null }
    ?.associateTo(mutableMapOf()) { parameter ->
      val variableName = parameter.nameAsSafeName.asString()
      val extendsBound = parameter.extendsBound?.requireTypeName(module)

      variableName to mutableListOf(extendsBound)
    } ?: mutableMapOf()

  typeConstraintList
    ?.constraints
    ?.filter { it.fqNameOrNull(module) == null }
    ?.forEach { constraint ->
      val variableName = constraint.subjectTypeParameterName
        ?.getReferencedName()
        ?: return@forEach
      val extendsBound = constraint.boundTypeReference?.requireTypeName(module)

      boundsByVariableName
        .getValue(variableName)
        .add(extendsBound)
    }

  return boundsByVariableName
    .map { (variableName, bounds) ->
      TypeVariableName(variableName, bounds.filterNotNull())
    }
}

@ExperimentalAnvilApi
public fun KtClassOrObject.functions(
  includeCompanionObjects: Boolean
): List<KtNamedFunction> = classBodies(includeCompanionObjects).flatMap { it.functions }

@ExperimentalAnvilApi
public fun KtClassOrObject.properties(
  includeCompanionObjects: Boolean
): List<KtProperty> = classBodies(includeCompanionObjects).flatMap { it.properties }

@ExperimentalAnvilApi
public fun KtClassOrObject.isObject(): Boolean = this is KtObjectDeclaration

private fun KtClassOrObject.classBodies(includeCompanionObjects: Boolean): List<KtClassBody> {
  val elements = children.toMutableList()
  if (includeCompanionObjects) {
    elements += companionObjects.flatMap { it.children.toList() }
  }
  return elements.filterIsInstance<KtClassBody>()
}

@ExperimentalAnvilApi
public fun KtTypeReference.isNullable(): Boolean = typeElement is KtNullableType

@ExperimentalAnvilApi
public fun KtTypeReference.isGenericType(): Boolean {
  val typeElement = typeElement ?: return false
  val children = typeElement.children

  if (children.size != 2) return false
  return children[1] is KtTypeArgumentList
}

@ExperimentalAnvilApi
public fun KtTypeReference.isFunctionType(): Boolean = typeElement is KtFunctionType

@ExperimentalAnvilApi
public fun KtClassOrObject.isGenericClass(): Boolean = typeParameterList != null

@ExperimentalAnvilApi
public fun KtTypeParameter.requireIdentifier(): String = nameIdentifier?.text
  ?: throw AnvilCompilationException(
    "unable to determine the name of this type parameter.", element = this
  )

/**
 * Safely resolves a PSI [KtTypeReference], when that type reference may be a generic expressed by a
 * type variable name. This is done by inspecting the class hierarchy to find where the generic type
 * is declared, then resolving *that* reference.
 *
 * For instance, given:
 *
 * ```
 * interface Factory<T> {
 *   fun create(): T
 * }
 *
 * interface ServiceFactory : Factory<Service>
 * ```
 *
 * The KtTypeReference `T` will fail to resolve, since it isn't a type. This function will instead
 * look to the `ServiceFactory` interface, then look at the supertype declaration in order to
 * determine the type.
 *
 * @receiver The class which actually references the type. In the above example, this would be
 *   `ServiceFactory`.
 */
@ExperimentalAnvilApi
public fun KtClassOrObject.resolveTypeReference(
  module: ModuleDescriptor,
  typeReference: KtTypeReference
): KtTypeReference? {

  // if the element isn't a type variable name like `T`, it can be resolved through imports.
  typeReference.typeElement?.fqNameOrNull(module)
    ?.let { return typeReference }

  val declaringClass = typeReference.requireContainingClassReference()
  val parameterName = typeReference.text

  return resolveGenericTypeReference(module, declaringClass, parameterName)
}

/**
 * Safely resolves a [KotlinType], when that type reference is a generic expressed by a type
 * variable name. This is done by inspecting the class hierarchy to find where the generic type is
 * declared, then resolving *that* reference.
 *
 * For instance, given:
 *
 * ```
 * interface SomeFactory : Function1<String, SomeClass>
 * ```
 *
 * There's an invisible `fun invoke(p1: P1): R`. If `SomeFactory` is parsed using PSI (such as if
 * it's generated), then the function can only be parsed to have the projected types of `P1` and `R`
 *
 * @receiver The class which actually references the type. In the above example, this would be
 *   `SomeFactory`.
 * @param declaringClass The class which declares the generic type name. In the above example, this
 *   would be `Function1`.
 * @param typeToResolve The generic reference to be resolved. In the above example, this is the `T`
 *   **return type**.
 */
@ExperimentalAnvilApi
public fun KtClassOrObject.resolveGenericKotlinType(
  module: ModuleDescriptor,
  declaringClass: ClassReference,
  typeToResolve: KotlinType
): KtTypeReference? {
  val parameterKotlinType = when (typeToResolve) {
    is FlexibleType -> {
      // A FlexibleType comes from Java code where the compiler doesn't know whether it's a nullable
      // or non-nullable type. toString() returns something like "(T..T?)". To get proper results
      // we use the type that's not nullable, "T" in this example.
      if (typeToResolve.lowerBound.isMarkedNullable) {
        typeToResolve.upperBound
      } else {
        typeToResolve.lowerBound
      }
    }
    is DefinitelyNotNullType -> {
      // This is known to be not null in Java, such as something annotated `@NotNull` or controlled
      // by a JSR305 or jSpecify annotation.
      // This is a special type and this logic appears to match how kotlinc is handling it here
      // https://github.com/JetBrains/kotlin/blob/9ee0d6b60ac4f0ea0ccc5dd01146bab92fabcdf2/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.java#L455-L458
      typeToResolve.original
    }
    else -> {
      typeToResolve
    }
  }

  return resolveGenericTypeReference(module, declaringClass, parameterKotlinType.toString())
}

private fun KtClassOrObject.resolveGenericTypeReference(
  module: ModuleDescriptor,
  declaringClass: ClassReference,
  parameterName: String
): KtTypeReference? {
  val declaringClassFqName = declaringClass.fqName

  // If the class/interface declaring the generic is the receiver class,
  // then the generic hasn't been set to a concrete type and can't be resolved.
  if (requireFqName() == declaringClassFqName) {
    return null
  }

  // Used to determine which parameter to look at in a KtTypeArgumentList
  val indexOfType = declaringClass.indexOfTypeParameter(parameterName)

  // Find where the supertype is actually declared by matching the FqName of the SuperTypeListEntry
  // to the type which declares the generic we're trying to resolve.
  // After finding the SuperTypeListEntry, just take the TypeReference from its type argument list
  val resolvedTypeReference = superTypeListEntryOrNull(module, declaringClassFqName)
    ?.typeReference
    ?.typeElement
    ?.getChildOfType<KtTypeArgumentList>()
    ?.arguments
    ?.get(indexOfType)
    ?.typeReference

  return resolvedTypeReference?.takeIf { it.fqNameOrNull(module) != null }
    ?: resolvedTypeReference?.let { resolveTypeReference(module, it) }
}

/**
 * Find where a super type is extended/implemented by matching the FqName of a SuperTypeListEntry to
 * the FqName of the target super type.
 *
 * For instance, given:
 *
 * ```
 * interface Base<T> {
 *   fun create(): T
 * }
 *
 * abstract class Middle : Comparable<MyClass>, Provider<Something>, Base<Something>
 *
 * class InjectClass : Middle()
 * ```
 *
 * We start at the declaration of `InjectClass`, looking for a super declaration of `Base<___>`.
 * Since `InjectClass` doesn't declare it, we look through the supers of `Middle` and find it, then
 * resolve `T` to `Something`.
 */
@ExperimentalAnvilApi
public fun KtClassOrObject.superTypeListEntryOrNull(
  module: ModuleDescriptor,
  superTypeFqName: FqName
): KtSuperTypeListEntry? {
  return toClassReference()
    .allSuperTypeClassReferences(module, includeSelf = true)
    .filterIsInstance<Psi>()
    .firstNotNullOfOrNull { classReference ->
      classReference.clazz
        .superTypeListEntries
        .firstOrNull { it.requireFqName(module) == superTypeFqName }
    }
}

@ExperimentalAnvilApi
public fun KtCallableDeclaration.requireTypeReference(module: ModuleDescriptor): KtTypeReference {
  typeReference?.let { return it }

  if (this is KtFunction && hasAnnotation(daggerProvidesFqName, module)) {
    throw AnvilCompilationException(
      message = "Dagger provider methods must specify the return type explicitly when using " +
        "Anvil. The return type cannot be inferred implicitly.",
      element = this
    )
  }

  throw AnvilCompilationException(
    message = "Couldn't obtain type reference. Did you declare an explicit type for ${this.name}?",
    element = this
  )
}

@ExperimentalAnvilApi
public fun KtUserType.isTypeParameter(): Boolean {
  return parents.filterIsInstance<KtClassOrObject>().first().typeParameters.any {
    val typeParameter = it.text.split(":").first().trim()
    typeParameter == text
  }
}

@ExperimentalAnvilApi
public fun KtUserType.findExtendsBound(): List<FqName> {
  return parents.filterIsInstance<KtClassOrObject>()
    .first()
    .typeParameters
    .mapNotNull { it.fqName }
}

@ExperimentalAnvilApi
public fun KtClassOrObject.requireClassDescriptor(module: ModuleDescriptor): ClassDescriptor {
  return classDescriptorOrNull(module)
    ?: throw AnvilCompilationException(
      "Couldn't resolve class for ${requireFqName()}.",
      element = this
    )
}

@ExperimentalAnvilApi
public fun KtClassOrObject.classDescriptorOrNull(module: ModuleDescriptor): ClassDescriptor? {
  return module.resolveClassByFqName(requireFqName(), KotlinLookupLocation(this))
}

@ExperimentalAnvilApi
public fun FqName.requireClassDescriptor(module: ModuleDescriptor): ClassDescriptor {
  return classDescriptorOrNull(module)
    ?: throw AnvilCompilationException("Couldn't resolve class for $this.")
}

@ExperimentalAnvilApi
public fun FqName.classDescriptorOrNull(module: ModuleDescriptor): ClassDescriptor? {
  return module.resolveClassByFqName(this, FROM_BACKEND)
    // In the case of a typealias, we need to look up the original reference instead.
    ?: module.findTypeAliasAcrossModuleDependencies(classIdBestGuess())?.classDescriptor
}

@ExperimentalAnvilApi
public fun ClassId.classDescriptorOrNull(module: ModuleDescriptor): ClassDescriptor? =
  asSingleFqName().classDescriptorOrNull(module)

@ExperimentalAnvilApi
public fun KtAnnotationEntry.isQualifier(module: ModuleDescriptor): Boolean {
  return typeReference
    ?.requireFqName(module)
    // Often entries are annotated with @Inject, in this case we know it's not a qualifier and we
    // can stop early.
    ?.takeIf { it != injectFqName }
    ?.requireClassDescriptor(module)
    ?.annotations
    ?.hasAnnotation(qualifierFqName)
    ?: false
}

@ExperimentalAnvilApi
public fun KtAnnotationEntry.isDaggerScope(module: ModuleDescriptor): Boolean {
  return typeReference
    ?.requireFqName(module)
    ?.requireClassDescriptor(module)
    ?.annotations
    ?.hasAnnotation(daggerScopeFqName)
    ?: false
}

@ExperimentalAnvilApi
public fun KtAnnotationEntry.isMapKey(module: ModuleDescriptor): Boolean {
  return typeReference
    ?.requireFqName(module)
    ?.takeIf { it != injectFqName }
    ?.requireClassDescriptor(module)
    ?.annotations
    ?.hasAnnotation(mapKeyFqName)
    ?: false
}

@ExperimentalAnvilApi
public fun KtClassOrObject.generateClassName(
  separator: String = "_"
): String =
  parentsWithSelf
    .filterIsInstance<KtClassOrObject>()
    .toList()
    .reversed()
    .joinToString(separator = separator) {
      it.requireFqName()
        .shortName()
        .asString()
    }

@ExperimentalAnvilApi
public fun KtTypeReference.containingClassReferenceOrNull(): ClassReference? {
  return typeElement
    ?.containingClass()
    ?.toClassReference()
}

@ExperimentalAnvilApi
public fun KtTypeReference.requireContainingClassReference(): ClassReference {
  return containingClassReferenceOrNull()
    ?: throw AnvilCompilationException(
      "Unable to find a containing class.",
      element = this
    )
}

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.compiler.ANVIL_MODULE_SUFFIX
import com.squareup.anvil.compiler.ClassScanner
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.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.codegen.GeneratedMethod.BindingMethod
import com.squareup.anvil.compiler.codegen.GeneratedMethod.ProviderMethod
import com.squareup.anvil.compiler.contributesBindingFqName
import com.squareup.anvil.compiler.contributesMultibindingFqName
import com.squareup.anvil.compiler.contributesToFqName
import com.squareup.anvil.compiler.daggerModuleFqName
import com.squareup.anvil.compiler.internal.ClassReference.Descriptor
import com.squareup.anvil.compiler.internal.ClassReference.Psi
import com.squareup.anvil.compiler.internal.annotationOrNull
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.decapitalize
import com.squareup.anvil.compiler.internal.findAnnotation
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.requireClassReference
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.scope
import com.squareup.anvil.compiler.internal.scopeOrNull
import com.squareup.anvil.compiler.internal.toClassReference
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.mergeModulesFqName
import com.squareup.anvil.compiler.mergeSubcomponentFqName
import com.squareup.anvil.compiler.replaces
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.ABSTRACT
import com.squareup.kotlinpoet.TypeSpec
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import java.io.File

private val supportedFqNames = listOf(
  mergeComponentFqName,
  mergeSubcomponentFqName,
  mergeModulesFqName
)

internal class BindingModuleGenerator(
  private val classScanner: ClassScanner
) : FlushingCodeGenerator {

  override fun isApplicable(context: AnvilContext): Boolean {
    throw NotImplementedError("This should not actually be checked as we instantiate this manually")
  }

  // Keeps track of for which scopes which files were generated. Usually there is only one file,
  // but technically there can be multiple.
  private val mergedClasses = mutableMapOf<MergedClassKey, File>()

  private val excludedTypesForClass = mutableMapOf<MergedClassKey, List<FqName>>()

  private val contributedBindingClasses = mutableListOf<FqName>()
  private val contributedMultibindingClasses = mutableListOf<FqName>()
  private val contributedModuleClasses = mutableListOf<KtClassOrObject>()

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    // Even though we support multiple rounds the Kotlin compiler let's us generate code only once.
    // It's possible that we use the @ContributesBinding annotation in the module in which we
    // merge components. Remember for which classes a hint was generated and generate a @Binds
    // method for them later.
    contributedBindingClasses += findContributedBindingClasses(
      module = module,
      projectFiles = projectFiles,
      hintPackagePrefix = HINT_BINDING_PACKAGE_PREFIX
    )
    contributedMultibindingClasses += findContributedBindingClasses(
      module = module,
      projectFiles = projectFiles,
      hintPackagePrefix = HINT_MULTIBINDING_PACKAGE_PREFIX
    )

    val classes = projectFiles.classesAndInnerClass(module).toList()

    // Similar to the explanation above, we must track contributed modules.
    findContributedModules(classes, module)

    // Generate a Dagger module for each @MergeComponent and friends.
    return classes
      .mapNotNull { psiClass ->
        supportedFqNames
          .firstNotNullOfOrNull { supportedFqName ->
            psiClass.findAnnotation(supportedFqName, module)
          }
          ?.let { psiClass to it }
      }
      .map { (psiClass, mergeAnnotation) ->
        val annotationFqName = mergeAnnotation.requireFqName(module)
        val scope = psiClass.scope(annotationFqName, module)
        val mergedClassKey = MergedClassKey(scope, psiClass)

        // Remember for which scopes which types were excluded so that we later don't generate
        // a binding method for these types.
        psiClass.excludeOrNull(module, annotationFqName)
          ?.let { excludedTypesForClass[mergedClassKey] = it }

        val packageName = generatePackageName(psiClass)
        val className = psiClass.generateClassName()

        val directory = File(codeGenDir, packageName.replace('.', File.separatorChar))
        val file = File(directory, "$className.kt")
        check(file.parentFile.exists() || file.parentFile.mkdirs()) {
          "Could not generate package directory: ${file.parentFile}"
        }

        // We cheat and don't actually write the content to the file. We write the content in the
        // flush() method when we collected all binding methods to avoid two writes.
        check(file.exists() || file.createNewFile()) {
          "Could not create new file: $file"
        }

        val content = daggerModuleContent(
          scope = scope.asString(),
          psiClass = psiClass,
          generatedMethods = emptyList()
        )

        mergedClasses[mergedClassKey] = file

        GeneratedFile(file, content)
      }
      .toList()
  }

  override fun flush(
    codeGenDir: File,
    module: ModuleDescriptor
  ): Collection<GeneratedFile> {
    return mergedClasses.map { (mergedClassKey, daggerModuleFile) ->
      val scope = mergedClassKey.scope

      // Precompute this list once per class since it's expensive.
      val excludedNames = excludedTypesForClass[mergedClassKey].orEmpty()

      // Contributed Dagger modules can replace other Dagger modules but also contributed bindings.
      // If a binding is replaced, then we must not generate the binding method.
      //
      // We precompute this list here and share the result in the methods below. Resolving classes
      // and types can be an expensive operation, so avoid doing it twice.
      val bindingsReplacedInDaggerModules = contributedModuleClasses
        .asSequence()
        .filter { it.scope(contributesToFqName, module) == scope }
        // Ignore replaced bindings specified by excluded modules for this scope.
        .filter { it.fqName !in excludedNames }
        .flatMap { it.replaces(contributesToFqName, module) }
        .plus(
          classScanner
            .findContributedClasses(
              module = module,
              packageName = HINT_CONTRIBUTES_PACKAGE_PREFIX,
              annotation = contributesToFqName,
              scope = scope
            )
            .filter { it.annotationOrNull(daggerModuleFqName) != null }
            // Ignore replaced bindings specified by excluded modules for this scope.
            .filter { it.fqNameSafe !in excludedNames }
            .flatMap {
              it.annotationOrNull(contributesToFqName, scope)
                ?.replaces(module)
                ?: emptyList()
            }
            .map { it.fqNameSafe }
        )
        .toList()

      // Note that this is an inner function to share some of the parameters. It computes all
      // generated functions for contributed bindings and multibindings. The generated functions
      // are so similar that it makes sense to share the code between normal binding and
      // multibinding methods.
      fun getContributedBindingClasses(
        collectedClasses: List<FqName>,
        hintPackagePrefix: String,
        annotationFqName: FqName,
        isMultibinding: Boolean
      ): List<GeneratedMethod> {

        val contributedBindingsDependencies = classScanner.findContributedClasses(
          module,
          packageName = hintPackagePrefix,
          annotation = annotationFqName,
          scope = scope
        ).map { it.toClassReference() }

        val allContributedClasses = collectedClasses
          .map { name -> module.requireClassReference(name) }
          .plus(contributedBindingsDependencies)

        val replacedBindings = allContributedClasses
          .flatMap { classReference ->

            when (classReference) {
              is Descriptor -> classReference.clazz.annotationOrNull(annotationFqName, scope)
                ?.replaces(module)
                ?.map { it.fqNameSafe }
              is Psi -> classReference.clazz.replaces(annotationFqName, module)
            }
              .orEmpty()
          }

        return allContributedClasses
          .asSequence()
          .filterNot { it.fqName in replacedBindings }
          .filterNot { it.fqName in bindingsReplacedInDaggerModules }
          .filterNot { it.fqName in excludedNames }
          .filter { scope == it.scopeOrNull(module, annotationFqName) }
          .map {
            it.toContributedBinding(
              module = module,
              annotationFqName = annotationFqName,
              isMultibinding = isMultibinding
            )
          }
          .let { bindings ->
            if (isMultibinding) {
              bindings
            } else {
              bindings.groupBy { it.qualifiersKeyLazy.value }
                .map { it.value.findHighestPriorityBinding() }
                .asSequence()
            }
          }
          .map { binding -> binding.toGeneratedMethod(module, isMultibinding) }
          .toList()
      }

      val generatedMethods = getContributedBindingClasses(
        collectedClasses = contributedBindingClasses,
        hintPackagePrefix = HINT_BINDING_PACKAGE_PREFIX,
        annotationFqName = contributesBindingFqName,
        isMultibinding = false
      ) + getContributedBindingClasses(
        collectedClasses = contributedMultibindingClasses,
        hintPackagePrefix = HINT_MULTIBINDING_PACKAGE_PREFIX,
        annotationFqName = contributesMultibindingFqName,
        isMultibinding = true
      )

      val content = daggerModuleContent(
        scope = scope.asString(),
        psiClass = mergedClassKey.clazz,
        generatedMethods = generatedMethods
      )

      daggerModuleFile.writeText(content)

      GeneratedFile(daggerModuleFile, content)
    }
  }

  private fun findContributedBindingClasses(
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>,
    hintPackagePrefix: String
  ): List<FqName> {
    return projectFiles
      .filter {
        it.packageFqName.asString().startsWith(hintPackagePrefix)
      }
      .flatMap {
        it.findChildrenByClass(KtProperty::class.java).toList()
      }
      .mapNotNull { ktProperty ->
        (ktProperty.initializer as? KtClassLiteralExpression)
          ?.firstChild
          ?.requireFqName(module)
      }
  }

  private fun findContributedModules(
    classes: List<KtClassOrObject>,
    module: ModuleDescriptor
  ) {
    contributedModuleClasses += classes
      .filter {
        it.hasAnnotation(contributesToFqName, module) &&
          it.hasAnnotation(daggerModuleFqName, module)
      }
  }

  private fun KtClassOrObject.generateClassName(): String =
    generateClassName(separator = "") + ANVIL_MODULE_SUFFIX

  private fun generatePackageName(psiClass: KtClassOrObject): String = MODULE_PACKAGE_PREFIX +
    psiClass.containingKtFile.packageFqName.safePackageString(dotPrefix = true)

  private fun daggerModuleContent(
    scope: String,
    psiClass: KtClassOrObject,
    generatedMethods: List<GeneratedMethod>
  ): String {
    val className = psiClass.generateClassName()

    val bindingMethods = generatedMethods.filterIsInstance<BindingMethod>()
    val providerMethods = generatedMethods.filterIsInstance<ProviderMethod>()

    return FileSpec.buildFile(generatePackageName(psiClass), className) {
      val builder = if (bindingMethods.isEmpty()) {
        TypeSpec.objectBuilder(className)
          .addFunctions(providerMethods.specs)
      } else {
        TypeSpec.classBuilder(className)
          .addModifiers(ABSTRACT)
          .addFunctions(bindingMethods.specs)
          .apply {
            if (providerMethods.isNotEmpty()) {
              addType(
                TypeSpec.companionObjectBuilder()
                  .addFunctions(providerMethods.specs)
                  .build()
              )
            }
          }
      }

      addType(
        builder
          .addAnnotation(Module::class)
          .addAnnotation(
            AnnotationSpec
              .builder(ContributesTo::class)
              .addMember("$scope::class")
              .build()
          )
          .build()
      )
    }
  }
}

private sealed class GeneratedMethod {
  abstract val spec: FunSpec

  class ProviderMethod(override val spec: FunSpec) : GeneratedMethod()
  class BindingMethod(override val spec: FunSpec) : GeneratedMethod()
}

private fun List<ContributedBinding>.findHighestPriorityBinding(): ContributedBinding {
  if (size == 1) return this[0]

  val bindings = groupBy { it.priority }
    .toSortedMap()
    .let { it.getValue(it.lastKey()) }
    // In some very rare cases we can see a binding for the same type twice. Just in case filter
    // them, see https://github.com/square/anvil/issues/460.
    .distinctBy { it.contributedFqName }

  if (bindings.size > 1) {
    throw AnvilCompilationException(
      "There are multiple contributed bindings with the same bound type. The bound type is " +
        "${bindings[0].boundTypeClassName}. The contributed binding classes are: " +
        bindings.joinToString(
          prefix = "[",
          postfix = "]"
        ) { it.contributedFqName.asString() }
    )
  }

  return bindings[0]
}

private fun ContributedBinding.toGeneratedMethod(
  module: ModuleDescriptor,
  isMultibinding: Boolean
): GeneratedMethod {

  val isMapMultibinding = mapKeys.isNotEmpty()

  return if (contributedClassIsObject) {
    ProviderMethod(
      FunSpec.builder(
        name = contributedFqName.asString()
          .split(".")
          .joinToString(
            separator = "",
            prefix = "provide",
            postfix = if (isMultibinding) "Multi" else ""
          ) {
            it.capitalize()
          }
      )
        .addAnnotation(Provides::class)
        .apply {
          when {
            isMapMultibinding -> addAnnotation(IntoMap::class)
            isMultibinding -> addAnnotation(IntoSet::class)
          }
        }
        .addAnnotations(qualifiers)
        .addAnnotations(mapKeys)
        .returns(boundTypeClassName)
        .addStatement("return %T", contributedFqName.asClassName(module))
        .build()
    )
  } else {
    BindingMethod(
      FunSpec.builder(
        name = contributedFqName.asString()
          .split(".")
          .joinToString(
            separator = "",
            prefix = "bind",
            postfix = if (isMultibinding) "Multi" else ""
          ) {
            it.capitalize()
          }
      )
        .addAnnotation(Binds::class)
        .apply {
          when {
            isMapMultibinding -> addAnnotation(IntoMap::class)
            isMultibinding -> addAnnotation(IntoSet::class)
          }
        }
        .addAnnotations(qualifiers)
        .addAnnotations(mapKeys)
        .addModifiers(ABSTRACT)
        .addParameter(
          name = contributedFqName.shortName().asString().decapitalize(),
          type = contributedFqName.asClassName(module)
        )
        .returns(boundTypeClassName)
        .build()
    )
  }
}

private val Collection<GeneratedMethod>.specs get() = map { it.spec }

private class MergedClassKey(
  val scope: FqName,
  val clazz: KtClassOrObject
) {
  private val classFqName = clazz.requireFqName()

  override fun equals(other: Any?): Boolean {
    if (this === other) return true
    if (javaClass != other?.javaClass) return false

    other as MergedClassKey

    if (scope != other.scope) return false
    if (classFqName != other.classFqName) return false

    return true
  }

  override fun hashCode(): Int {
    var result = scope.hashCode()
    result = 31 * result + classFqName.hashCode()
    return result
  }
}

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.analyzer.AnalysisResult.RetryWithAdditionalRoots
import org.jetbrains.kotlin.com.intellij.openapi.project.Project
import org.jetbrains.kotlin.com.intellij.psi.PsiManager
import org.jetbrains.kotlin.com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import java.io.File

internal class CodeGenerationExtension(
  private val codeGenDir: File,
  codeGenerators: List<CodeGenerator>
) : AnalysisHandlerExtension {

  private var didRecompile = false

  private val codeGenerators = codeGenerators
    .onEach {
      check(it !is FlushingCodeGenerator || it !is PrivateCodeGenerator) {
        "A code generator can't be a private code generator and flushing code generator at the " +
          "same time. Private code generators don't impact other code generators, therefore " +
          "they shouldn't need to flush files after other code generators generated code."
      }
    }
    // Use a stable sort in case code generators depend on the order.
    // At least don't make it random.
    .sortedWith(compareBy({ it is PrivateCodeGenerator }, { it::class.qualifiedName }))

  override fun doAnalysis(
    project: Project,
    module: ModuleDescriptor,
    projectContext: ProjectContext,
    files: Collection<KtFile>,
    bindingTrace: BindingTrace,
    componentProvider: ComponentProvider
  ): AnalysisResult? {
    // Tell the compiler that we have something to do in the analysisCompleted() method if
    // necessary.
    return if (!didRecompile && codeGenerators.isNotEmpty()) AnalysisResult.EMPTY else null
  }

  override fun analysisCompleted(
    project: Project,
    module: ModuleDescriptor,
    bindingTrace: BindingTrace,
    files: Collection<KtFile>
  ): AnalysisResult? {
    if (didRecompile || codeGenerators.isEmpty()) return null
    didRecompile = true

    // It's important to resolve types before clearing the output directory
    // to avoid https://youtrack.jetbrains.com/issue/KT-49340
    val psiManager = PsiManager.getInstance(project)
    val anvilModule = RealAnvilModuleDescriptor(module)
    anvilModule.addFiles(files)

    codeGenDir.listFiles()
      ?.forEach {
        check(it.deleteRecursively()) {
          "Could not clean file: $it"
        }
      }

    val generatedFilePaths = mutableSetOf<String>()

    val (privateCodeGenerators, nonPrivateCodeGenerators) =
      codeGenerators.partition { it is PrivateCodeGenerator }

    fun Collection<GeneratedFile>.toKtFile(): Collection<KtFile> {
      return this
        .mapNotNull { (file, content) ->
          val virtualFile = LightVirtualFile(
            file.relativeTo(codeGenDir).path,
            KotlinFileType.INSTANCE,
            content
          )

          psiManager.findFile(virtualFile)
        }
        .filterIsInstance<KtFile>()
        .also { anvilModule.addFiles(it) }
    }

    fun Collection<CodeGenerator>.generateCode(files: Collection<KtFile>): Collection<KtFile> =
      flatMap { codeGenerator ->
        codeGenerator.generateCode(codeGenDir, anvilModule, files)
          .onEach { generatedFile ->
            val path = generatedFile.file.relativeTo(codeGenDir).path
            if (!generatedFilePaths.add(path)) {
              throw AnvilCompilationException(
                "There were duplicate generated files. Generating and overwriting the same file " +
                  "leads to unexpected results. The relative path is: $path. The file was " +
                  "generated by ${codeGenerator::class}. The input files were " +
                  files.joinToString(prefix = "[", postfix = "].") { ktFile ->
                    "${ktFile.packageFqName}.${ktFile.name}"
                  }
              )
            }
          }
          .toKtFile()
      }

    fun Collection<CodeGenerator>.flush(): Collection<KtFile> =
      filterIsInstance<FlushingCodeGenerator>()
        .flatMap { codeGenerator ->
          codeGenerator.flush(codeGenDir, anvilModule).toKtFile()
        }

    var newFiles = nonPrivateCodeGenerators.generateCode(files)

    while (newFiles.isNotEmpty()) {
      // Parse the KtFile for each generated file. Then feed the code generators with the new
      // parsed files until no new files are generated.
      newFiles = nonPrivateCodeGenerators.generateCode(newFiles)
    }

    nonPrivateCodeGenerators.flush()

    // PrivateCodeGenerators don't impact other code generators. Therefore, they can be called a
    // single time at the end.
    privateCodeGenerators.generateCode(anvilModule.allFiles)

    // This restarts the analysis phase and will include our files.
    return RetryWithAdditionalRoots(
      bindingContext = bindingTrace.bindingContext,
      moduleDescriptor = anvilModule,
      additionalJavaRoots = emptyList(),
      additionalKotlinRoots = listOf(codeGenDir),
      addToEnvironment = true
    )
  }
}

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.annotations.ContributesBinding.Priority
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.boundTypeOrNull
import com.squareup.anvil.compiler.ignoreQualifier
import com.squareup.anvil.compiler.injectFqName
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.annotation
import com.squareup.anvil.compiler.internal.argumentType
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.directSuperClassReferences
import com.squareup.anvil.compiler.internal.isMapKey
import com.squareup.anvil.compiler.internal.isObject
import com.squareup.anvil.compiler.internal.isQualifier
import com.squareup.anvil.compiler.internal.qualifiers
import com.squareup.anvil.compiler.internal.requireAnnotation
import com.squareup.anvil.compiler.internal.requireClassDescriptor
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.requireKtClassOrObject
import com.squareup.anvil.compiler.internal.toAnnotationSpec
import com.squareup.anvil.compiler.isMapKey
import com.squareup.anvil.compiler.priority
import com.squareup.anvil.compiler.qualifierFqName
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.TypeName
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType

internal data class ContributedBinding(
  val contributedFqName: FqName,
  val contributedClassIsObject: Boolean,
  val mapKeys: List<AnnotationSpec>,
  val qualifiers: List<AnnotationSpec>,
  val boundTypeClassName: TypeName,
  val priority: Priority,
  val qualifiersKeyLazy: Lazy<String>
)

internal fun ClassReference.toContributedBinding(
  module: ModuleDescriptor,
  annotationFqName: FqName,
  isMultibinding: Boolean
): ContributedBinding {

  val boundType = requireBoundType(module, annotationFqName)

  val mapKeys = if (isMultibinding) {
    mapKeys(module)
  } else {
    emptyList()
  }

  val ignoreQualifier = ignoreQualifier(module, annotationFqName)
  val qualifiers = if (ignoreQualifier) {
    emptyList()
  } else {
    qualifiers(module)
  }

  val boundTypeClassName = when (boundType) {
    is Descriptor -> boundType.clazz.asClassName()
    is Psi -> boundType.clazz.asClassName()
  }

  return ContributedBinding(
    contributedFqName = fqName,
    contributedClassIsObject = isObject(),
    mapKeys = mapKeys,
    qualifiers = qualifiers,
    boundTypeClassName = boundTypeClassName,
    priority = priority(module, annotationFqName),
    qualifiersKeyLazy = qualifiersKeyLazy(module, boundType.fqName, ignoreQualifier)
  )
}

private fun ClassReference.requireBoundType(
  module: ModuleDescriptor,
  annotationFqName: FqName
): ClassReference {
  val boundFromAnnotation = when (this) {
    is Descriptor -> clazz.annotation(annotationFqName)
      .boundTypeOrNull(module)
      ?.fqNameSafe
    is Psi -> clazz.boundTypeOrNull(annotationFqName, module)
  }

  if (boundFromAnnotation != null) {
    // ensure that the bound type is actually a supertype of the contributing class
    val boundType = allSuperTypeClassReferences(module)
      .firstOrNull { it.fqName == boundFromAnnotation }
      ?: throw AnvilCompilationException(
        "$fqName contributes a binding for $boundFromAnnotation, but doesn't extend this type."
      )

    boundType.checkNotGeneric(contributedFqName = fqName)
    return boundType
  }

  // If there's no bound type in the annotation,
  // it must be the only supertype of the contributing class
  val boundType = directSuperClassReferences(module).singleOrNull()
    ?: throw AnvilCompilationException(
      message = "$annotationFqName contributes a binding, but does not " +
        "specify the bound type. This is only allowed with exactly one direct super type. " +
        "If there are multiple or none, then the bound type must be explicitly defined in " +
        "the @${annotationFqName.shortName()} annotation.",
      element = module.requireKtClassOrObject(fqName)
    )

  boundType.checkNotGeneric(contributedFqName = fqName)
  return boundType
}

private fun ClassReference.checkNotGeneric(
  contributedFqName: FqName
) {
  fun exceptionText(typeString: String): String {
    return "Class $contributedFqName binds $fqName," +
      " but the bound type contains type parameter(s) $typeString." +
      " Type parameters in bindings are not supported. This binding needs" +
      " to be contributed in a Dagger module manually."
  }

  fun KotlinType.describeTypeParameters(): String = arguments
    .ifEmpty { return "" }
    .joinToString(prefix = "<", postfix = ">") { typeArgument ->
      typeArgument.type.toString() + typeArgument.type.describeTypeParameters()
    }

  when (this) {
    is Descriptor -> {
      if (clazz.declaredTypeParameters.isNotEmpty()) {

        throw AnvilCompilationException(
          classDescriptor = clazz,
          message = exceptionText(clazz.defaultType.describeTypeParameters())
        )
      }
    }
    is Psi -> {
      if (clazz.typeParameters.isNotEmpty()) {
        val typeString = clazz.typeParameters
          .joinToString(prefix = "<", postfix = ">") { it.name!! }

        throw AnvilCompilationException(
          message = exceptionText(typeString),
          element = clazz.nameIdentifier
        )
      }
    }
  }
}

private fun ClassReference.ignoreQualifier(
  module: ModuleDescriptor,
  annotationFqName: FqName
): Boolean {
  return when (this) {
    is Descriptor -> clazz.annotation(annotationFqName).ignoreQualifier()
    is Psi -> clazz.ignoreQualifier(module, annotationFqName)
  }
}

private fun ClassReference.mapKeys(
  module: ModuleDescriptor
): List<AnnotationSpec> {
  return when (this) {
    is Descriptor ->
      clazz.annotations
        .filter { it.isMapKey() }
        .map { it.toAnnotationSpec(module) }
    is Psi ->
      clazz.annotationEntries
        .filter { it.isMapKey(module) }
        .map { it.toAnnotationSpec(module) }
  }
}

private fun ClassReference.isObject(): Boolean {
  return when (this) {
    is Descriptor -> DescriptorUtils.isObject(clazz)
    is Psi -> clazz.isObject()
  }
}

private fun ClassReference.priority(
  module: ModuleDescriptor,
  annotationFqName: FqName
): Priority {
  return when (this) {
    is Descriptor -> clazz.annotation(annotationFqName).priority()
    is Psi -> clazz.requireAnnotation(annotationFqName, module).priority()
  }
}

private fun ClassReference.qualifiersKeyLazy(
  module: ModuleDescriptor,
  boundTypeFqName: FqName,
  ignoreQualifier: Boolean
): Lazy<String> {

  // Careful! If we ever decide to support generic types, then we might need to use the
  // Kotlin type and not just the FqName.
  if (ignoreQualifier) {
    return lazy { boundTypeFqName.asString() }
  }

  return when (this) {
    is Descriptor -> lazy { clazz.qualifiersKey(module, boundTypeFqName) }
    is Psi -> lazy { clazz.qualifiersKey(module, boundTypeFqName) }
  }
}

private fun KtClassOrObject.qualifiersKey(
  module: ModuleDescriptor,
  boundTypeFqName: FqName
): String {

  // Note that we sort all elements. That's important for a stable string comparison.
  val allArguments = annotationEntries
    // filter out anything which isn't a qualifier
    .mapNotNull { annotationEntry ->
      val descriptor = annotationEntry.typeReference
        ?.requireFqName(module)
        ?.takeIf { it != injectFqName }
        ?.requireClassDescriptor(module)
        ?.takeIf { it.annotations.hasAnnotation(qualifierFqName) }
        ?: return@mapNotNull null
      annotationEntry to descriptor
    }
    .sortedBy { it.second.fqNameSafe.hashCode() }
    .joinToString(separator = "") { (entry, descriptor) ->

      val annotationFqName = descriptor.fqNameSafe
      val annotationFqNameString = annotationFqName.asString()

      val valueParams = descriptor.constructors
        .first()
        .valueParameters

      val argumentString = entry.valueArguments
        .mapIndexed { index, valueArgument ->

          // If arguments don't have names, then the name can be found at that index
          val name = valueArgument.getArgumentName()?.asName?.identifier
            ?: valueParams[index].name.identifier

          fun KtExpression.valueString(): String {
            return when (this) {
              is KtClassLiteralExpression -> requireFqName(module).asString()
              is KtNameReferenceExpression -> getReferencedName()
              // primitives
              else -> text
            }
          }

          val valueString = valueArgument.getArgumentExpression()?.valueString()

          name + valueString
        }

      annotationFqNameString + argumentString
    }

  return boundTypeFqName.asString() + allArguments
}

private fun ClassDescriptor.qualifiersKey(
  module: ModuleDescriptor,
  boundTypeName: FqName
): String {

  // Note that we sort all elements. That's important for a stable string comparison.
  val allArguments = annotations
    .filter { it.isQualifier() }
    .sortedBy { it.requireFqName().hashCode() }
    .joinToString(separator = "") { annotation ->
      val annotationFqName = annotation.requireFqName().asString()

      val argumentString = annotation.allValueArguments
        .toList()
        .sortedBy { it.first }
        .map { (name, value) ->
          val valueString = when (value) {
            is KClassValue -> value.argumentType(module)
              .requireClassDescriptor().fqNameSafe.asString()
            is EnumValue -> value.enumEntryName.asString()
            // String, int, long, ... other primitives.
            else -> value.toString()
          }

          name.asString() + valueString
        }

      annotationFqName + argumentString
    }

  return boundTypeName.asString() + allArguments
}

package com.squareup.anvil.compiler.codegen

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.HINT_BINDING_PACKAGE_PREFIX
import com.squareup.anvil.compiler.REFERENCE_SUFFIX
import com.squareup.anvil.compiler.SCOPE_SUFFIX
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.contributesBindingFqName
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.fqNameOrNull
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.isQualifier
import com.squareup.anvil.compiler.internal.requireClassDescriptor
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.scope
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier.PUBLIC
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asClassName
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
import java.io.File
import kotlin.reflect.KClass

/**
 * Generates a hint for each contributed class in the `anvil.hint.bindings` package. This allows
 * the compiler plugin to find all contributed bindings a lot faster when merging modules and
 * component interfaces.
 */
@AutoService(CodeGenerator::class)
internal class ContributesBindingGenerator : CodeGenerator {

  override fun isApplicable(context: AnvilContext) = !context.generateFactoriesOnly

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    return projectFiles
      .classesAndInnerClass(module)
      .filter { it.hasAnnotation(contributesBindingFqName, module) }
      .onEach { clazz ->
        clazz.checkClassIsPublic()
        clazz.checkNotMoreThanOneQualifier(module, contributesBindingFqName)
        clazz.checkSingleSuperType(module, contributesBindingFqName)
        clazz.checkClassExtendsBoundType(module, contributesBindingFqName)
      }
      .map { clazz ->
        val fileName = clazz.generateClassName()
        val generatedPackage = HINT_BINDING_PACKAGE_PREFIX +
          clazz.containingKtFile.packageFqName.safePackageString(dotPrefix = true)
        val className = clazz.asClassName()
        val classFqName = clazz.requireFqName().toString()
        val propertyName = classFqName.replace('.', '_')
        val scope = clazz.scope(contributesBindingFqName, module).asClassName(module)

        val content =
          FileSpec.buildFile(generatedPackage, fileName) {
            addProperty(
              PropertySpec
                .builder(
                  name = propertyName + REFERENCE_SUFFIX,
                  type = KClass::class.asClassName().parameterizedBy(className)
                )
                .initializer("%T::class", className)
                .addModifiers(PUBLIC)
                .build()
            )

            addProperty(
              PropertySpec
                .builder(
                  name = propertyName + SCOPE_SUFFIX,
                  type = KClass::class.asClassName().parameterizedBy(scope)
                )
                .initializer("%T::class", scope)
                .addModifiers(PUBLIC)
                .build()
            )
          }

        createGeneratedFile(
          codeGenDir = codeGenDir,
          packageName = generatedPackage,
          fileName = fileName,
          content = content
        )
      }
      .toList()
  }
}

internal fun KtClassOrObject.checkNotMoreThanOneQualifier(
  module: ModuleDescriptor,
  annotationFqName: FqName
) {
  // The class is annotated with @ContributesBinding. If there is less than 2 further
  // annotations, then there can't be more than two qualifiers.
  if (annotationEntries.size <= 2) return

  val qualifiers = annotationEntries.filter { it.isQualifier(module) }

  if (qualifiers.size > 1) {
    throw AnvilCompilationException(
      message = "Classes annotated with @${annotationFqName.shortName()} may not use more " +
        "than one @Qualifier.",
      element = this
    )
  }
}

internal fun KtClassOrObject.checkClassIsPublic() {
  if (visibilityModifierTypeOrDefault().value != KtTokens.PUBLIC_KEYWORD.value) {
    throw AnvilCompilationException(
      "${requireFqName()} is binding a type, but the class is not public. Only " +
        "public types are supported.",
      element = identifyingElement
    )
  }
}

internal fun KtClassOrObject.checkSingleSuperType(
  module: ModuleDescriptor,
  annotationFqName: FqName
) {
  // If the bound type exists, then you're allowed to have multiple super types. Without the bound
  // type there must be exactly one super type.
  if (boundTypeOrNull(annotationFqName, module) != null) return

  val superTypes = superTypeListEntries
  if (superTypes.size != 1) {
    throw AnvilCompilationException(
      element = this,
      message = "${requireFqName()} contributes a binding, but does not " +
        "specify the bound type. This is only allowed with exactly one direct super type. " +
        "If there are multiple or none, then the bound type must be explicitly defined in " +
        "the @${annotationFqName.shortName()} annotation."
    )
  }
}

internal fun KtClassOrObject.checkClassExtendsBoundType(
  module: ModuleDescriptor,
  annotationFqName: FqName
) {
  // Check the super type through the Psi APIs first.
  val boundType = boundTypeOrNull(annotationFqName, module)
    ?: superTypeListEntries.singleOrNull()?.requireFqName(module)
    ?: throw AnvilCompilationException(
      element = this,
      message = "Couldn't find the bound type."
    )

  val hasSuperTypePsi = superTypeListEntries.any { it.fqNameOrNull(module) == boundType }
  if (hasSuperTypePsi) return

  val descriptor = requireClassDescriptor(module)
  val hasSuperType = descriptor.getAllSuperClassifiers()
    .any { it.fqNameSafe == boundType }

  if (!hasSuperType) {
    throw AnvilCompilationException(
      classDescriptor = descriptor,
      message = "${descriptor.fqNameSafe} contributes a binding for $boundType, but doesn't " +
        "extend this type."
    )
  }
}

package com.squareup.anvil.compiler.codegen

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.HINT_MULTIBINDING_PACKAGE_PREFIX
import com.squareup.anvil.compiler.REFERENCE_SUFFIX
import com.squareup.anvil.compiler.SCOPE_SUFFIX
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.contributesMultibindingFqName
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.requireClassDescriptor
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.scope
import com.squareup.anvil.compiler.isMapKey
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier.PUBLIC
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asClassName
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import kotlin.reflect.KClass

/**
 * Generates a hint for each contributed class in the `anvil.hint.multibinding` package. This
 * allows the compiler plugin to find all contributed multibindings a lot faster when merging
 * modules and component interfaces.
 */
@AutoService(CodeGenerator::class)
internal class ContributesMultibindingGenerator : CodeGenerator {

  override fun isApplicable(context: AnvilContext) = !context.generateFactoriesOnly

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    return projectFiles
      .classesAndInnerClass(module)
      .filter { it.hasAnnotation(contributesMultibindingFqName, module) }
      .onEach { clazz ->
        clazz.checkClassIsPublic()
        clazz.checkNotMoreThanOneQualifier(module, contributesMultibindingFqName)
        clazz.checkNotMoreThanOneMapKey(module)
        clazz.checkSingleSuperType(module, contributesMultibindingFqName)
        clazz.checkClassExtendsBoundType(module, contributesMultibindingFqName)
      }
      .map { clazz ->
        val fileName = clazz.generateClassName()
        val generatedPackage = HINT_MULTIBINDING_PACKAGE_PREFIX +
          clazz.containingKtFile.packageFqName.safePackageString(dotPrefix = true)
        val className = clazz.asClassName()
        val classFqName = clazz.requireFqName().toString()
        val propertyName = classFqName.replace('.', '_')
        val scope = clazz.scope(contributesMultibindingFqName, module).asClassName(module)

        val content =
          FileSpec.buildFile(generatedPackage, fileName) {
            addProperty(
              PropertySpec
                .builder(
                  name = propertyName + REFERENCE_SUFFIX,
                  type = KClass::class.asClassName().parameterizedBy(className)
                )
                .initializer("%T::class", className)
                .addModifiers(PUBLIC)
                .build()
            )

            addProperty(
              PropertySpec
                .builder(
                  name = propertyName + SCOPE_SUFFIX,
                  type = KClass::class.asClassName().parameterizedBy(scope)
                )
                .initializer("%T::class", scope)
                .addModifiers(PUBLIC)
                .build()
            )
          }

        createGeneratedFile(
          codeGenDir = codeGenDir,
          packageName = generatedPackage,
          fileName = fileName,
          content = content
        )
      }
      .toList()
  }
}

private fun KtClassOrObject.checkNotMoreThanOneMapKey(
  module: ModuleDescriptor
) {
  // The class is annotated with @ContributesMultibinding. If there is less than 2 further
  // annotations, then there can't be more than two map keys.
  if (annotationEntries.size <= 2) return

  val mapKeys = requireClassDescriptor(module).annotations.filter { it.isMapKey() }

  if (mapKeys.size > 1) {
    throw AnvilCompilationException(
      message = "Classes annotated with @${contributesMultibindingFqName.shortName()} may not " +
        "use more than one @MapKey.",
      element = this
    )
  }
}

package com.squareup.anvil.compiler.codegen

import com.google.auto.service.AutoService
import com.squareup.anvil.annotations.ContributesSubcomponent
import com.squareup.anvil.compiler.HINT_SUBCOMPONENTS_PACKAGE_PREFIX
import com.squareup.anvil.compiler.REFERENCE_SUFFIX
import com.squareup.anvil.compiler.SCOPE_SUFFIX
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.contributesSubcomponentFactoryFqName
import com.squareup.anvil.compiler.contributesSubcomponentFqName
import com.squareup.anvil.compiler.contributesToFqName
import com.squareup.anvil.compiler.daggerSubcomponentBuilderFqName
import com.squareup.anvil.compiler.daggerSubcomponentFactoryFqName
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.fqNameOrNull
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.isInterface
import com.squareup.anvil.compiler.internal.parentScope
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.scope
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier.PUBLIC
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asClassName
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassBody
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
import java.io.File
import kotlin.reflect.KClass

/**
 * Generates a hint for each contributed subcomponent in the `anvil.hint.subcomponent` packages.
 * This allows the compiler plugin to find all contributed classes a lot faster.
 */
@AutoService(CodeGenerator::class)
internal class ContributesSubcomponentGenerator : CodeGenerator {

  override fun isApplicable(context: AnvilContext) = !context.generateFactoriesOnly

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    return projectFiles
      .classesAndInnerClass(module)
      .filter { it.hasAnnotation(contributesSubcomponentFqName, module) }
      .onEach { clazz ->
        if (!clazz.isInterface() && !clazz.hasModifier(ABSTRACT_KEYWORD)) {
          throw AnvilCompilationException(
            "${clazz.requireFqName()} is annotated with " +
              "@${ContributesSubcomponent::class.simpleName}, but this class is not an interface.",
            element = clazz.identifyingElement
          )
        }

        if (clazz.visibilityModifierTypeOrDefault().value != KtTokens.PUBLIC_KEYWORD.value) {
          throw AnvilCompilationException(
            "${clazz.requireFqName()} is contributed to the Dagger graph, but the " +
              "interface is not public. Only public interfaces are supported.",
            element = clazz.identifyingElement
          )
        }
      }
      .map { clazz ->
        val fileName = clazz.generateClassName()
        val generatedPackage = HINT_SUBCOMPONENTS_PACKAGE_PREFIX +
          clazz.containingKtFile.packageFqName.safePackageString(dotPrefix = true)
        val className = clazz.asClassName()
        val classFqName = clazz.requireFqName().toString()
        val propertyName = classFqName.replace('.', '_')
        val parentScopeFqName = clazz.parentScope(contributesSubcomponentFqName, module)
        val parentScope = parentScopeFqName.asClassName(module)

        val innerClasses = clazz.childrenInBody.filterIsInstance<KtClassOrObject>()
        clazz.checkParentComponentInterface(innerClasses, module, parentScopeFqName)
        clazz.checkFactory(innerClasses, module)

        val content =
          FileSpec.buildFile(generatedPackage, fileName) {
            addProperty(
              PropertySpec
                .builder(
                  name = propertyName + REFERENCE_SUFFIX,
                  type = KClass::class.asClassName().parameterizedBy(className)
                )
                .initializer("%T::class", className)
                .addModifiers(PUBLIC)
                .build()
            )

            addProperty(
              PropertySpec
                .builder(
                  name = propertyName + SCOPE_SUFFIX,
                  type = KClass::class.asClassName().parameterizedBy(parentScope)
                )
                .initializer("%T::class", parentScope)
                .addModifiers(PUBLIC)
                .build()
            )
          }

        createGeneratedFile(
          codeGenDir = codeGenDir,
          packageName = generatedPackage,
          fileName = fileName,
          content = content
        )
      }
      .toList()
  }

  private fun KtClassOrObject.checkParentComponentInterface(
    innerClasses: List<KtClassOrObject>,
    module: ModuleDescriptor,
    parentScope: FqName
  ) {
    val fqName = requireFqName()

    val parentComponents = innerClasses
      .filter {
        if (!it.hasAnnotation(contributesToFqName, module)) return@filter false
        it.scope(contributesToFqName, module) == parentScope
      }

    val componentInterface = when (parentComponents.size) {
      0 -> return
      1 -> parentComponents[0]
      else -> throw AnvilCompilationException(
        element = this,
        message = "Expected zero or one parent component interface within " +
          "${requireFqName()} being contributed to the parent scope."
      )
    }

    val functions = componentInterface.childrenInBody
      .filterIsInstance<KtFunction>()
      .filter { it.typeReference?.fqNameOrNull(module) == fqName }

    if (functions.size >= 2) {
      throw AnvilCompilationException(
        element = this,
        message = "Expected zero or one function returning the subcomponent $fqName."
      )
    }
  }

  private fun KtClassOrObject.checkFactory(
    innerClasses: List<KtClassOrObject>,
    module: ModuleDescriptor
  ) {
    val fqName = requireFqName()

    innerClasses
      .firstOrNull { it.hasAnnotation(daggerSubcomponentFactoryFqName, module) }
      ?.let { factoryClass ->
        throw AnvilCompilationException(
          element = factoryClass,
          message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " +
            "must use $contributesSubcomponentFactoryFqName and not " +
            "$daggerSubcomponentFactoryFqName."
        )
      }

    innerClasses
      .firstOrNull { it.hasAnnotation(daggerSubcomponentBuilderFqName, module) }
      ?.let { factoryClass ->
        throw AnvilCompilationException(
          element = factoryClass,
          message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " +
            "must use $contributesSubcomponentFactoryFqName and not " +
            "$daggerSubcomponentBuilderFqName. Builders aren't supported."
        )
      }

    val factories = innerClasses
      .filter { it.hasAnnotation(contributesSubcomponentFactoryFqName, module) }

    val factory = when (factories.size) {
      0 -> return
      1 -> factories[0]
      else -> throw AnvilCompilationException(
        element = this,
        message = "Expected zero or one factory within ${requireFqName()}."
      )
    }

    if (!factory.isInterface() && !factory.hasModifier(ABSTRACT_KEYWORD)) {
      throw AnvilCompilationException(
        element = factory,
        message = "A factory must be an interface or an abstract class."
      )
    }

    val functions = factory.childrenInBody
      .filterIsInstance<KtFunction>()
      .let { functions ->
        if (factory.isInterface()) {
          functions
        } else {
          functions.filter { it.hasModifier(ABSTRACT_KEYWORD) }
        }
      }

    if (functions.size != 1 || functions[0].typeReference?.fqNameOrNull(module) != fqName) {
      throw AnvilCompilationException(
        element = factory,
        message = "A factory must have exactly one abstract function returning the " +
          "subcomponent $fqName."
      )
    }
  }

  private val KtClassOrObject.childrenInBody: List<PsiElement>
    get() = children.filterIsInstance<KtClassBody>().singleOrNull()?.children?.toList()
      ?: emptyList()
}

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
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.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.contributesSubcomponentFactoryFqName
import com.squareup.anvil.compiler.contributesSubcomponentFqName
import com.squareup.anvil.compiler.contributesToFqName
import com.squareup.anvil.compiler.internal.ClassReference
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.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.daggerScopes
import com.squareup.anvil.compiler.internal.findAnnotation
import com.squareup.anvil.compiler.internal.findAnnotationArgument
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.getAnnotationValue
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.parentScope
import com.squareup.anvil.compiler.internal.requireClassDescriptor
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.scope
import com.squareup.anvil.compiler.internal.toClassId
import com.squareup.anvil.compiler.internal.toClassReference
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.mergeInterfacesFqName
import com.squareup.anvil.compiler.mergeSubcomponentFqName
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.ABSTRACT
import com.squareup.kotlinpoet.KModifier.OVERRIDE
import com.squareup.kotlinpoet.TypeSpec
import dagger.Subcomponent
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities.PUBLIC
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.CLASSIFIERS
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.FUNCTIONS
import java.io.File

/**
 * Looks for `@MergeComponent`, `@MergeSubcomponent` or `@MergeModules` annotations and generates
 * the actual contributed subcomponents that specified these scopes as parent scope, e.g.
 *
 * ```
 * @MergeComponent(Unit::class)
 * interface ComponentInterface
 *
 * @ContributesSubcomponent(Any::class, parentScope = Unit::class)
 * interface SubcomponentInterface
 * ```
 * For this code snippet the code generator would generate:
 * ```
 * @MergeSubcomponent(Any::class)
 * interface SubcomponentInterfaceAnvilSubcomponent
 * ```
 */
internal class ContributesSubcomponentHandlerGenerator(
  private val classScanner: ClassScanner
) : CodeGenerator {

  private val triggers = mutableListOf<Trigger>()
  private val contributions = mutableListOf<Contribution>()

  override fun isApplicable(context: AnvilContext): Boolean =
    throw NotImplementedError(
      "This should not actually be checked as we instantiate this class manually."
    )

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {

    // Find new @MergeComponent (and similar triggers) that would cause generating new code.
    val newTriggers = projectFiles
      .classesAndInnerClass(module)
      .mapNotNull { clazz ->
        val annotation = generationTrigger.firstOrNull { trigger ->
          clazz.hasAnnotation(trigger, module)
        } ?: return@mapNotNull null

        val exclusions = clazz.excludeOrNull(module, annotation) ?: emptyList()

        Trigger(clazz, clazz.scope(annotation, module), exclusions)
      }
      .also {
        triggers += it
      }

    // If there is a new trigger, then find all contributed subcomponents from precompiled
    // dependencies for this scope and generate the necessary code.
    contributions += newTriggers
      .flatMap { trigger ->
        classScanner
          .findContributedClasses(
            module = module,
            packageName = HINT_SUBCOMPONENTS_PACKAGE_PREFIX,
            annotation = contributesSubcomponentFqName,
            // Don't use trigger.scope, because it refers to the parent scope of the
            // @ContributesSubcomponent annotation.
            scope = null
          )
          .mapNotNull { descriptor ->
            val annotation = descriptor.annotation(contributesSubcomponentFqName)

            val parentScope = annotation.parentScope(module).fqNameSafe
            if (parentScope != trigger.scope) return@mapNotNull null

            val modules = (annotation.getAnnotationValue("modules") as? ArrayValue)
              ?.value
              ?.map { it.argumentType(module).fqName() }
              ?: emptyList()

            val exclude = (annotation.getAnnotationValue("exclude") as? ArrayValue)
              ?.value
              ?.map { it.argumentType(module).fqName() }
              ?: emptyList()

            Contribution(
              classReference = descriptor.toClassReference(),
              scope = annotation.scope(module).fqNameSafe,
              parentScope = parentScope,
              modules = modules,
              exclude = exclude
            )
          }
      }

    // Find new contributed subcomponents in this module. If there's a trigger for them, then we
    // also need to generate code for them.
    contributions += projectFiles
      .classesAndInnerClass(module)
      .mapNotNull { clazz ->
        val annotation = clazz.findAnnotation(contributesSubcomponentFqName, module)
          ?: return@mapNotNull null

        val modules = annotation
          .findAnnotationArgument<KtCollectionLiteralExpression>("modules", 2)
          ?.toFqNames(module)
          ?: emptyList()

        val exclude = annotation
          .findAnnotationArgument<KtCollectionLiteralExpression>("exclude", 3)
          ?.toFqNames(module)
          ?: emptyList()

        Contribution(
          classReference = clazz.toClassReference(),
          scope = clazz.scope(contributesSubcomponentFqName, module),
          parentScope = clazz.parentScope(contributesSubcomponentFqName, module),
          modules = modules,
          exclude = exclude
        )
      }

    val newContributions = contributions
      .flatMap { contribution ->
        newTriggers
          .filter { trigger ->
            trigger.scope == contribution.parentScope &&
              contribution.clazzFqName !in trigger.exclusions
          }
          .map { trigger ->
            GenerateCodeEvent(trigger, contribution)
          }
      }

    contributions -= newContributions.mapTo(mutableSetOf()) { it.contribution }

    return newContributions
      .map { generateCodeEvent ->
        val contribution = generateCodeEvent.contribution
        val generatedAnvilSubcomponent = generateCodeEvent.generatedAnvilSubcomponent

        val generatedPackage = generatedAnvilSubcomponent.packageFqName.asString()
        val componentClassName = generatedAnvilSubcomponent.relativeClassName.asString()

        val factoryClass = findFactoryClass(contribution, generatedAnvilSubcomponent, module)

        val content = FileSpec.buildFile(generatedPackage, componentClassName) {
          TypeSpec
            .interfaceBuilder(componentClassName)
            .addSuperinterface(contribution.classReference.asClassName())
            .addAnnotation(
              AnnotationSpec
                .builder(MergeSubcomponent::class)
                .addMember("scope = %T::class", contribution.scope.asClassName(module))
                .apply {
                  fun addClassArrayMember(
                    name: String,
                    fqNames: List<FqName>
                  ) {
                    if (fqNames.isNotEmpty()) {
                      val classes = fqNames.map { it.asClassName(module) }
                      val template = classes
                        .joinToString(prefix = "[", postfix = "]") { "%T::class" }

                      addMember("$name = $template", *classes.toTypedArray())
                    }
                  }

                  addClassArrayMember("modules", contribution.modules)
                  addClassArrayMember("exclude", contribution.exclude)
                }
                .build()
            )
            .addAnnotations(contribution.classReference.daggerScopes(module))
            .apply {
              if (factoryClass != null) {
                addType(generateFactory(factoryClass.originalDescriptor, contribution, module))
              }
            }
            .addType(
              generateParentComponent(
                contribution, generatedAnvilSubcomponent, module, factoryClass
              )
            )
            .build()
            .also { addType(it) }
        }

        createGeneratedFile(
          codeGenDir = codeGenDir,
          packageName = generatedPackage,
          fileName = componentClassName,
          content = content
        )
      }
      .toList()
  }

  private fun generateFactory(
    factoryDescriptor: ClassDescriptor,
    contribution: Contribution,
    module: ModuleDescriptor
  ): TypeSpec {
    val superclass = factoryDescriptor.asClassName()

    val builder = if (DescriptorUtils.isInterface(factoryDescriptor)) {
      TypeSpec
        .interfaceBuilder(SUBCOMPONENT_FACTORY)
        .addSuperinterface(superclass)
    } else {
      TypeSpec
        .classBuilder(SUBCOMPONENT_FACTORY)
        .addModifiers(ABSTRACT)
        .superclass(superclass)
    }

    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()
      )
      .build()
  }

  private fun generateParentComponent(
    contribution: Contribution,
    generatedAnvilSubcomponent: ClassId,
    module: ModuleDescriptor,
    factoryClass: FactoryClassHolder?,
  ): TypeSpec {
    val parentComponentInterface = findParentComponentInterface(
      contribution, module, factoryClass?.originalDescriptor?.fqNameSafe
    )

    return TypeSpec
      .interfaceBuilder(PARENT_COMPONENT)
      .apply {
        if (parentComponentInterface != null) {
          addSuperinterface(parentComponentInterface.componentInterface)
        }
      }
      .addFunction(
        FunSpec
          .builder(
            name = parentComponentInterface
              ?.functionName
              ?: factoryClass?.let { "create${it.originalDescriptor.name}" }
              ?: "create${generatedAnvilSubcomponent.relativeClassName}"
          )
          .addModifiers(ABSTRACT)
          .apply {
            if (parentComponentInterface != null) {
              addModifiers(OVERRIDE)
            }

            if (factoryClass != null) {
              returns(factoryClass.generatedFactoryName)
            } else {
              returns(generatedAnvilSubcomponent.asClassName())
            }
          }
          .build()
      )
      .build()
  }

  private fun findParentComponentInterface(
    contribution: Contribution,
    module: ModuleDescriptor,
    factoryClass: FqName?
  ): ParentComponentInterfaceHolder? {
    val contributionFqName = contribution.clazzFqName
    val contributionDescriptor = contributionFqName.requireClassDescriptor(module)

    val contributedInnerComponentInterfaces = contributionDescriptor.unsubstitutedMemberScope
      .getContributedDescriptors(kindFilter = CLASSIFIERS)
      .filterIsInstance<ClassDescriptor>()
      .filter { DescriptorUtils.isInterface(it) }
      .filter {
        val annotation = it.annotationOrNull(contributesToFqName, contribution.parentScope)
        annotation != null
      }

    val componentInterface = when (contributedInnerComponentInterfaces.size) {
      0 -> return null
      1 -> contributedInnerComponentInterfaces[0]
      else -> throw AnvilCompilationException(
        classDescriptor = contributionDescriptor,
        message = "Expected zero or one parent component interface within " +
          "${contributionDescriptor.fqNameSafe} being contributed to the parent scope."
      )
    }

    val functions = componentInterface.unsubstitutedMemberScope
      .getContributedDescriptors(kindFilter = FUNCTIONS)
      .filterIsInstance<FunctionDescriptor>()
      .filter { it.modality == Modality.ABSTRACT && it.visibility == PUBLIC }
      .filter {
        val returnType = it.returnType?.fqNameOrNull() ?: return@filter false
        returnType == contributionFqName || (factoryClass != null && returnType == factoryClass)
      }

    val function = when (functions.size) {
      0 -> return null
      1 -> functions[0]
      else -> throw AnvilCompilationException(
        classDescriptor = componentInterface,
        message = "Expected zero or one function returning the subcomponent $contributionFqName."
      )
    }

    return ParentComponentInterfaceHolder(componentInterface, function)
  }

  private fun findFactoryClass(
    contribution: Contribution,
    generatedAnvilSubcomponent: ClassId,
    module: ModuleDescriptor
  ): FactoryClassHolder? {
    val contributionFqName = contribution.clazzFqName
    val contributionDescriptor = contributionFqName.requireClassDescriptor(module)

    val contributedFactories = contributionDescriptor.unsubstitutedMemberScope
      .getContributedDescriptors(kindFilter = CLASSIFIERS)
      .filterIsInstance<ClassDescriptor>()
      .filter { it.annotations.hasAnnotation(contributesSubcomponentFactoryFqName) }
      .onEach { factory ->
        if (!DescriptorUtils.isInterface(factory) && factory.modality != Modality.ABSTRACT) {
          throw AnvilCompilationException(
            classDescriptor = factory,
            message = "A factory must be an interface or an abstract class."
          )
        }

        val createComponentFunctions = factory.unsubstitutedMemberScope
          .getContributedDescriptors(kindFilter = FUNCTIONS)
          .filterIsInstance<FunctionDescriptor>()
          .filter { it.modality == Modality.ABSTRACT }
          .filter { it.returnType?.fqNameOrNull() == contributionFqName }

        if (createComponentFunctions.size != 1) {
          throw AnvilCompilationException(
            classDescriptor = factory,
            message = "A factory must have exactly one abstract function returning the " +
              "subcomponent $contributionFqName."
          )
        }
      }

    val descriptor = when (contributedFactories.size) {
      0 -> return null
      1 -> contributedFactories[0]
      else -> throw AnvilCompilationException(
        classDescriptor = contributionDescriptor,
        message = "Expected zero or one factory within ${contributionDescriptor.fqNameSafe}."
      )
    }

    return FactoryClassHolder(
      originalDescriptor = descriptor,
      generatedFactoryName = generatedAnvilSubcomponent
        .createNestedClassId(Name.identifier(SUBCOMPONENT_FACTORY))
        .asClassName()
    )
  }

  private companion object {
    val generationTrigger = setOf(
      mergeComponentFqName,
      mergeSubcomponentFqName,
      // Note that we don't include @MergeModules, because we would potentially generate
      // components twice. @MergeInterfaces and @MergeModules are doing separately what
      // @MergeComponent is doing at once.
      mergeInterfacesFqName,
    )
  }

  private class Trigger(
    val clazz: KtClassOrObject,
    val scope: FqName,
    val exclusions: List<FqName>
  ) {
    override fun toString(): String {
      return "Trigger(clazz=$clazz, scope=$scope)"
    }
  }

  private class Contribution(
    val classReference: ClassReference,
    val scope: FqName,
    val parentScope: FqName,
    val modules: List<FqName>,
    val exclude: List<FqName>
  ) {
    val clazzFqName = classReference.fqName

    override fun toString(): String {
      return "Contribution(class=$classReference, parentScope=$parentScope)"
    }
  }

  private data class GenerateCodeEvent(
    val trigger: Trigger,
    val contribution: Contribution
  ) {
    val generatedAnvilSubcomponent = contribution.classReference.classId
      .generatedAnvilSubcomponent(trigger.clazz.toClassId())
  }

  private class ParentComponentInterfaceHolder(
    componentInterface: ClassDescriptor,
    function: FunctionDescriptor
  ) {
    val componentInterface = componentInterface.asClassName()
    val functionName = function.name.asString()
  }

  private class FactoryClassHolder(
    val originalDescriptor: ClassDescriptor,
    val generatedFactoryName: ClassName
  )
}

/**
 * Returns the Anvil subcomponent that will be generated for a class annotated with
 * `ContributesSubcomponent`.
 */
internal fun ClassId.generatedAnvilSubcomponent(parentClass: ClassId): ClassId {
  // Encode the parent class name in the package rather than the class name itself. This avoids
  // issues with too long class names. Dagger will generate subcomponents as inner classes and
  // deep hierarchies will be a problem. See https://github.com/google/dagger/issues/421
  //
  // To avoid potential issues, encode the parent class name in the package and keep the generated
  // class name short.
  val parentClassPackageSuffix = if (parentClass.relativeClassName.isRoot) {
    "root"
  } else {
    parentClass.relativeClassName.asString().lowercase()
  }

  // Note that use the package from the parent class and not our actual subcomponent. This is
  // necessary to avoid conflicts as well.
  val parentPackageName = parentClass.packageFqName
    .safePackageString(dotPrefix = false, dotSuffix = true) + parentClassPackageSuffix

  val packageFqName = if (parentPackageName.startsWith(COMPONENT_PACKAGE_PREFIX)) {
    // This happens if the parent is generated by Anvil itself. Avoid nesting too deeply.
    parentPackageName
  } else {
    "$COMPONENT_PACKAGE_PREFIX.$parentPackageName"
  }

  val relativeClassName = relativeClassName.generateClassName(suffix = ANVIL_SUBCOMPONENT_SUFFIX)

  return ClassId(FqName(packageFqName), relativeClassName.relativeClassName, false)
}

package com.squareup.anvil.compiler.codegen

import com.google.auto.service.AutoService
import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.compiler.HINT_CONTRIBUTES_PACKAGE_PREFIX
import com.squareup.anvil.compiler.REFERENCE_SUFFIX
import com.squareup.anvil.compiler.SCOPE_SUFFIX
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.contributesToFqName
import com.squareup.anvil.compiler.daggerModuleFqName
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.isInterface
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.scope
import com.squareup.anvil.compiler.mergeModulesFqName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier.PUBLIC
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asClassName
import dagger.Module
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
import java.io.File
import kotlin.reflect.KClass

/**
 * Generates a hint for each contributed class in the `hint.anvil` packages. This allows the
 * compiler plugin to find all contributed classes a lot faster when merging modules and component
 * interfaces.
 */
@AutoService(CodeGenerator::class)
internal class ContributesToGenerator : CodeGenerator {

  override fun isApplicable(context: AnvilContext) = !context.generateFactoriesOnly

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    return projectFiles
      .classesAndInnerClass(module)
      .filter { it.hasAnnotation(contributesToFqName, module) }
      .onEach { clazz ->
        if (!clazz.isInterface() &&
          !clazz.hasAnnotation(daggerModuleFqName, module) &&
          !clazz.hasAnnotation(mergeModulesFqName, module)
        ) {
          throw AnvilCompilationException(
            "${clazz.requireFqName()} is annotated with " +
              "@${ContributesTo::class.simpleName}, but this class is neither an interface " +
              "nor a Dagger module. Did you forget to add @${Module::class.simpleName}?",
            element = clazz.identifyingElement
          )
        }

        if (clazz.visibilityModifierTypeOrDefault().value != KtTokens.PUBLIC_KEYWORD.value) {
          throw AnvilCompilationException(
            "${clazz.requireFqName()} is contributed to the Dagger graph, but the " +
              "module is not public. Only public modules are supported.",
            element = clazz.identifyingElement
          )
        }
      }
      .map { clazz ->
        val fileName = clazz.generateClassName()
        val generatedPackage = HINT_CONTRIBUTES_PACKAGE_PREFIX +
          clazz.containingKtFile.packageFqName.safePackageString(dotPrefix = true)
        val className = clazz.asClassName()
        val classFqName = clazz.requireFqName().toString()
        val propertyName = classFqName.replace('.', '_')
        val scope = clazz.scope(contributesToFqName, module).asClassName(module)

        val content =
          FileSpec.buildFile(generatedPackage, fileName) {
            addProperty(
              PropertySpec
                .builder(
                  name = propertyName + REFERENCE_SUFFIX,
                  type = KClass::class.asClassName().parameterizedBy(className)
                )
                .initializer("%T::class", className)
                .addModifiers(PUBLIC)
                .build()
            )

            addProperty(
              PropertySpec
                .builder(
                  name = propertyName + SCOPE_SUFFIX,
                  type = KClass::class.asClassName().parameterizedBy(scope)
                )
                .initializer("%T::class", scope)
                .addModifiers(PUBLIC)
                .build()
            )
          }

        createGeneratedFile(
          codeGenDir = codeGenDir,
          packageName = generatedPackage,
          fileName = fileName,
          content = content
        )
      }
      .toList()
  }
}

package com.squareup.anvil.compiler.codegen

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.asTypeName
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.classDescriptorOrNull
import com.squareup.anvil.compiler.internal.isQualifier
import com.squareup.anvil.compiler.internal.requireClassDescriptor
import com.squareup.anvil.compiler.internal.toAnnotationSpec
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.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.utils.addToStdlib.safeAs

fun ClassDescriptor.memberInjectedPropertyDescriptors(): List<PropertyDescriptor> {

  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) }
}

fun PropertyDescriptor.hasAnnotation(annotationFqName: FqName): Boolean {

  // `@Inject lateinit var` is really `@field:Inject lateinit var`, which needs `backingField`
  return backingField?.annotations?.hasAnnotation(annotationFqName) == true ||
    setter?.annotations?.hasAnnotation(annotationFqName) == true ||
    annotations.hasAnnotation(annotationFqName)
}

fun PropertyDescriptor.isSetterInjected(): Boolean {
  return setter?.annotations?.hasAnnotation(injectFqName) == true
}

fun KotlinType.fqNameOrNull(): FqName? = classDescriptorOrNull()?.fqNameOrNull()

fun KotlinType.fqName(): FqName = requireClassDescriptor().fqNameSafe

internal fun List<CallableMemberDescriptor>.mapToMemberInjectParameters(
  module: ModuleDescriptor,
  containingClassName: ClassName,
  superParameters: List<Parameter>
): List<MemberInjectParameter> {

  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

    val typeFqName = type.fqNameOrNull()

    val isWrappedInProvider = typeFqName == providerFqName
    val isWrappedInLazy = typeFqName == daggerLazyFqName

    var isLazyWrappedInProvider = false

    val typeName = when {
      type.isNullable() -> type.asTypeName()
        .copy(nullable = true)

      isWrappedInLazy || isWrappedInProvider -> {
        val singleTypeParamType = type.arguments
          .singleOrNull()
          ?.type

        if (isWrappedInProvider && singleTypeParamType?.fqNameOrNull() == daggerLazyFqName) {

          // This is a super rare case when someone injects Provider<Lazy<Type>> that requires
          // special care.
          isLazyWrappedInProvider = true
          singleTypeParamType.asTypeName()
        } else {
          type.argumentType().asTypeName()
        }
      }

      else -> type.asTypeName()
    }.withJvmSuppressWildcardsIfNeeded(descriptor)

    val assistedAnnotation = descriptor.annotations
      .findAnnotation(assistedFqName)

    val assistedIdentifier = assistedAnnotation
      ?.allValueArguments
      ?.values
      ?.firstOrNull()
      ?.value as? String
      ?: ""

    val memberInjectorClassName = containingClassName.simpleNames
      .joinToString("_") + "_MembersInjector"

    val memberInjectorClass = ClassName(
      containingClassName.packageName, memberInjectorClassName
    )

    val isSetterInjected = descriptor.safeAs<PropertyDescriptor>()
      ?.isSetterInjected() == true

    // setter delegates require a "set" prefix for their inject function
    val accessName = if (isSetterInjected) {
      "set${originalName.capitalize()}"
    } else {
      originalName
    }

    val qualifierAnnotations = descriptor.annotations
      .filter { it.isQualifier() }
      .map { it.toAnnotationSpec(module) }

    acc + MemberInjectParameter(
      name = uniqueName,
      originalName = originalName,
      typeName = typeName,
      providerTypeName = typeName.wrapInProvider(),
      lazyTypeName = typeName.wrapInLazy(),
      isWrappedInProvider = isWrappedInProvider,
      isWrappedInLazy = isWrappedInLazy,
      isLazyWrappedInProvider = isLazyWrappedInProvider,
      isAssisted = assistedAnnotation != null,
      assistedIdentifier = assistedIdentifier,
      memberInjectorClassName = memberInjectorClass,
      isSetterInjected = isSetterInjected,
      accessName = accessName,
      qualifierAnnotationSpecs = qualifierAnnotations,
      injectedFieldSignature = descriptor.fqNameSafe
    )
  }
}

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import java.io.File

internal interface FlushingCodeGenerator : CodeGenerator {

  /**
   * Called after the last round of [generateCode] when no new files were produced by any code
   * generator anymore. The returned result should contain files that were added or changed one
   * last time. Code generates that do not impact other code generators get a last chance to
   * evaluate these results.
   */
  fun flush(
    codeGenDir: File,
    module: ModuleDescriptor
  ): Collection<GeneratedFile> = emptyList()
}

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.assistedFqName
import com.squareup.anvil.compiler.codegen.dagger.isSetterInjected
import com.squareup.anvil.compiler.daggerDoubleCheckFqNameString
import com.squareup.anvil.compiler.daggerLazyFqName
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.findAnnotation
import com.squareup.anvil.compiler.internal.fqNameOrNull
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.isNullable
import com.squareup.anvil.compiler.internal.qualifierAnnotationSpecs
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.requireTypeName
import com.squareup.anvil.compiler.internal.requireTypeReference
import com.squareup.anvil.compiler.internal.withJvmSuppressWildcardsIfNeeded
import com.squareup.anvil.compiler.providerFqName
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.asClassName
import dagger.Lazy
import dagger.internal.ProviderOfLazy
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.KtTypeElement
import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import javax.inject.Provider

private fun KtTypeElement.singleTypeArgument(): KtTypeReference {
  return children
    .filterIsInstance<KtTypeArgumentList>()
    .single()
    .children
    .filterIsInstance<KtTypeProjection>()
    .single()
    .children
    .filterIsInstance<KtTypeReference>()
    .single()
}

internal fun List<KtCallableDeclaration>.mapToConstructorParameters(
  module: ModuleDescriptor
): List<ConstructorParameter> =
  fold(listOf()) { acc, ktCallableDeclaration ->

    val uniqueName = ktCallableDeclaration.nameAsSafeName.asString().uniqueParameterName(acc)

    acc + ktCallableDeclaration.toConstructorParameter(module = module, uniqueName = uniqueName)
  }

private fun KtCallableDeclaration.toConstructorParameter(
  module: ModuleDescriptor,
  uniqueName: String
): ConstructorParameter {

  val typeElement = typeReference?.typeElement
  val typeFqName = typeElement?.fqNameOrNull(module)

  val isWrappedInProvider = typeFqName == providerFqName
  val isWrappedInLazy = typeFqName == daggerLazyFqName

  var isLazyWrappedInProvider = false

  val typeName = when {
    requireTypeReference(module).isNullable() ->
      requireTypeReference(module).requireTypeName(module).copy(nullable = true)

    isWrappedInProvider || isWrappedInLazy -> {
      val typeParameterReference = typeElement!!.singleTypeArgument()

      if (isWrappedInProvider &&
        typeParameterReference.fqNameOrNull(module) == daggerLazyFqName
      ) {
        // This is a super rare case when someone injects Provider<Lazy<Type>> that requires
        // special care.
        isLazyWrappedInProvider = true
        typeParameterReference.typeElement!!.singleTypeArgument().requireTypeName(module)
      } else {
        typeParameterReference.requireTypeName(module)
      }
    }

    else -> requireTypeReference(module).requireTypeName(module)
  }.withJvmSuppressWildcardsIfNeeded(this, module)

  val assistedAnnotation = findAnnotation(assistedFqName, module)
  val assistedIdentifier =
    (assistedAnnotation?.valueArguments?.firstOrNull() as? KtValueArgument)
      ?.children
      ?.filterIsInstance<KtStringTemplateExpression>()
      ?.single()
      ?.children
      ?.first()
      ?.text
      ?: ""

  return ConstructorParameter(
    name = uniqueName,
    originalName = nameAsSafeName.asString(),
    typeName = typeName,
    providerTypeName = typeName.wrapInProvider(),
    lazyTypeName = typeName.wrapInLazy(),
    isWrappedInProvider = isWrappedInProvider,
    isWrappedInLazy = isWrappedInLazy,
    isLazyWrappedInProvider = isLazyWrappedInProvider,
    isAssisted = assistedAnnotation != null,
    assistedIdentifier = assistedIdentifier
  )
}

internal fun List<KtProperty>.mapToMemberInjectParameters(
  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,
  uniqueName: String
): MemberInjectParameter {

  val constructorParameter = toConstructorParameter(module, uniqueName)

  val containingClass = containingClass()!!
  val packageName = containingClass.containingKtFile.packageFqName.asString()

  val memberInjectorClassName = "${containingClass.generateClassName()}_MembersInjector"
  val memberInjectorClass = ClassName(packageName, memberInjectorClassName)

  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()}"
  } else {
    originalName
  }
  return MemberInjectParameter(
    name = constructorParameter.name,
    typeName = constructorParameter.typeName,
    providerTypeName = constructorParameter.providerTypeName,
    lazyTypeName = constructorParameter.lazyTypeName,
    isWrappedInProvider = constructorParameter.isWrappedInProvider,
    isWrappedInLazy = constructorParameter.isWrappedInLazy,
    isLazyWrappedInProvider = constructorParameter.isLazyWrappedInProvider,
    isAssisted = constructorParameter.isAssisted,
    assistedIdentifier = constructorParameter.assistedIdentifier,
    memberInjectorClassName = memberInjectorClass,
    originalName = originalName,
    isSetterInjected = isSetterInjected,
    accessName = accessName,
    qualifierAnnotationSpecs = qualifierAnnotations,
    injectedFieldSignature = requireFqName()
  )
}

/**
 * Converts the parameter list to comma separated argument list that can be used to call other
 * functions, e.g.
 * ```
 * [param0: String, param1: Int] -> "param0, param1"
 * ```
 * [asProvider] allows you to decide if each parameter is wrapped in a `Provider` interface. If
 * true, then the `get()` function will be called for the provider parameter. If false, then
 * then always only the parameter name will used in the argument list:
 * ```
 * "param0.get()" vs "param0"
 * ```
 * Set [includeModule] to true if a Dagger module instance is part of the argument list.
 */
internal fun List<Parameter>.asArgumentList(
  asProvider: Boolean,
  includeModule: Boolean
): String {
  return this
    .let { list ->
      if (asProvider) {
        list.map { parameter ->
          when {
            parameter.isLazyWrappedInProvider ->
              "${ProviderOfLazy::class.qualifiedName}.create(${parameter.name})"
            parameter.isWrappedInProvider -> parameter.name
            // Normally Dagger changes Lazy<Type> parameters to a Provider<Type> (usually the
            // container is a joined type), therefore we use `.lazy(..)` to convert the Provider
            // to a Lazy. Assisted parameters behave differently and the Lazy type is not changed
            // to a Provider and we can simply use the parameter name in the argument list.
            parameter.isWrappedInLazy && parameter.isAssisted -> parameter.name
            parameter.isWrappedInLazy -> "$daggerDoubleCheckFqNameString.lazy(${parameter.name})"
            parameter.isAssisted -> parameter.name
            else -> "${parameter.name}.get()"
          }
        }
      } else list.map { it.name }
    }
    .let {
      if (includeModule) {
        val result = it.toMutableList()
        result.add(0, "module")
        result.toList()
      } else {
        it
      }
    }
    .joinToString()
}

internal fun TypeName.wrapInProvider(): ParameterizedTypeName {
  return Provider::class.asClassName().parameterizedBy(this)
}

internal fun TypeName.wrapInLazy(): ParameterizedTypeName {
  return Lazy::class.asClassName().parameterizedBy(this)
}

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.codegen.Parameter.AssistedParameterKey
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.TypeName
import org.jetbrains.kotlin.name.FqName

sealed interface Parameter {
  val name: String
  val originalName: String
  val typeName: TypeName
  val providerTypeName: ParameterizedTypeName
  val lazyTypeName: ParameterizedTypeName
  val isWrappedInProvider: Boolean
  val isWrappedInLazy: Boolean
  val isLazyWrappedInProvider: Boolean
  val isAssisted: Boolean
  val assistedIdentifier: String
  val assistedParameterKey: AssistedParameterKey

  // @Assisted parameters are equal, if the type and the identifier match. This subclass makes
  // diffing the parameters easier.
  data class AssistedParameterKey(
    private val typeName: TypeName,
    private val assistedIdentifier: String
  )

  val originalTypeName: TypeName
    get() = when {
      isLazyWrappedInProvider -> lazyTypeName.wrapInProvider()
      isWrappedInProvider -> providerTypeName
      isWrappedInLazy -> lazyTypeName
      else -> typeName
    }
}

/**
 * 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,
  override val isWrappedInProvider: Boolean,
  override val isWrappedInLazy: Boolean,
  override val isLazyWrappedInProvider: Boolean,
  override val isAssisted: Boolean,
  override val assistedIdentifier: String,
  override val assistedParameterKey: AssistedParameterKey = AssistedParameterKey(
    typeName,
    assistedIdentifier
  )
) : Parameter

internal data class MemberInjectParameter(
  override val name: String,
  override val originalName: String,
  override val typeName: TypeName,
  override val providerTypeName: ParameterizedTypeName,
  override val lazyTypeName: ParameterizedTypeName,
  override val isWrappedInProvider: Boolean,
  override val isWrappedInLazy: Boolean,
  override val isLazyWrappedInProvider: Boolean,
  override val isAssisted: Boolean,
  override val assistedIdentifier: String,
  val memberInjectorClassName: ClassName,
  val isSetterInjected: Boolean,
  val accessName: String,
  val qualifierAnnotationSpecs: List<AnnotationSpec>,
  val injectedFieldSignature: FqName,
  override val assistedParameterKey: AssistedParameterKey = AssistedParameterKey(
    typeName,
    assistedIdentifier
  )
) : Parameter

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

/**
 * Generates code that doesn't impact any other [CodeGenerator], meaning no other code generator
 * will process the generated code produced by this instance. A [PrivateCodeGenerator] is called
 * one last time after [FlushingCodeGenerator.flush] has been called to get a chance to evaluate
 * written results.
 */
internal abstract class PrivateCodeGenerator : CodeGenerator {
  final override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    generateCodePrivate(codeGenDir, module, projectFiles)
    return emptyList()
  }

  protected abstract fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  )
}

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesBinding.Priority.NORMAL
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.assistedInjectFqName
import com.squareup.anvil.compiler.contributesBindingFqName
import com.squareup.anvil.compiler.contributesMultibindingFqName
import com.squareup.anvil.compiler.contributesToFqName
import com.squareup.anvil.compiler.injectFqName
import com.squareup.anvil.compiler.internal.findAnnotation
import com.squareup.anvil.compiler.internal.findAnnotationArgument
import com.squareup.anvil.compiler.internal.fqNameOrNull
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.requireAnnotation
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.mergeInterfacesFqName
import com.squareup.anvil.compiler.mergeModulesFqName
import com.squareup.anvil.compiler.mergeSubcomponentFqName
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.allConstructors

/**
 * Returns the with [injectAnnotationFqName] annotated constructor for this class.
 * [injectAnnotationFqName] must be either `@Inject` or `@AssistedInject`. If the class contains
 * multiple constructors annotated with either of these annotations, then this method throws
 * an error as multiple injected constructors aren't allowed.
 */
internal fun KtClassOrObject.injectConstructor(
  injectAnnotationFqName: FqName,
  module: ModuleDescriptor
): KtConstructor<*>? {
  if (injectAnnotationFqName != injectFqName && injectAnnotationFqName != assistedInjectFqName) {
    throw IllegalArgumentException(
      "injectAnnotationFqName must be either $injectFqName or $assistedInjectFqName. " +
        "It was $injectAnnotationFqName."
    )
  }

  val constructors = allConstructors.filter {
    it.hasAnnotation(injectFqName, module) || it.hasAnnotation(assistedInjectFqName, module)
  }

  return when (constructors.size) {
    0 -> null
    1 -> if (constructors[0].hasAnnotation(injectAnnotationFqName, module)) {
      constructors[0]
    } else {
      null
    }
    else -> throw AnvilCompilationException(
      "Types may only contain one injected constructor.",
      element = this
    )
  }
}

internal fun KtClassOrObject.boundTypeOrNull(
  annotationFqName: FqName,
  module: ModuleDescriptor
): FqName? {
  return requireAnnotation(annotationFqName, module)
    .findAnnotationArgument<KtClassLiteralExpression>(name = "boundType", index = 1)
    ?.requireFqName(module)
}

internal fun KtClassOrObject.replaces(
  annotationFqName: FqName,
  module: ModuleDescriptor
): List<FqName> {
  val index = when (annotationFqName) {
    contributesToFqName -> 1
    contributesBindingFqName, contributesMultibindingFqName -> 2
    else -> throw IllegalArgumentException("$annotationFqName has no replaces field.")
  }

  return findAnnotation(annotationFqName, module)
    ?.findAnnotationArgument<KtCollectionLiteralExpression>(name = "replaces", index = index)
    ?.toFqNames(module)
    ?: emptyList()
}

internal fun KtClassOrObject.ignoreQualifier(
  module: ModuleDescriptor,
  annotationFqName: FqName
): Boolean {
  val index = when (annotationFqName) {
    contributesBindingFqName -> 4
    contributesMultibindingFqName -> 3
    else -> return false
  }

  return requireAnnotation(annotationFqName, module)
    .findAnnotationArgument<KtConstantExpression>(name = "ignoreQualifier", index = index)
    ?.text
    ?.toBooleanStrictOrNull()
    ?: false
}

/**
 * Returns the `List<FqName>` of types excluded by a "Merge___" annotation,
 * or null if there are none.
 *
 * @receiver the annotated interface or abstract class
 * @param annotationFqName the "Merge___" annotation to inspect
 * @see KtAnnotationEntry.findAnnotationArgument
 */
internal fun KtClassOrObject.excludeOrNull(
  module: ModuleDescriptor,
  annotationFqName: FqName
): List<FqName>? {

  val index = when (annotationFqName) {
    mergeInterfacesFqName -> 1
    mergeSubcomponentFqName -> 2
    mergeComponentFqName, mergeModulesFqName -> 3
    else -> throw IllegalArgumentException("$annotationFqName has no exclude field.")
  }

  return requireAnnotation(annotationFqName, module)
    .findAnnotationArgument<KtCollectionLiteralExpression>(name = "exclude", index = index)
    ?.children
    ?.filterIsInstance<KtClassLiteralExpression>()
    ?.mapNotNull { it.fqNameOrNull(module) }
}

/**
 * @return the priority specified in the annotation, or [NORMAL] if there is no argument.
 */
internal fun KtAnnotationEntry.priority(): ContributesBinding.Priority {
  val enumValue = findAnnotationArgument<KtNameReferenceExpression>("priority", 4) ?: return NORMAL
  return ContributesBinding.Priority.valueOf(enumValue.text)
}

internal fun KtCollectionLiteralExpression.toFqNames(
  module: ModuleDescriptor
): List<FqName> = children
  .filterIsInstance<KtClassLiteralExpression>()
  .map { it.requireFqName(module) }

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.api.AnvilContext

internal data class RealAnvilContext(
  override val generateFactories: Boolean,
  override val generateFactoriesOnly: Boolean,
  override val disableComponentMerging: Boolean
) : AnvilContext

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.internal.AnvilModuleDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.findTypeAliasAcrossModuleDependencies
import org.jetbrains.kotlin.descriptors.resolveClassByFqName
import org.jetbrains.kotlin.incremental.components.NoLookupLocation.FROM_BACKEND
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe

class RealAnvilModuleDescriptor(
  delegate: ModuleDescriptor
) : AnvilModuleDescriptor, ModuleDescriptor by delegate {

  internal val allFiles = mutableListOf<KtFile>()

  private val classesMap = mutableMapOf<String, List<KtClassOrObject>>()
  private val allClasses: Sequence<KtClassOrObject>
    get() = classesMap.values.asSequence().flatMap { it }

  fun addFiles(files: Collection<KtFile>) {
    allFiles += files

    files.forEach { ktFile ->
      classesMap[ktFile.identifier] = ktFile.classesAndInnerClasses()
    }
  }

  override fun getClassesAndInnerClasses(ktFile: KtFile): List<KtClassOrObject> {
    return classesMap.getOrPut(ktFile.identifier) {
      ktFile.classesAndInnerClasses()
    }
  }

  override fun resolveClassIdOrNull(classId: ClassId): FqName? {
    val fqName = classId.asSingleFqName()

    resolveClassByFqName(fqName, FROM_BACKEND)
      ?.let { return it.fqNameSafe }

    findTypeAliasAcrossModuleDependencies(classId)
      ?.let { return it.fqNameSafe }

    return allClasses
      .firstOrNull { it.fqName == fqName }
      ?.fqName
  }

  override fun getKtClassOrObjectOrNull(fqName: FqName): KtClassOrObject? {
    return allClasses
      .firstOrNull { it.fqName == fqName }
  }

  private val KtFile.identifier: String
    get() = packageFqName.asString() + name
}

private fun KtFile.classesAndInnerClasses(): List<KtClassOrObject> {
  val children = findChildrenByClass(KtClassOrObject::class.java)

  return generateSequence(children.toList()) { list ->
    list
      .flatMap {
        it.declarations.filterIsInstance<KtClassOrObject>()
      }
      .ifEmpty { null }
  }.flatten().toList()
}

package com.squareup.anvil.sample

import com.squareup.anvil.annotations.MergeComponent
import com.squareup.scopes.AppScope
import com.squareup.scopes.SingleIn

@SingleIn(AppScope::class)
@MergeComponent(AppScope::class)
interface AppComponent {
  fun loggedInComponent(): LoggedInComponent
}

package com.squareup.anvil.sample

import com.squareup.anvil.annotations.MergeSubcomponent
import com.squareup.scopes.LoggedInScope
import com.squareup.scopes.SingleIn

@SingleIn(LoggedInScope::class)
@MergeSubcomponent(LoggedInScope::class)
interface LoggedInComponent

package com.squareup.anvil.sample

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.squareup.anvil.sample.God.HEPHAESTUS
import com.squareup.anvil.sample.R.id
import com.squareup.anvil.sample.R.layout

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(layout.activity_main)

    findViewById<TextView>(id.textView).text = Description.of(HEPHAESTUS)
  }
}

package com.squareup.anvil.sample

import com.squareup.anvil.annotations.ContributesTo
import com.squareup.scopes.AppScope
import com.squareup.scopes.SingleIn
import dagger.Module
import dagger.Provides
import javax.inject.Named
import kotlin.random.Random

@Module
@ContributesTo(AppScope::class)
object RandomModule {

  @SingleIn(AppScope::class)
  @Provides
  fun provideRandomNumber() = Random.nextInt()

  @Provides @Named("random") fun provideString(number: Int): String = "Hello! $number"
}

@ContributesTo(AppScope::class)
interface RandomComponent {
  @Named("random") fun string(): String
}

package com.squareup.anvil.sample

import com.squareup.anvil.sample.Parent.FATHER
import com.squareup.anvil.sample.Parent.MOTHER
import com.squareup.scopes.ComponentHolder
import java.util.Locale

object Description {
  fun of(god: God): String {
    val name = god.name.lowercase(Locale.US)
      .replaceFirstChar(Char::uppercaseChar)

    val child = when (god.parent) {
      FATHER -> "son"
      MOTHER -> "daughter"
    }

    return "$name, $child of " +
      "${ComponentHolder.component<DescriptionComponent>().fatherProvider().father(god)} and " +
      ComponentHolder.component<DescriptionComponent>().motherProvider().mother(god)
  }
}

package com.squareup.anvil.sample

import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.sample.father.FatherProvider
import com.squareup.anvil.sample.mother.MotherProvider
import com.squareup.scopes.AppScope

@ContributesTo(AppScope::class)
interface DescriptionComponent {
  fun fatherProvider(): FatherProvider
  fun motherProvider(): MotherProvider
}

package com.squareup.anvil.compiler.internal.testing

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.annotations.MergeComponent
import com.squareup.anvil.annotations.MergeSubcomponent
import kotlin.reflect.KClass

@ExperimentalAnvilApi
public interface AnyDaggerComponent {
  public val modules: List<KClass<*>>
  public val dependencies: List<KClass<*>>
}

@ExperimentalAnvilApi
public fun Class<*>.anyDaggerComponent(annotationClass: KClass<*>): AnyDaggerComponent {
  return when (annotationClass) {
    MergeComponent::class -> object : AnyDaggerComponent {
      override val modules: List<KClass<*>> = daggerComponent.modules.toList()
      override val dependencies: List<KClass<*>> = daggerComponent.dependencies.toList()
    }
    MergeSubcomponent::class -> object : AnyDaggerComponent {
      override val modules: List<KClass<*>> = daggerSubcomponent.modules.toList()
      override val dependencies: List<KClass<*>> get() = throw IllegalAccessException()
    }
    else -> throw IllegalArgumentException("Cannot handle $annotationClass")
  }
}

@file:Suppress("unused")

package com.squareup.anvil.compiler.internal.testing

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import java.lang.reflect.AccessibleObject
import java.lang.reflect.Member
import java.lang.reflect.Modifier

@ExperimentalAnvilApi
public val Member.isStatic: Boolean get() = Modifier.isStatic(modifiers)
@ExperimentalAnvilApi
public val Member.isAbstract: Boolean get() = Modifier.isAbstract(modifiers)

/**
 * Creates a new instance of this class with the given arguments. This method assumes that this
 * class only declares a single constructor.
 */
@ExperimentalAnvilApi
@Suppress("UNCHECKED_CAST")
public fun <T : Any> Class<T>.createInstance(
  vararg initargs: Any?
): T = declaredConstructors.single().use { it.newInstance(*initargs) } as T

@ExperimentalAnvilApi
public inline fun <T, E : AccessibleObject> E.use(block: (E) -> T): T {
  // Deprecated since Java 9, but many projects still use JDK 8 for compilation.
  @Suppress("DEPRECATION")
  val original = isAccessible

  return try {
    isAccessible = true
    block(this)
  } finally {
    isAccessible = original
  }
}

@file:Suppress("unused")

package com.squareup.anvil.compiler.internal.testing

import com.google.common.truth.Truth.assertThat
import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.AnvilCommandLineProcessor
import com.squareup.anvil.compiler.AnvilComponentRegistrar
import com.squareup.anvil.compiler.internal.capitalize
import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.KotlinCompilation.Result
import com.tschuchort.compiletesting.PluginOption
import com.tschuchort.compiletesting.SourceFile
import dagger.Component
import dagger.Module
import dagger.Subcomponent
import dagger.internal.codegen.ComponentProcessor
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.config.JvmTarget
import java.io.File
import java.io.OutputStream
import java.net.URLClassLoader
import java.nio.file.Files
import kotlin.reflect.KClass

/**
 * A simple API over a [KotlinCompilation] with extra configuration support for Anvil.
 */
@ExperimentalAnvilApi
public class AnvilCompilation internal constructor(
  val kotlinCompilation: KotlinCompilation
) {

  private var isCompiled = false
  private var anvilConfigured = false
  private var swapClassLoader = false

  /** Configures this the Anvil behavior of this compilation. */
  @Suppress("SuspiciousCollectionReassignment")
  @ExperimentalAnvilApi
  public fun configureAnvil(
    enableDaggerAnnotationProcessor: Boolean = false,
    generateDaggerFactories: Boolean = false,
    generateDaggerFactoriesOnly: Boolean = false,
    disableComponentMerging: Boolean = false,
    enableExperimentalAnvilApis: Boolean = true,
  ) = apply {
    checkNotCompiled()
    anvilConfigured = true
    kotlinCompilation.apply {
      compilerPlugins = listOf(AnvilComponentRegistrar())
      if (enableDaggerAnnotationProcessor) {
        annotationProcessors = listOf(ComponentProcessor())
      }

      val anvilCommandLineProcessor = AnvilCommandLineProcessor()
      commandLineProcessors = listOf(anvilCommandLineProcessor)

      pluginOptions = listOf(
        PluginOption(
          pluginId = anvilCommandLineProcessor.pluginId,
          optionName = "src-gen-dir",
          optionValue = File(workingDir, "build/anvil").absolutePath
        ),
        PluginOption(
          pluginId = anvilCommandLineProcessor.pluginId,
          optionName = "generate-dagger-factories",
          optionValue = generateDaggerFactories.toString()
        ),
        PluginOption(
          pluginId = anvilCommandLineProcessor.pluginId,
          optionName = "generate-dagger-factories-only",
          optionValue = generateDaggerFactoriesOnly.toString()
        ),
        PluginOption(
          pluginId = anvilCommandLineProcessor.pluginId,
          optionName = "disable-component-merging",
          optionValue = disableComponentMerging.toString()
        )
      )

      if (enableExperimentalAnvilApis) {
        kotlincArguments += listOf(
          "-Xopt-in=kotlin.RequiresOptIn",
          "-Xopt-in=com.squareup.anvil.annotations.ExperimentalAnvilApi"
        )
      }
    }
  }

  /**
   * Helpful shim to configure both [KotlinCompilation.useIR] and [KotlinCompilation.useOldBackend]
   * accordingly.
   */
  public fun useIR(useIR: Boolean) = apply {
    checkNotCompiled()
    kotlinCompilation.useIR = useIR
    kotlinCompilation.useOldBackend = !useIR

    if (!useIR) {
      // To suppress:
      //  w: Language version is automatically inferred to 1.5 when using the old JVM backend.
      //    Consider specifying -language-version explicitly, or remove -Xuse-old-backend
      //  e: warnings found and -Werror specified
      kotlinCompilation.languageVersion = "1.5"

      // To suppress:
      //  w: -Xuse-old-backend is deprecated and will be removed in a future release
      //  e: warnings found and -Werror specified
      kotlinCompilation.allWarningsAsErrors = false
    }
  }

  /** Adds the given sources to this compilation with their packages and names inferred. */
  public fun addSources(@Language("kotlin") vararg sources: String) = apply {
    checkNotCompiled()
    kotlinCompilation.sources += sources.mapIndexed { index, content ->
      val packageDir = content.lines()
        .firstOrNull { it.trim().startsWith("package ") }
        ?.substringAfter("package ")
        ?.replace('.', '/')
        ?.let { "$it/" }
        ?: ""

      val name = "${kotlinCompilation.workingDir.absolutePath}/sources/src/main/java/" +
        "$packageDir/Source$index.kt"

      Files.createDirectories(File(name).parentFile.toPath())

      SourceFile.kotlin(name, contents = content, trimIndent = true)
    }
  }

  public fun addPreviousCompilationResult(result: Result) = apply {
    checkNotCompiled()
    kotlinCompilation.classpaths += result.outputDirectory
    swapClassLoader = true
  }

  /**
   * Returns an Anvil-generated file with the given [packageName] and [fileName] from its expected
   * path.
   */
  public fun generatedAnvilFile(
    packageName: String,
    fileName: String
  ): File {
    check(isCompiled) {
      "No compilation run yet! Call compile() first."
    }
    return File(
      kotlinCompilation.workingDir,
      "build/anvil/${packageName.replace('.', File.separatorChar)}/$fileName.kt"
    )
      .apply {
        check(exists()) {
          "Generated file not found!"
        }
      }
  }

  private fun checkNotCompiled() {
    check(!isCompiled) {
      "Already compiled! Create a new compilation if you want to compile again."
    }
  }

  /**
   * Compiles the underlying [KotlinCompilation]. Note that if [configureAnvil] has not been called
   * prior to this, it will be configured with default behavior.
   */
  public fun compile(
    @Language("kotlin") vararg sources: String,
    block: Result.() -> Unit = {}
  ): Result {
    checkNotCompiled()
    if (!anvilConfigured) {
      // Configure with default behaviors
      configureAnvil()
    }
    addSources(*sources)
    isCompiled = true
    return kotlinCompilation.compile()
      .apply(block)
      .apply { swapClassLoaderIfNecessary() }
  }

  // This workaround is needed until this issue is resolved:
  // https://github.com/tschuchortdev/kotlin-compile-testing/issues/216
  private fun Result.swapClassLoaderIfNecessary() {
    if (!swapClassLoader) return

    val newClassLoader = URLClassLoader(
      kotlinCompilation.classpaths.plus(outputDirectory).map { it.toURI().toURL() }.toTypedArray(),
      this::class.java.classLoader
    )

    Result::class.java.declaredFields
      .single { it.name == "classLoader" }
      .use { it.set(this, newClassLoader) }
  }

  companion object {
    public operator fun invoke(): AnvilCompilation {
      return AnvilCompilation(
        KotlinCompilation().apply {
          // Sensible default behaviors
          inheritClassPath = true
          jvmTarget = JvmTarget.JVM_1_8.description
          verbose = false
        }
      )
    }
  }
}

/**
 * Helpful for testing code generators in unit tests end to end.
 *
 * This covers common cases, but is built upon reusable logic in [AnvilCompilation] and
 * [configureForAnvil]. Consider using those APIs if more advanced configuration is needed.
 */
@ExperimentalAnvilApi
public fun compileAnvil(
  @Language("kotlin") vararg sources: String,
  enableDaggerAnnotationProcessor: Boolean = false,
  generateDaggerFactories: Boolean = false,
  generateDaggerFactoriesOnly: Boolean = false,
  disableComponentMerging: Boolean = false,
  allWarningsAsErrors: Boolean = true,
  useIR: Boolean = true,
  messageOutputStream: OutputStream = System.out,
  workingDir: File? = null,
  enableExperimentalAnvilApis: Boolean = true,
  previousCompilationResult: Result? = null,
  block: Result.() -> Unit = { }
): Result {
  return AnvilCompilation()
    .apply {
      kotlinCompilation.apply {
        this.allWarningsAsErrors = allWarningsAsErrors
        this.messageOutputStream = messageOutputStream
        if (workingDir != null) {
          this.workingDir = workingDir
        }
      }

      if (previousCompilationResult != null) {
        addPreviousCompilationResult(previousCompilationResult)
      }
    }
    .configureAnvil(
      enableDaggerAnnotationProcessor = enableDaggerAnnotationProcessor,
      generateDaggerFactories = generateDaggerFactories,
      generateDaggerFactoriesOnly = generateDaggerFactoriesOnly,
      disableComponentMerging = disableComponentMerging,
      enableExperimentalAnvilApis = enableExperimentalAnvilApis,
    )
    .useIR(useIR)
    .compile(*sources)
    .also(block)
}

@ExperimentalAnvilApi
public fun Class<*>.moduleFactoryClass(
  providerMethodName: String,
  companion: Boolean = false
): Class<*> {
  val companionString = if (companion) "_Companion" else ""
  return classLoader.loadClass(
    "${generatedClassesString()}${companionString}_${providerMethodName.capitalize()}Factory"
  )
}

@ExperimentalAnvilApi
public fun Class<*>.factoryClass(): Class<*> {
  return classLoader.loadClass("${generatedClassesString()}_Factory")
}

@ExperimentalAnvilApi
public fun Class<*>.implClass(): Class<*> {
  return classLoader.loadClass("${generatedClassesString()}_Impl")
}

@ExperimentalAnvilApi
public fun Class<*>.membersInjector(): Class<*> {
  return classLoader.loadClass("${generatedClassesString()}_MembersInjector")
}

@ExperimentalAnvilApi
public fun Class<*>.generatedClassesString(
  separator: String = "_"
): String {
  return generateSequence(enclosingClass) { it.enclosingClass }
    .toList()
    .reversed()
    .joinToString(separator = "", prefix = packageName(), postfix = simpleName) {
      "${it.simpleName}$separator"
    }
}

@ExperimentalAnvilApi
public fun Class<*>.packageName(): String = `package`.name.let {
  if (it.isBlank()) "" else "$it."
}

@ExperimentalAnvilApi
public val Class<*>.daggerComponent: Component
  get() = annotations.filterIsInstance<Component>()
    .also { assertThat(it).hasSize(1) }
    .first()

@ExperimentalAnvilApi
public val Class<*>.daggerSubcomponent: Subcomponent
  get() = annotations.filterIsInstance<Subcomponent>()
    .also { assertThat(it).hasSize(1) }
    .first()

@ExperimentalAnvilApi
public val Class<*>.daggerModule: Module
  get() = annotations.filterIsInstance<Module>()
    .also { assertThat(it).hasSize(1) }
    .first()

@ExperimentalAnvilApi
public infix fun Class<*>.extends(other: Class<*>): Boolean = other.isAssignableFrom(this)

@ExperimentalAnvilApi
public infix fun KClass<*>.extends(other: KClass<*>): Boolean =
  other.java.isAssignableFrom(this.java)

@ExperimentalAnvilApi
public fun Array<KClass<*>>.withoutAnvilModule(): List<KClass<*>> = toList().withoutAnvilModule()

@ExperimentalAnvilApi
public fun Collection<KClass<*>>.withoutAnvilModule(): List<KClass<*>> =
  filterNot { it.qualifiedName!!.startsWith("anvil.module") }

@ExperimentalAnvilApi
public fun Any.invokeGet(vararg args: Any?): Any {
  val method = this::class.java.declaredMethods.single { it.name == "get" }
  return method.invoke(this, *args)
}

@ExperimentalAnvilApi
public fun Any.getPropertyValue(name: String): Any {
  return this::class.java.fields.first { it.name == name }.use { it.get(this) }
}

@Suppress("UNCHECKED_CAST")
@ExperimentalAnvilApi
public fun <T> Annotation.getValue(): T =
  this::class.java.declaredMethods.single { it.name == "value" }.invoke(this) as T

package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.contributesBindingFqName
import com.squareup.anvil.compiler.contributesSubcomponentFqName
import com.squareup.anvil.compiler.contributesToFqName
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.mergeInterfacesFqName
import com.squareup.anvil.compiler.mergeModulesFqName
import com.squareup.anvil.compiler.mergeSubcomponentFqName
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

@AutoService(CodeGenerator::class)
internal class AnvilAnnotationDetectorCheck : PrivateCodeGenerator() {

  override fun isApplicable(context: AnvilContext) = context.generateFactoriesOnly &&
    !context.disableComponentMerging

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    val clazz = projectFiles
      .classesAndInnerClass(module)
      .firstOrNull {
        it.hasAnnotation(mergeComponentFqName, module) ||
          it.hasAnnotation(mergeSubcomponentFqName, module) ||
          it.hasAnnotation(mergeInterfacesFqName, module) ||
          it.hasAnnotation(mergeModulesFqName, module) ||
          it.hasAnnotation(contributesToFqName, module) ||
          it.hasAnnotation(contributesSubcomponentFqName, module) ||
          it.hasAnnotation(contributesBindingFqName, module)
      }

    if (clazz != null) {
      throw AnvilCompilationException(
        message = "This Gradle module is configured to ONLY generate Dagger factories with " +
          "the `generateDaggerFactoriesOnly` flag. However, this module contains code that " +
          "uses other Anvil annotations. That's not supported.",
        element = clazz
      )
    }
  }
}

package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.mergeInterfacesFqName
import com.squareup.anvil.compiler.mergeModulesFqName
import com.squareup.anvil.compiler.mergeSubcomponentFqName
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

@AutoService(CodeGenerator::class)
internal class AnvilMergeAnnotationDetectorCheck : PrivateCodeGenerator() {

  override fun isApplicable(context: AnvilContext): Boolean = context.disableComponentMerging

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    val clazz = projectFiles
      .classesAndInnerClass(module)
      .firstOrNull {
        it.hasAnnotation(mergeComponentFqName, module) ||
          it.hasAnnotation(mergeSubcomponentFqName, module) ||
          it.hasAnnotation(mergeInterfacesFqName, module) ||
          it.hasAnnotation(mergeModulesFqName, module)
      }

    if (clazz != null) {
      throw AnvilCompilationException(
        message = "This Gradle module is configured to ONLY generate code with " +
          "the `disableComponentMerging` flag. However, this module contains code that " +
          "uses Anvil @Merge* annotations. That's not supported.",
        element = clazz
      )
    }
  }
}

package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.assistedFactoryFqName
import com.squareup.anvil.compiler.assistedFqName
import com.squareup.anvil.compiler.assistedInjectFqName
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.codegen.dagger.AssistedFactoryGenerator.AssistedFactoryFunction.Companion.toAssistedFactoryFunction
import com.squareup.anvil.compiler.codegen.dagger.AssistedFactoryGenerator.AssistedParameterKey.Companion.toKeysList
import com.squareup.anvil.compiler.codegen.injectConstructor
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.asFunctionType
import com.squareup.anvil.compiler.internal.asTypeName
import com.squareup.anvil.compiler.internal.asTypeNameOrNull
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.classDescriptorOrNull
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.findAnnotation
import com.squareup.anvil.compiler.internal.findAnnotationArgument
import com.squareup.anvil.compiler.internal.fqNameOrNull
import com.squareup.anvil.compiler.internal.functions
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.getKtClassOrObjectOrNull
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.isInterface
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.requireTypeName
import com.squareup.anvil.compiler.internal.resolveGenericKotlinType
import com.squareup.anvil.compiler.internal.resolveTypeReference
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.toClassReference
import com.squareup.anvil.compiler.internal.typeVariableNames
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.OVERRIDE
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.jvm.jvmStatic
import dagger.internal.InstanceFactory
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities.PROTECTED
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities.PUBLIC
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality.ABSTRACT
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtStringTemplateEntry
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.isProtected
import org.jetbrains.kotlin.psi.psiUtil.isPublic
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import java.io.File
import javax.inject.Provider
import kotlin.LazyThreadSafetyMode.NONE

@AutoService(CodeGenerator::class)
internal class AssistedFactoryGenerator : PrivateCodeGenerator() {

  override fun isApplicable(context: AnvilContext) = context.generateFactories

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    projectFiles
      .classesAndInnerClass(module)
      .filter { it.hasAnnotation(assistedFactoryFqName, module) }
      .forEach { clazz ->
        generateFactoryClass(codeGenDir, module, clazz)
      }
  }

  private fun generateFactoryClass(
    codeGenDir: File,
    module: ModuleDescriptor,
    clazz: KtClassOrObject
  ): GeneratedFile {

    val packageName = clazz.containingKtFile.packageFqName.safePackageString()

    val delegateFactoryName = "delegateFactory"

    val function = clazz.requireSingleAbstractFunction(module)

    val returnTypeFqName = function.returnTypeLazy.value

    // The return type of the function must have an @AssistedInject constructor.
    val injectedClass = module.getKtClassOrObjectOrNull(returnTypeFqName)
    val constructor = injectedClass
      ?.injectConstructor(assistedInjectFqName, module)
      ?: throw AnvilCompilationException(
        "Invalid return type: $returnTypeFqName. An assisted factory's abstract " +
          "method must return a type with an @AssistedInject-annotated constructor.",
        element = clazz
      )

    val functionParameters = function.parameterKeys.value
    val assistedParameters = constructor.valueParameters.filter {
      it.findAnnotation(assistedFqName, module) != null
    }

    // Check that the parameters of the function match the @Assisted parameters of the constructor.
    if (assistedParameters.size != functionParameters.size) {
      throw AnvilCompilationException(
        "The parameters in the factory method must match the @Assisted parameters in " +
          "$returnTypeFqName.",
        element = clazz
      )
    }

    // Compute for each parameter its key.
    val functionParameterKeys = function.parameterKeys.value
    val assistedParameterKeys = assistedParameters.toKeysList(module, injectedClass)

    // The factory function may not have two or more parameters with the same key.
    val duplicateKeys = functionParameterKeys
      .groupBy { it.key }
      .filter { it.value.size > 1 }
      .values
      .flatten()

    if (duplicateKeys.isNotEmpty()) {
      // Complain about the first duplicate key that occurs, similar to Dagger.
      val key = functionParameterKeys.first { it in duplicateKeys }

      throw AnvilCompilationException(
        message = buildString {
          append("@AssistedFactory method has duplicate @Assisted types: ")
          if (key.identifier.isNotEmpty()) {
            append("@Assisted(\"${key.identifier}\") ")
          }
          append(key.typeName)
        },
        element = clazz
      )
    }

    // Check that for each parameter of the factory function there is a parameter with the same
    // key in the @AssistedInject constructor.
    val notMatchingKeys = (functionParameterKeys + assistedParameterKeys)
      .groupBy { it.key }
      .filter { it.value.size == 1 }
      .values
      .flatten()

    if (notMatchingKeys.isNotEmpty()) {
      throw AnvilCompilationException(
        "The parameters in the factory method must match the @Assisted parameters in " +
          "$returnTypeFqName.",
        element = clazz
      )
    }

    val typeParameters = clazz.typeVariableNames(module)

    fun ClassName.parameterized(): TypeName {
      return if (typeParameters.isEmpty()) this else parameterizedBy(typeParameters)
    }

    val generatedFactoryTypeName = returnTypeFqName.generateClassName(suffix = "_Factory")
      .asClassName()
      .parameterized()

    val baseFactoryTypeName = clazz.asClassName().parameterized()

    val returnTypeName = returnTypeFqName.asClassName(module).parameterized()

    val implClassName = ClassName(packageName, "${clazz.generateClassName()}_Impl")
    val implParameterizedTypeName = implClassName.parameterized()
    val functionName = function.name
    val baseFactoryIsInterface = clazz.isInterface()
    val functionParameterPairs = function.parameterPairs

    val content = FileSpec.buildFile(packageName, implClassName.simpleName) {
      TypeSpec.classBuilder(implClassName)
        .apply {
          typeParameters.forEach { addTypeVariable(it) }

          if (baseFactoryIsInterface) {
            addSuperinterface(baseFactoryTypeName)
          } else {
            superclass(baseFactoryTypeName)
          }

          primaryConstructor(
            FunSpec.constructorBuilder()
              .addParameter(delegateFactoryName, generatedFactoryTypeName)
              .build()
          )

          addProperty(
            PropertySpec.builder(delegateFactoryName, generatedFactoryTypeName)
              .initializer(delegateFactoryName)
              .addModifiers(PRIVATE)
              .build()
          )
        }
        .addFunction(
          FunSpec.builder(functionName)
            .addModifiers(OVERRIDE)
            .returns(returnTypeName)
            .apply {
              functionParameterPairs.value
                .forEach { parameter ->
                  addParameter(parameter.first, parameter.second)
                }

              // We call the @AssistedInject constructor. Therefore, find for each assisted
              // parameter the function parameter where the keys match.
              val argumentList = assistedParameterKeys.joinToString { assistedParameterKey ->
                val functionIndex = functionParameterKeys.indexOfFirst {
                  it.key == assistedParameterKey.key
                }
                check(functionIndex >= 0) {
                  // Sanity check, this should not happen with the noMatchingKeys list check above.
                  "Unexpected assistedIndex."
                }

                functionParameterPairs.value[functionIndex].first
              }

              addStatement("return $delegateFactoryName.get($argumentList)")
            }
            .build()
        )
        .apply {
          TypeSpec.companionObjectBuilder()
            .addFunction(
              FunSpec.builder("create")
                .jvmStatic()
                .addTypeVariables(typeParameters)
                .addParameter(delegateFactoryName, generatedFactoryTypeName)
                .returns(Provider::class.asClassName().parameterizedBy(baseFactoryTypeName))
                .addStatement(
                  "return %T.create(%T($delegateFactoryName))",
                  InstanceFactory::class,
                  implParameterizedTypeName
                )
                .build()
            )
            .build()
            .let {
              addType(it)
            }
        }
        .build()
        .let { addType(it) }
    }

    return createGeneratedFile(codeGenDir, packageName, implClassName.simpleName, content)
  }

  /**
   * Represents a parsed function in an `@AssistedInject.Factory`-annotated interface.
   */
  private data class AssistedFactoryFunction(
    val name: String,
    val returnTypeLazy: Lazy<FqName>,
    val parameterKeys: Lazy<List<AssistedParameterKey>>,
    /**
     * Pair of parameter name to parameter type
     */
    val parameterPairs: Lazy<List<Pair<String, TypeName>>>
  ) {
    companion object {
      fun FunctionDescriptor.toAssistedFactoryFunction(
        module: ModuleDescriptor,
        factoryClass: KtClassOrObject
      ): AssistedFactoryFunction {

        val containingClass = containingDeclaration as ClassDescriptor

        val returnTypeLazy = lazy(NONE) {

          fun fail(): Nothing {
            throw AnvilCompilationException(
              "Invalid return type: ${factoryClass.requireFqName()}. An assisted factory's " +
                "abstract method must return a type with an @AssistedInject-annotated constructor.",
              element = factoryClass
            )
          }

          // This can be a generic type like `T` if the function is declared in a super type.
          // Check that it can resolve before using this type.
          returnType?.classDescriptorOrNull()
            ?.fqNameSafe
            ?: factoryClass.resolveGenericKotlinType(
              module,
              containingClass.toClassReference(),
              returnType ?: fail()
            )?.fqNameOrNull(module)
            ?: fail()
        }
        val parameterPairs = lazy(NONE) {
          valueParameters.map { param ->

            val fromDescriptor = param.type
              // This can be a generic type like `T` if the function is declared in a super type.
              // Check that it can resolve before using this type.
              .takeIf { it.classDescriptorOrNull() != null }
              ?.asTypeName()
              ?: factoryClass.resolveGenericKotlinType(
                module,
                containingClass.toClassReference(),
                param.type
              )?.requireTypeName(module)
              ?: throw AnvilCompilationException(
                "Unable to resolve assisted function's argument type: ${param.type}.",
                element = factoryClass
              )

            param.name.asString() to fromDescriptor
          }
        }
        return AssistedFactoryFunction(
          name = name.asString(),
          returnTypeLazy = returnTypeLazy,
          parameterKeys = lazy(NONE) {
            valueParameters.toKeysList(module, containingClass, factoryClass)
          },
          parameterPairs = parameterPairs
        )
      }

      fun KtNamedFunction.toAssistedFactoryFunction(
        module: ModuleDescriptor,
        assistedFactoryClass: KtClassOrObject
      ): AssistedFactoryFunction {
        val returnTypeLazy = lazy(NONE) {
          typeReference
            ?.let { typeReference ->
              assistedFactoryClass.resolveTypeReference(module, typeReference)
                ?.fqNameOrNull(module)
            }
            ?: typeReference?.fqNameOrNull(module)
            ?: throw AnvilCompilationException(
              "Invalid return type: ${requireFqName()}. An assisted factory's abstract " +
                "method must return a type with an @AssistedInject-annotated constructor.",
              element = this
            )
        }
        val parameterPairs = lazy(NONE) {
          valueParameters.map { param ->

            val typeName = param.typeReference
              ?.let { typeReference ->

                assistedFactoryClass.resolveTypeReference(module, typeReference)
                  ?.requireTypeName(module)
                  ?: typeReference.requireTypeName(module)
              }
              ?: throw AnvilCompilationException(
                "Couldn't get type for parameter.",
                element = param
              )

            param.nameAsSafeName.asString() to typeName
          }
        }

        return AssistedFactoryFunction(
          name = name!!,
          returnTypeLazy = returnTypeLazy,
          parameterKeys = lazy(NONE) { valueParameters.toKeysList(module, assistedFactoryClass) },
          parameterPairs = parameterPairs
        )
      }
    }
  }

  private fun KtClassOrObject.requireSingleAbstractFunction(
    module: ModuleDescriptor
  ): AssistedFactoryFunction {

    fun ClassReference.assistedFunctions(): List<AssistedFactoryFunction> {
      return when (this) {
        is Psi -> {
          val ktFunctions = clazz.takeIf { it.isInterface() }
            ?.functions(false)
            ?.filterNot { it.hasBody() }
            ?: clazz.functions(false)
              .filter { it.hasModifier(KtTokens.ABSTRACT_KEYWORD) }
              .filter { it.isPublic || it.isProtected() }

          ktFunctions
            .map { it.toAssistedFactoryFunction(module, this@requireSingleAbstractFunction) }
        }
        is Descriptor -> {
          clazz.unsubstitutedMemberScope
            .getContributedDescriptors(DescriptorKindFilter.FUNCTIONS)
            .asSequence()
            .filterIsInstance<FunctionDescriptor>()
            .filter { it.modality == ABSTRACT }
            .filter { it.visibility == PUBLIC || it.visibility == PROTECTED }
            .map { it.toAssistedFactoryFunction(module, this@requireSingleAbstractFunction) }
            .toList()
        }
      }
    }

    // `clazz` must be first in the list because of `distinctBy { ... }`, which keeps the first
    // matched element.  If the function's inherited, it can be overridden as well.  Prioritizing
    // the version from the file we're parsing ensures the correct variance of the referenced types.
    val assistedFunctions = toClassReference()
      .allSuperTypeClassReferences(module, includeSelf = true)
      .distinctBy { it.fqName }
      .flatMap { it.assistedFunctions() }
      .distinctBy { it.name }
      .toList()

    // Check for exact number of functions.
    return when (assistedFunctions.size) {
      0 -> throw AnvilCompilationException(
        "The @AssistedFactory-annotated type is missing an abstract, non-default method " +
          "whose return type matches the assisted injection type.",
        element = this
      )
      1 -> assistedFunctions[0]
      else -> {
        val foundFunctions = assistedFunctions
          .sortedBy { it.name }
          .joinToString { func ->
            "${func.name}(${func.parameterPairs.value.map { it.first }})"
          }
        throw AnvilCompilationException(
          "The @AssistedFactory-annotated type should contain a single abstract, non-default " +
            "method but found multiple: [$foundFunctions]",
          element = this
        )
      }
    }
  }

  // Dagger matches parameters of the factory function with the parameters of the @AssistedInject
  // constructor through a key. Initially, they used the order of parameters, but that has changed.
  // The key is a combination of the type and identifier (value parameter) of the
  // @Assisted("...") annotation. For each parameter the key must be unique.
  private data class AssistedParameterKey(
    val typeName: TypeName,
    val identifier: String
  ) {

    // Key value is similar to a hash function.  There used to be a special case for KotlinTypes
    // which were parameterized, but this is now handled by KotlinPoet's TypeName.
    // `MyType<String>` and `MyType<Int>` now generate different hashCodes.
    val key: Int = identifier.hashCode() * 31 + typeName.hashCode()

    companion object {
      fun Collection<ValueParameterDescriptor>.toKeysList(
        module: ModuleDescriptor,
        containingClass: ClassDescriptor,
        factoryClass: KtClassOrObject
      ): List<AssistedParameterKey> =
        map { descriptor ->

          val type = descriptor.type
            .takeIf { it.classDescriptorOrNull() != null }
            ?.asTypeNameOrNull()
            ?: factoryClass.resolveGenericKotlinType(
              module = module,
              declaringClass = containingClass.toClassReference(),
              typeToResolve = descriptor.type
            )?.requireTypeName(module)
            ?: throw AnvilCompilationException(
              containingClass,
              "Couldn't get type for parameter."
            )

          AssistedParameterKey(
            typeName = type,
            identifier = descriptor.annotations.findAnnotation(assistedFqName)
              ?.argumentValue("value")
              ?.value
              ?.toString()
              ?: ""
          )
        }

      fun Collection<KtParameter>.toKeysList(
        module: ModuleDescriptor,
        classForParameter: KtClassOrObject
      ): List<AssistedParameterKey> = map { parameter ->

        val typeName = parameter.typeReference
          ?.let { typeReference ->

            classForParameter.resolveTypeReference(module, typeReference)
              ?: typeReference
          }
          ?.requireTypeName(module)
          ?.let { typeName ->
            // This is a special case when mixing parsing between descriptors and PSI.  Descriptors
            // will always represent lambda types as kotlin.Function* types, so lambda arguments
            // must be converted or their signatures won't match.
            // see https://github.com/square/anvil/issues/400
            (typeName as? LambdaTypeName)?.asFunctionType()
              ?: typeName
          }
          ?: throw AnvilCompilationException(
            "Couldn't get type for parameter.",
            element = parameter
          )

        val identifier = parameter.findAnnotation(assistedFqName, module)
          ?.findAnnotationArgument<KtStringTemplateExpression>("value", 0)
          ?.getChildOfType<KtStringTemplateEntry>()
          ?.text
          ?: ""

        AssistedParameterKey(
          typeName = typeName,
          identifier = identifier
        )
      }
    }
  }
}

package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.assistedInjectFqName
import com.squareup.anvil.compiler.codegen.Parameter
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.codegen.asArgumentList
import com.squareup.anvil.compiler.codegen.injectConstructor
import com.squareup.anvil.compiler.codegen.mapToConstructorParameters
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.typeVariableNames
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.jvm.jvmStatic
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

/**
 * Generates the `_Factory` class for a type with an `@AssistedInject` constructor, e.g. for
 * ```
 * class AssistedService @AssistedInject constructor()
 * ```
 * this generator would create
 * ```
 * class AssistedService_Factory { .. }
 * ```
 */
@AutoService(CodeGenerator::class)
internal class AssistedInjectGenerator : PrivateCodeGenerator() {

  override fun isApplicable(context: AnvilContext) = context.generateFactories

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    projectFiles
      .classesAndInnerClass(module)
      .forEach { clazz ->
        clazz.injectConstructor(assistedInjectFqName, module)
          ?.let {
            generateFactoryClass(codeGenDir, module, clazz, it)
          }
      }
  }

  private fun generateFactoryClass(
    codeGenDir: File,
    module: ModuleDescriptor,
    clazz: KtClassOrObject,
    constructor: KtConstructor<*>
  ): GeneratedFile {
    val packageName = clazz.containingKtFile.packageFqName.safePackageString()
    val className = "${clazz.generateClassName()}_Factory"

    val constructorParameters = constructor.valueParameters
      .mapToConstructorParameters(module)

    val memberInjectParameters = clazz.memberInjectParameters(module)

    val parameters = constructorParameters + memberInjectParameters

    val parametersAssisted = parameters.filter { it.isAssisted }
    val parametersNotAssisted = parameters.filterNot { it.isAssisted }

    checkAssistedParametersAreDistinct(clazz, parametersAssisted)

    val typeParameters = clazz.typeVariableNames(module)

    val factoryClass = ClassName(packageName, className)
    val factoryClassParameterized =
      if (typeParameters.isEmpty()) factoryClass else factoryClass.parameterizedBy(typeParameters)

    val classType = clazz.asClassName().let {
      if (typeParameters.isEmpty()) it else it.parameterizedBy(typeParameters)
    }

    val content = FileSpec.buildFile(packageName, className) {
      TypeSpec.classBuilder(factoryClass)
        .apply {
          typeParameters.forEach { addTypeVariable(it) }

          primaryConstructor(
            FunSpec.constructorBuilder()
              .apply {
                parametersNotAssisted.forEach { parameter ->
                  addParameter(parameter.name, parameter.providerTypeName)
                }
              }
              .build()
          )

          parametersNotAssisted.forEach { parameter ->
            addProperty(
              PropertySpec.builder(parameter.name, parameter.providerTypeName)
                .initializer(parameter.name)
                .addModifiers(PRIVATE)
                .build()
            )
          }
        }
        .addFunction(
          FunSpec.builder("get")
            .returns(classType)
            .apply {
              parametersAssisted.forEach { parameter ->
                addParameter(parameter.name, parameter.originalTypeName)
              }

              val argumentList = constructorParameters.asArgumentList(
                asProvider = true,
                includeModule = false
              )

              if (memberInjectParameters.isEmpty()) {
                addStatement("return newInstance($argumentList)")
              } else {
                val instanceName = "instance"
                addStatement("val $instanceName = newInstance($argumentList)")
                addMemberInjection(memberInjectParameters, instanceName)
                addStatement("return $instanceName")
              }
            }
            .build()
        )
        .apply {
          TypeSpec.companionObjectBuilder()
            .addFunction(
              FunSpec.builder("create")
                .jvmStatic()
                .apply {
                  if (typeParameters.isNotEmpty()) {
                    addTypeVariables(typeParameters)
                  }
                  parametersNotAssisted.forEach { parameter ->
                    addParameter(parameter.name, parameter.providerTypeName)
                  }

                  val argumentList = parametersNotAssisted.asArgumentList(
                    asProvider = false,
                    includeModule = false
                  )

                  addStatement(
                    "return %T($argumentList)",
                    factoryClassParameterized
                  )
                }
                .returns(factoryClassParameterized)
                .build()
            )
            .addFunction(
              FunSpec.builder("newInstance")
                .jvmStatic()
                .apply {
                  if (typeParameters.isNotEmpty()) {
                    addTypeVariables(typeParameters)
                  }
                  constructorParameters.forEach { parameter ->
                    addParameter(
                      name = parameter.name,
                      type = parameter.originalTypeName
                    )
                  }
                  val argumentsWithoutModule = constructorParameters.joinToString { it.name }

                  addStatement("return %T($argumentsWithoutModule)", classType)
                }
                .returns(classType)
                .build()
            )
            .build()
            .let {
              addType(it)
            }
        }
        .build()
        .let { addType(it) }
    }

    return createGeneratedFile(codeGenDir, packageName, className, content)
  }

  private fun checkAssistedParametersAreDistinct(
    clazz: KtClassOrObject,
    parameters: List<Parameter>
  ) {
    // Parameters are identical, if there types and identifier match.
    val duplicateAssistedParameters = parameters
      .groupBy { it.assistedParameterKey }
      .filterValues { parameterList ->
        parameterList.size > 1
      }

    if (duplicateAssistedParameters.isEmpty()) return

    // Pick the first value in the map and report it as error. Dagger only reports the first
    // error as well. The first value is a list of parameters. Since all parameters in this list
    // are identical, we can simply use the first to construct the error message.
    val parameter = duplicateAssistedParameters.values.first().first()
    val assistedIdentifier = parameter.assistedIdentifier

    val errorMessage = buildString {
      append("@AssistedInject constructor has duplicate @Assisted type: @Assisted")
      if (assistedIdentifier.isNotEmpty()) {
        append("(\"")
        append(assistedIdentifier)
        append("\")")
      }
      append(' ')
      append(parameter.typeName)
    }

    throw AnvilCompilationException(errorMessage, element = clazz)
  }
}

package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.daggerComponentFqName
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.hasAnnotation
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

@AutoService(CodeGenerator::class)
internal class ComponentDetectorCheck : PrivateCodeGenerator() {

  override fun isApplicable(context: AnvilContext) = context.generateFactories

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    val component = projectFiles
      .classesAndInnerClass(module)
      .firstOrNull { it.hasAnnotation(daggerComponentFqName, module) }

    if (component != null) {
      throw AnvilCompilationException(
        message = "Anvil cannot generate the code for Dagger components or subcomponents. In " +
          "these cases the Dagger annotation processor is required. Enabling the Dagger " +
          "annotation processor and turning on Anvil to generate Dagger factories is " +
          "redundant. Set 'generateDaggerFactories' to false.",
        element = component
      )
    }
  }
}

package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.codegen.asArgumentList
import com.squareup.anvil.compiler.codegen.injectConstructor
import com.squareup.anvil.compiler.codegen.mapToConstructorParameters
import com.squareup.anvil.compiler.injectFqName
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.typeVariableNames
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.OVERRIDE
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.jvm.jvmStatic
import dagger.internal.Factory
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

@AutoService(CodeGenerator::class)
internal class InjectConstructorFactoryGenerator : PrivateCodeGenerator() {

  override fun isApplicable(context: AnvilContext) = context.generateFactories

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    projectFiles
      .classesAndInnerClass(module)
      .forEach { clazz ->
        clazz.injectConstructor(injectFqName, module)
          ?.let {
            generateFactoryClass(codeGenDir, module, clazz, it)
          }
      }
  }

  private fun generateFactoryClass(
    codeGenDir: File,
    module: ModuleDescriptor,
    clazz: KtClassOrObject,
    constructor: KtConstructor<*>
  ): GeneratedFile {
    val packageName = clazz.containingKtFile.packageFqName.safePackageString()
    val className = "${clazz.generateClassName()}_Factory"

    val constructorParameters = constructor.valueParameters
      .mapToConstructorParameters(module)

    val memberInjectParameters = clazz.memberInjectParameters(module)

    val allParameters = constructorParameters + memberInjectParameters

    val typeParameters = clazz.typeVariableNames(module)

    val factoryClass = ClassName(packageName, className)
    val factoryClassParameterized =
      if (typeParameters.isEmpty()) factoryClass else factoryClass.parameterizedBy(typeParameters)

    val classType = clazz.asClassName().let {
      if (typeParameters.isEmpty()) it else it.parameterizedBy(typeParameters)
    }

    val content = FileSpec.buildFile(packageName, className) {
      val canGenerateAnObject = allParameters.isEmpty() && typeParameters.isEmpty()
      val classBuilder = if (canGenerateAnObject) {
        TypeSpec.objectBuilder(factoryClass)
      } else {
        TypeSpec.classBuilder(factoryClass)
      }
      typeParameters.forEach { classBuilder.addTypeVariable(it) }

      classBuilder
        .addSuperinterface(Factory::class.asClassName().parameterizedBy(classType))
        .apply {
          if (allParameters.isNotEmpty()) {
            primaryConstructor(
              FunSpec.constructorBuilder()
                .apply {
                  allParameters.forEach { parameter ->
                    addParameter(parameter.name, parameter.providerTypeName)
                  }
                }
                .build()
            )

            allParameters.forEach { parameter ->
              addProperty(
                PropertySpec.builder(parameter.name, parameter.providerTypeName)
                  .initializer(parameter.name)
                  .addModifiers(PRIVATE)
                  .build()
              )
            }
          }
        }
        .addFunction(
          FunSpec.builder("get")
            .addModifiers(OVERRIDE)
            .returns(classType)
            .apply {
              val newInstanceArgumentList = constructorParameters.asArgumentList(
                asProvider = true,
                includeModule = false
              )

              if (memberInjectParameters.isEmpty()) {
                addStatement("return newInstance($newInstanceArgumentList)")
              } else {
                val instanceName = "instance"
                addStatement("val $instanceName = newInstance($newInstanceArgumentList)")
                addMemberInjection(memberInjectParameters, instanceName)
                addStatement("return $instanceName")
              }
            }
            .build()
        )
        .apply {
          val builder = if (canGenerateAnObject) this else TypeSpec.companionObjectBuilder()
          builder
            .addFunction(
              FunSpec.builder("create")
                .jvmStatic()
                .apply {
                  if (typeParameters.isNotEmpty()) {
                    addTypeVariables(typeParameters)
                  }
                  if (canGenerateAnObject) {
                    addStatement("return this")
                  } else {
                    allParameters.forEach { parameter ->
                      addParameter(parameter.name, parameter.providerTypeName)
                    }

                    val argumentList = allParameters.asArgumentList(
                      asProvider = false,
                      includeModule = false
                    )

                    addStatement(
                      "return %T($argumentList)",
                      factoryClassParameterized
                    )
                  }
                }
                .returns(factoryClassParameterized)
                .build()
            )
            .addFunction(
              FunSpec.builder("newInstance")
                .jvmStatic()
                .apply {
                  if (typeParameters.isNotEmpty()) {
                    addTypeVariables(typeParameters)
                  }
                  constructorParameters.forEach { parameter ->
                    addParameter(
                      name = parameter.name,
                      type = parameter.originalTypeName
                    )
                  }
                  val argumentsWithoutModule = constructorParameters.joinToString { it.name }

                  addStatement("return %T($argumentsWithoutModule)", classType)
                }
                .returns(classType)
                .build()
            )
            .build()
            .let {
              if (!canGenerateAnObject) {
                addType(it)
              }
            }
        }
        .build()
        .let { addType(it) }
    }

    return createGeneratedFile(codeGenDir, packageName, className, content)
  }
}

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.memberInjectedPropertyDescriptors
import com.squareup.anvil.compiler.daggerDoubleCheckFqNameString
import com.squareup.anvil.compiler.injectFqName
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.findAnnotation
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.isInterface
import com.squareup.anvil.compiler.internal.toClassReference
import com.squareup.kotlinpoet.FunSpec
import dagger.internal.ProviderOfLazy
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.lexer.KtTokens
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

internal fun KtProperty.isSetterInjected(module: ModuleDescriptor): Boolean {
  return setter?.hasAnnotation(injectFqName, module) == true ||
    findAnnotation(injectFqName, module)
    ?.useSiteTarget
    ?.getAnnotationUseSiteTarget() == AnnotationUseSiteTarget.PROPERTY_SETTER
}

// TODO
//  Include methods: https://github.com/square/anvil/issues/339
internal fun KtClassOrObject.injectedMembers(module: ModuleDescriptor) = children
  .asSequence()
  .filterIsInstance<KtClassBody>()
  .flatMap { it.properties }
  .filterNot { it.visibilityModifierTypeOrDefault() == KtTokens.PRIVATE_KEYWORD }
  .filter {
    it.hasAnnotation(injectFqName, module) ||
      it.isSetterInjected(module)
  }
  .toList()

internal fun FunSpec.Builder.addMemberInjection(
  memberInjectParameters: List<MemberInjectParameter>,
  instanceName: String
): FunSpec.Builder = apply {

  memberInjectParameters.forEach { parameter ->

    val functionName = "inject${parameter.accessName.capitalize()}"

    val param = when {
      parameter.isLazyWrappedInProvider ->
        "${ProviderOfLazy::class.qualifiedName}.create(${parameter.name})"
      parameter.isWrappedInProvider -> parameter.name
      parameter.isWrappedInLazy ->
        "$daggerDoubleCheckFqNameString.lazy(${parameter.name})"
      else -> parameter.name + ".get()"
    }

    addStatement("%T.$functionName($instanceName, $param)", parameter.memberInjectorClassName)
  }
}

/**
 * Returns all member-injected parameters for the receiver class *and any superclasses*.
 *
 * We use Psi whenever possible, to support generated code.
 *
 * Order is important. Dagger expects the properties of the most-upstream class to be listed first
 * in a factory's constructor.
 *
 * Given the hierarchy:
 * Impl -> Middle -> Base
 * The order of dependencies in `Impl_Factory`'s constructor should be:
 * Base -> Middle -> Impl
 *
 * @param inheritedOnly If true, only returns the injected properties declared in superclasses.
 *   If false, the injected properties of the receiver class will be included.
 */
internal fun KtClassOrObject.memberInjectParameters(
  module: ModuleDescriptor,
  inheritedOnly: Boolean = false
): List<MemberInjectParameter> {
  return toClassReference()
    .allSuperTypeClassReferences(module, includeSelf = !inheritedOnly)
    .filterNot { it.isInterface() }
    .toList()
    .foldRight(listOf()) { classReference, acc ->

      acc + classReference.declaredMemberInjectParameters(module, acc)
    }
}

/**
 * @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)
  }
}

package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.codegen.MemberInjectParameter
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.codegen.mapToMemberInjectParameters
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.isGenericClass
import com.squareup.anvil.compiler.internal.isInterface
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.OVERRIDE
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.jvm.jvmStatic
import dagger.MembersInjector
import dagger.internal.InjectedFieldSignature
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

@AutoService(CodeGenerator::class)
internal class MembersInjectorGenerator : PrivateCodeGenerator() {

  override fun isApplicable(context: AnvilContext) = context.generateFactories

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    projectFiles
      .classesAndInnerClass(module)
      .filterNot { it.isInterface() }
      .forEach { clazz ->

        // Only generate a MembersInjector if the target class declares its own member-injected
        // properties. If it does, then any properties from superclasses must be added as well.
        val declaredInjectedProperties = clazz.injectedMembers(module)
          .ifEmpty { return@forEach }

        val parameters = clazz.memberInjectParameters(module, inheritedOnly = true)
          .let { inherited ->
            inherited + declaredInjectedProperties.mapToMemberInjectParameters(module, inherited)
          }

        generateMembersInjectorClass(
          codeGenDir = codeGenDir,
          clazz = clazz,
          parameters = parameters
        )
      }
  }

  private fun generateMembersInjectorClass(
    codeGenDir: File,
    clazz: KtClassOrObject,
    parameters: List<MemberInjectParameter>
  ): GeneratedFile {
    val packageName = clazz.containingKtFile.packageFqName.safePackageString()
    val className = "${clazz.generateClassName()}_MembersInjector"
    val classType = clazz.asClassName()
      .let {
        if (clazz.isGenericClass()) {
          it.parameterizedBy(List(size = clazz.typeParameters.size) { STAR })
        } else {
          it
        }
      }

    fun createArgumentList(asProvider: Boolean): String {
      return parameters
        .map { it.name }
        .let { list ->
          if (asProvider) list.map { "$it.get()" } else list
        }
        .joinToString()
    }

    val memberInjectorClass = ClassName(packageName, className)

    val content = FileSpec.buildFile(packageName, className) {
      addType(
        TypeSpec
          .classBuilder(memberInjectorClass)
          .addSuperinterface(MembersInjector::class.asClassName().parameterizedBy(classType))
          .apply {
            primaryConstructor(
              FunSpec.constructorBuilder()
                .apply {
                  parameters.forEach { parameter ->
                    addParameter(parameter.name, parameter.providerTypeName)
                  }
                }
                .build()
            )

            parameters.forEach { parameter ->
              addProperty(
                PropertySpec.builder(parameter.name, parameter.providerTypeName)
                  .initializer(parameter.name)
                  .addModifiers(PRIVATE)
                  .build()
              )
            }
          }
          .addFunction(
            FunSpec.builder("injectMembers")
              .addModifiers(OVERRIDE)
              .addParameter("instance", classType)
              .addMemberInjection(parameters, "instance")
              .build()
          )
          .addType(
            TypeSpec
              .companionObjectBuilder()
              .addFunction(
                FunSpec.builder("create")
                  .jvmStatic()
                  .apply {
                    parameters.forEach { parameter ->
                      addParameter(parameter.name, parameter.providerTypeName)
                    }

                    addStatement(
                      "return %T(${createArgumentList(false)})",
                      memberInjectorClass
                    )
                  }
                  .returns(memberInjectorClass)
                  .build()
              )
              .apply {
                parameters
                  // Don't generate the static single-property "inject___" functions for super-classes
                  .filter { it.memberInjectorClassName == memberInjectorClass }
                  .forEach { parameter ->

                    val name = parameter.name

                    addFunction(
                      FunSpec.builder("inject${parameter.accessName.capitalize()}")
                        .jvmStatic()
                        .apply {
                          // Don't add @InjectedFieldSignature when it's calling a setter method
                          if (!parameter.isSetterInjected) {
                            addAnnotation(
                              AnnotationSpec.builder(InjectedFieldSignature::class)
                                .addMember("%S", parameter.injectedFieldSignature)
                                .build()
                            )
                          }
                        }
                        .addAnnotations(parameter.qualifierAnnotationSpecs)
                        .addParameter("instance", classType)
                        .addParameter(name, parameter.originalTypeName)
                        .addStatement("instance.${parameter.originalName} = $name")
                        .build()
                    )
                  }
              }
              .build()
          )
          .build()
      )
    }

    return createGeneratedFile(codeGenDir, packageName, className, content)
  }
}

package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.codegen.asArgumentList
import com.squareup.anvil.compiler.codegen.mapToConstructorParameters
import com.squareup.anvil.compiler.daggerModuleFqName
import com.squareup.anvil.compiler.daggerProvidesFqName
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.classesAndInnerClass
import com.squareup.anvil.compiler.internal.findAnnotation
import com.squareup.anvil.compiler.internal.functions
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.isInterface
import com.squareup.anvil.compiler.internal.isNullable
import com.squareup.anvil.compiler.internal.properties
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.anvil.compiler.internal.requireTypeName
import com.squareup.anvil.compiler.internal.requireTypeReference
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.withJvmSuppressWildcardsIfNeeded
import com.squareup.anvil.compiler.publishedApiFqName
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.OVERRIDE
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.jvm.jvmStatic
import dagger.internal.Factory
import dagger.internal.Preconditions
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INTERNAL_KEYWORD
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
import java.io.File

@AutoService(CodeGenerator::class)
internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {

  override fun isApplicable(context: AnvilContext) = context.generateFactories

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    projectFiles
      .classesAndInnerClass(module)
      .filter { it.hasAnnotation(daggerModuleFqName, module) }
      .forEach { clazz ->
        clazz
          .functions(includeCompanionObjects = true)
          .asSequence()
          .filter { it.hasAnnotation(daggerProvidesFqName, module) }
          .onEach { function ->
            checkFunctionIsNotAbstract(clazz, function)
          }
          .also { functions ->
            // Check for duplicate function names.
            val duplicateFunctions = functions
              .groupBy { it.requireFqName() }
              .filterValues { it.size > 1 }

            if (duplicateFunctions.isNotEmpty()) {
              throw AnvilCompilationException(
                element = clazz,
                message = "Cannot have more than one binding method with the same name in " +
                  "a single module: ${duplicateFunctions.keys.joinToString()}"
              )
            }
          }
          .forEach { function ->
            generateFactoryClass(codeGenDir, module, clazz, function)
          }

        clazz
          .properties(includeCompanionObjects = true)
          .asSequence()
          .filter { property ->
            // Must be '@get:Provides'.
            property.findAnnotation(daggerProvidesFqName, module)?.useSiteTarget?.text == "get"
          }
          .forEach { property ->
            generateFactoryClass(codeGenDir, module, clazz, property)
          }
      }
  }

  private fun generateFactoryClass(
    codeGenDir: File,
    module: ModuleDescriptor,
    clazz: KtClassOrObject,
    declaration: KtCallableDeclaration
  ): GeneratedFile {
    val isCompanionObject = declaration.parents
      .filterIsInstance<KtObjectDeclaration>()
      .firstOrNull()
      ?.isCompanion()
      ?: false
    val isObject = isCompanionObject || clazz is KtObjectDeclaration

    val isProperty = declaration is KtProperty

    val isMangled = !isProperty &&
      declaration.visibilityModifierTypeOrDefault() == INTERNAL_KEYWORD &&
      !declaration.hasAnnotation(publishedApiFqName, module)

    val packageName = clazz.containingKtFile.packageFqName.safePackageString()
    val className = buildString {
      append(clazz.generateClassName())
      append('_')
      if (isCompanionObject) {
        append("Companion_")
      }
      if (isProperty) {
        append("Get")
      }
      append(declaration.requireFqName().shortName().asString().capitalize())
      if (isMangled) {
        append("\$${module.mangledNameSuffix()}")
      }
      append("Factory")
    }

    val callableName = declaration.nameAsSafeName.asString()

    val parameters = declaration.valueParameters
      .mapToConstructorParameters(module)

    val returnType = declaration.requireTypeReference(module).requireTypeName(module)
      .withJvmSuppressWildcardsIfNeeded(declaration, module)
    val returnTypeIsNullable = declaration.typeReference?.isNullable() ?: false

    val factoryClass = ClassName(packageName, className)
    val moduleClass = clazz.asClassName()

    val byteCodeFunctionName = when {
      isProperty -> "get" + callableName.capitalize()
      isMangled -> "$callableName\$${module.mangledNameSuffix()}"
      else -> callableName
    }

    val content = FileSpec.buildFile(packageName, className) {
      val canGenerateAnObject = isObject && parameters.isEmpty()
      val classBuilder = if (canGenerateAnObject) {
        TypeSpec.objectBuilder(factoryClass)
      } else {
        TypeSpec.classBuilder(factoryClass)
      }

      classBuilder.addSuperinterface(Factory::class.asClassName().parameterizedBy(returnType))
        .apply {
          if (!canGenerateAnObject) {
            primaryConstructor(
              FunSpec.constructorBuilder()
                .apply {
                  if (!isObject) {
                    addParameter("module", moduleClass)
                  }
                  parameters.forEach { parameter ->
                    addParameter(parameter.name, parameter.providerTypeName)
                  }
                }
                .build()
            )

            if (!isObject) {
              addProperty(
                PropertySpec.builder("module", moduleClass)
                  .initializer("module")
                  .addModifiers(PRIVATE)
                  .build()
              )
            }

            parameters.forEach { parameter ->
              addProperty(
                PropertySpec.builder(parameter.name, parameter.providerTypeName)
                  .initializer(parameter.name)
                  .addModifiers(PRIVATE)
                  .build()
              )
            }
          }
        }
        .addFunction(
          FunSpec.builder("get")
            .addModifiers(OVERRIDE)
            .returns(returnType)
            .apply {
              val argumentList = parameters.asArgumentList(
                asProvider = true,
                includeModule = !isObject
              )
              addStatement("return %N($argumentList)", byteCodeFunctionName)
            }
            .build()
        )
        .apply {
          val builder = if (canGenerateAnObject) this else TypeSpec.companionObjectBuilder()
          builder
            .addFunction(
              FunSpec.builder("create")
                .jvmStatic()
                .apply {
                  if (canGenerateAnObject) {
                    addStatement("return this")
                  } else {
                    if (!isObject) {
                      addParameter("module", moduleClass)
                    }
                    parameters.forEach { parameter ->
                      addParameter(parameter.name, parameter.providerTypeName)
                    }

                    val argumentList = parameters.asArgumentList(
                      asProvider = false,
                      includeModule = !isObject
                    )

                    addStatement("return %T($argumentList)", factoryClass)
                  }
                }
                .returns(factoryClass)
                .build()
            )
            .addFunction(
              FunSpec.builder(byteCodeFunctionName)
                .jvmStatic()
                .apply {
                  if (!isObject) {
                    addParameter("module", moduleClass)
                  }

                  parameters.forEach { parameter ->
                    addParameter(
                      name = parameter.name,
                      type = parameter.originalTypeName
                    )
                  }

                  val argumentsWithoutModule = if (isProperty) {
                    ""
                  } else {
                    "(${parameters.joinToString { it.name }})"
                  }

                  when {
                    isObject && returnTypeIsNullable ->
                      addStatement(
                        "return %T.$callableName$argumentsWithoutModule",
                        moduleClass
                      )
                    isObject && !returnTypeIsNullable ->
                      addStatement(
                        "return %T.checkNotNull(%T.$callableName" +
                          "$argumentsWithoutModule, %S)",
                        Preconditions::class,
                        moduleClass,
                        "Cannot return null from a non-@Nullable @Provides method"
                      )
                    !isObject && returnTypeIsNullable ->
                      addStatement(
                        "return module.$callableName$argumentsWithoutModule"
                      )
                    !isObject && !returnTypeIsNullable ->
                      addStatement(
                        "return %T.checkNotNull(module.$callableName" +
                          "$argumentsWithoutModule, %S)",
                        Preconditions::class,
                        "Cannot return null from a non-@Nullable @Provides method"
                      )
                  }
                }
                .returns(returnType)
                .build()
            )
            .build()
            .let {
              if (!canGenerateAnObject) {
                addType(it)
              }
            }
        }
        .build()
        .let { addType(it) }
    }

    return createGeneratedFile(codeGenDir, packageName, className, content)
  }

  private fun checkFunctionIsNotAbstract(
    clazz: KtClassOrObject,
    function: KtFunction
  ) {
    fun fail(): Nothing = throw AnvilCompilationException(
      "@Provides methods cannot be abstract",
      element = function
    )

    // If the function is abstract, then it's an error.
    if (function.hasModifier(ABSTRACT_KEYWORD)) fail()

    // If the class is not an interface and doesn't use the abstract keyword, then there is
    // no issue.
    if (!clazz.isInterface()) return

    // If the parent of the function is a companion object, then the function inside of the
    // interface is not abstract.
    val parentClass = function.parents.filterIsInstance<KtClassOrObject>().firstOrNull() ?: return
    if (parentClass is KtObjectDeclaration && parentClass.isCompanion()) return

    fail()
  }

  private fun ModuleDescriptor.mangledNameSuffix(): String {
    val name = name.asString()
    return if (name.startsWith('<') && name.endsWith('>')) {
      name.substring(1, name.length - 1)
    } else {
      name
    }
  }
}

package com.squareup.anvil.sample.father

import com.squareup.anvil.sample.God

interface FatherProvider {
  fun father(god: God): String
}

package com.squareup.anvil.sample.father

import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.sample.God
import com.squareup.anvil.sample.God.HEPHAESTUS
import com.squareup.anvil.sample.God.HERA
import com.squareup.anvil.sample.God.ZEUS
import com.squareup.scopes.AppScope

@ContributesBinding(AppScope::class)
object RealFatherProvider : FatherProvider {
  override fun father(god: God): String =
    when (god) {
      ZEUS, HERA -> "Cronus"
      HEPHAESTUS -> "Zeus"
    }
}

package com.squareup.anvil.sample.mother

import com.squareup.anvil.sample.God

interface MotherProvider {
  fun mother(god: God): String
}

package com.squareup.anvil.sample.mother

import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.sample.God
import com.squareup.anvil.sample.God.HEPHAESTUS
import com.squareup.anvil.sample.God.HERA
import com.squareup.anvil.sample.God.ZEUS
import com.squareup.scopes.AppScope

@ContributesBinding(AppScope::class)
object RealMotherProvider : MotherProvider {
  override fun mother(god: God): String =
    when (god) {
      HERA, ZEUS -> "Rhea"
      HEPHAESTUS -> "Hera"
    }
}