"is..."-named Module properties break build

A module property with a name starting with "is" that acts as a provider breaks Dagger processing when processing the module with Anvil 2.4.0-M2. This does not happen when using straight Dagger for annotation processing. I suppose there is some problem with different beans-like name processing being applied by the processors. At a minimum, this is super-confusing; I was chasing this problem for hours before thinking of the property name and finding the actual repro path.

For example:

    @Module
    @ContributesTo(MyComponentScope::class)
    class MyModule {
        @get:Provides
        val isValidCache: BooleanArray = booleanArrayOf(false)
    }

The element needs to be injected somewhere to trigger the error:

    > Task :app:compileDebugJavaWithJavac FAILED
    MyComponent.java:86: error: cannot find symbol
    import com.example.MyModule_IsValidCacheFactory;
    ^
    symbol:   class MyModule_IsValidCacheFactory
    location: package com.example
    app/build/generated/source/kapt/debug/com/example/DaggerMyComponent.java:800: error: cannot find symbol
    return (T) new SampleViewModel(MyModule_IsValidCacheFactory.isValidCache(myComponentImpl.myModule));

If I use a different property name like 'validCache', Dagger prefixes 'get' when generating the component code: 'getValidCache()' instead of 'isValidCache()'

diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt
index 8df7e7e..fe1f791 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt
@@ -116,6 +116,8 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {
     val isObject = isCompanionObject || clazz.isObject()

     val isProperty = declaration.isProperty
+    val declarationName = declaration.fqName.shortName().asString()
+    val useGetPrefix = isProperty && !declarationName.startsWith("is")

     val isMangled = !isProperty &&
       declaration.visibility == INTERNAL &&
@@ -128,10 +130,10 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {
       if (isCompanionObject) {
         append("Companion_")
       }
-      if (isProperty) {
+      if (useGetPrefix) {
         append("Get")
       }
-      append(declaration.fqName.shortName().asString().capitalize())
+      append(declarationName.capitalize())
       if (isMangled) {
         append("\\$${module.mangledNameSuffix()}")
       }
@@ -150,7 +152,7 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {
     val moduleClass = clazz.asClassName()

     val byteCodeFunctionName = when {
-      isProperty -> "get" + callableName.capitalize()
+      useGetPrefix -> "get" + callableName.capitalize()
       isMangled -> "$callableName\\$${module.mangledNameSuffix()}"
       else -> callableName
     }
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/ProvidesMethodFactoryGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/ProvidesMethodFactoryGeneratorTest.kt
index 92eb5ad..4c4129a 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/ProvidesMethodFactoryGeneratorTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/ProvidesMethodFactoryGeneratorTest.kt
@@ -2624,6 +2624,7 @@ public final class DaggerComponentInterface implements ComponentInterface {

   @Test
   fun `a factory class is generated for an uppercase factory function`() {
+    @Suppress("TestFunctionName")
     compile(
       """
       package com.squareup.test.a
@@ -3319,6 +3320,78 @@ public final class DaggerModule1_ProvideFunctionFactory implements Factory<Set<S
     }
   }

+  @Test fun `a provides method with 'is' as prefix is supported`() {
+    compile(
+      """
+      package com.squareup.test
+
+      import dagger.Module
+      import dagger.Provides
+      import javax.inject.Singleton
+
+      @Module
+      class DaggerModule1 {
+        @get:Provides
+        val isValidCache: BooleanArray = booleanArrayOf(false)
+      }
+      """
+    ) {
+      val factoryClass = daggerModule1.moduleFactoryClass("isValidCache")
+
+      val constructor = factoryClass.declaredConstructors.single()
+      assertThat(constructor.parameterTypes.toList()).containsExactly(daggerModule1)
+
+      val staticMethods = factoryClass.declaredMethods.filter { it.isStatic }
+      assertThat(staticMethods).hasSize(2)
+
+      val module = daggerModule1.createInstance()
+
+      val factoryInstance = staticMethods.single { it.name == "create" }
+        .invoke(null, module)
+      assertThat(factoryInstance::class.java).isEqualTo(factoryClass)
+
+      val providedFactory = staticMethods.single { it.name == "isValidCache" }
+        .invoke(null, module) as Any
+
+      assertThat((factoryInstance as Factory<*>).get()).isSameInstanceAs(providedFactory)
+    }
+  }
+
+  @Test fun `a provides method with 'is' as prefix is supported for objects`() {
+    compile(
+      """
+      package com.squareup.test
+
+      import dagger.Module
+      import dagger.Provides
+      import javax.inject.Singleton
+
+      @Module
+      object DaggerModule1 {
+        @get:Provides
+        val isValidCache: Boolean = false
+      }
+      """
+    ) {
+      val factoryClass = daggerModule1.moduleFactoryClass("isValidCache")
+
+      val constructor = factoryClass.declaredConstructors.single()
+      assertThat(constructor.parameterTypes.toList()).isEmpty()
+
+      val staticMethods = factoryClass.declaredMethods.filter { it.isStatic }
+
+      val factoryInstance = staticMethods.single { it.name == "create" }
+        .invoke(null)
+      assertThat(factoryInstance::class.java).isEqualTo(factoryClass)
+
+      val providedBoolean = staticMethods.single { it.name == "isValidCache" }
+        .invoke(null) as Boolean
+
+      assertThat(providedBoolean).isFalse()
+      assertThat((factoryInstance as Factory<Boolean>).get()).isFalse()
+    }
+  }
+
   private fun compile(
     @Language("kotlin") vararg sources: String,
     enableDagger: Boolean = useDagger,

"is..."-named Module properties break build
A module property with a name starting with "is" that acts as a provider breaks Dagger processing when processing the module with Anvil 2.4.0-M2. This does not happen when using straight Dagger for annotation processing. I suppose there is some problem with different beans-like name processing being applied by the processors. At a minimum, this is super-confusing; I was chasing this problem for hours before thinking of the property name and finding the actual repro path.

For example:

    @Module
    @ContributesTo(MyComponentScope::class)
    class MyModule {
        @get:Provides
        val isValidCache: BooleanArray = booleanArrayOf(false)
    }

The element needs to be injected somewhere to trigger the error:

    > Task :app:compileDebugJavaWithJavac FAILED
    MyComponent.java:86: error: cannot find symbol
    import com.example.MyModule_IsValidCacheFactory;
    ^
    symbol:   class MyModule_IsValidCacheFactory
    location: package com.example
    app/build/generated/source/kapt/debug/com/example/DaggerMyComponent.java:800: error: cannot find symbol
    return (T) new SampleViewModel(MyModule_IsValidCacheFactory.isValidCache(myComponentImpl.myModule));

If I use a different property name like 'validCache', Dagger prefixes 'get' when generating the component code: 'getValidCache()' instead of 'isValidCache()'

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)
@Repeatable
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)
@Repeatable
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)
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<*>> = [],
  /**
   * This contributed subcomponent will replace these contributed subcomponents. All replaced
   * subcomponents must use the same scope.
   */
  val replaces: 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)
@Repeatable
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)
@Repeatable
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)
@Repeatable
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.benchmark

import com.squareup.anvil.benchmark.Module.AppModule
import com.squareup.anvil.benchmark.Module.LibraryModule
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.ParameterSpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import java.io.File

private const val MODULE_COUNT = 1000

/**
 * Generates 1000 library modules and 1 application module to benchmark Anvil. The first 10
 * generated library modules are special, because they define the interfaces that are bound by
 * other implementation classes in the other library modules. These 10 library modules also
 * contribute one subcomponent each.
 *
 * All library modules contribute a binding and Dagger module. The contributed binding replaces
 * the contributed binding from the module with the index - 10 to avoid duplicate bindings. The
 * Dagger module has one provider method for another interface. We replace all Dagger modules
 * except for the last 10 in the application module to avoid duplicate bindings.
 *
 * The app module contains 10 Dagger components with the same scope and runs the Dagger
 * annotation processor with KAPT. Each of the Dagger components merges the 10 contributed
 * subcomponents, so in total we have 100 subcomponents. For each of them + the 10 components
 * we run Anvil's slowest operations of merging all contributions.
 *
 * All generated modules are included in the main Gradle project for Anvil. This way we get
 * Gradle's dependency substitution feature for free and we always rebuild Anvil from source.
 *
 * To generate the project and run the benchmarks run:
 * ```
 * > ./gradlew :createBenchmarkProject
 * > gradle-profiler --benchmark --measure-config-time --scenario-file benchmark/benchmark.scenarios
 * ```
 *
 * There are two benchmark scenarios. The first only downloads all dependencies, because the second
 * runs in offline mode. In the real benchmark we only exercise the Kotlin compilation task of the
 * app module and clean the output of this task alone between iterations. This gives almost the
 * time Anvil needs (the Kotlin compiler does other things too, but Anvil is certainly the longest
 * running operation during compilation in this module).
 */
open class CreateBenchmarkProjectTask : DefaultTask() {

  private val rootDir: File get() = File(project.rootDir, "benchmark")

  @TaskAction fun createProject() {
    val libraryModules = List(MODULE_COUNT) { index ->
      val name = "lib$index"
      val path = ":${rootDir.name}:$name"

      LibraryModule(
        name = name,
        path = path,
        index = index,
      )
    }.associateBy { it.index }

    val appModule = AppModule(
      name = "app",
      path = ":${rootDir.name}:app"
    )

    createSettingsGradleFile(libraryModules.values + appModule)
    createScenariosFile()
    createAppModule(appModule, libraryModules)

    libraryModules.values.forEach {
      createLibraryModule(it, libraryModules)
    }
  }

  private fun createSettingsGradleFile(modules: Collection<Module>) {
    val content = modules.sortedBy { it.path }.joinToString(separator = "\n") {
      "include '${it.path}'"
    }

    File(rootDir, "settings.gradle").writeTextSafely(content)
  }

  private fun createScenariosFile() {
    val content = """
      # Prefix with aa_ to run this scenario first. The Gradle Profiler uses an alphabetical sort
      # for all scenarios.
      aa_fill_cache {
        tasks = [":benchmark:app:assemble"]
        warm-ups = 1
        iterations = 0
      }

      anvil_benchmark {
        tasks = [":benchmark:app:compileKotlin"]
        cleanup-tasks = [":benchmark:app:cleanCompileKotlin"]

        warm-ups = 6
        iterations = 10

        gradle-args = ["--offline", "--no-build-cache"]
      }
    """.trimIndent()

    File(rootDir, "benchmark.scenarios").writeTextSafely(content)
  }

  private fun createLibraryModule(
    module: LibraryModule,
    allLibraryModules: Map<Int, LibraryModule>
  ) {
    val moduleDir = File(rootDir, module.name)
    createLibraryBuildGradleFile(module, allLibraryModules, moduleDir)
    createLibraryModuleSourceFiles(module, moduleDir)
  }

  private fun createLibraryBuildGradleFile(
    module: LibraryModule,
    allLibraryModules: Map<Int, LibraryModule>,
    moduleDir: File
  ) {
    val libDependency = allLibraryModules[module.index - 10]
      ?.let {
        """
          |dependencies {
          |  api project('${it.path}')
          |}
        """
      }

    val content = """
      |apply plugin: 'org.jetbrains.kotlin.jvm'
      |apply plugin: 'com.squareup.anvil'
      |
      |anvil {
      |  generateDaggerFactories = true
      |}
      |
      |dependencies {
      |  api deps.dagger2.dagger
      |}
      ${libDependency ?: "|"}
    """.trimMargin()

    File(moduleDir, "build.gradle").writeTextSafely(content)
  }

  private fun createLibraryModuleSourceFiles(
    module: LibraryModule,
    moduleDir: File
  ) {
    val index = module.index
    val group = index % 10
    val isFirstTenModule = index / 10 == 0
    val packageBase = "com.squareup.anvil.benchmark"

    val scopeComponent = Unit::class.asClassName()
    val scopeSubcomponent = Any::class.asClassName()
    val boundInterface = ClassName("$packageBase$group", "BoundInterface$group")
    val injectedInterface = ClassName("$packageBase$group", "InjectedInterface$group")

    val injectedInterfaceImpl = ClassName("$packageBase$index", "InjectedInterfaceImpl$index")

    val contributesBinding = ClassName("com.squareup.anvil.annotations", "ContributesBinding")
    val contributesSubcomponent =
      ClassName("com.squareup.anvil.annotations", "ContributesSubcomponent")
    val contributesTo = ClassName("com.squareup.anvil.annotations", "ContributesTo")
    val injectAnnotation = ClassName("javax.inject", "Inject")
    val moduleAnnotation = ClassName("dagger", "Module")
    val providesAnnotation = ClassName("dagger", "Provides")

    val content = FileSpec.builder("$packageBase$index", "Source.kt")
      .apply {
        if (isFirstTenModule) {
          addType(TypeSpec.interfaceBuilder(boundInterface).build())
          addType(
            TypeSpec
              .interfaceBuilder("ContributedInterfaceComponent$index")
              .addAnnotation(
                AnnotationSpec
                  .builder(contributesTo)
                  .addMember("scope = %T::class", scopeComponent)
                  .build()
              )
              .addFunction(
                FunSpec
                  .builder("boundInterface$index")
                  .addModifiers(ABSTRACT)
                  .returns(boundInterface)
                  .build()
              )
              .build()
          )

          addType(TypeSpec.interfaceBuilder(injectedInterface).build())
          addType(
            TypeSpec
              .interfaceBuilder("ContributedSubcomponent$index")
              .addAnnotation(
                AnnotationSpec
                  .builder(contributesSubcomponent)
                  .addMember("scope = %T::class", scopeSubcomponent)
                  .addMember("parentScope = %T::class", scopeComponent)
                  .build()
              )
              .build()
          )
          addType(
            TypeSpec
              .interfaceBuilder("ContributedInterfaceSubcomponent$index")
              .addAnnotation(
                AnnotationSpec
                  .builder(contributesTo)
                  .addMember("scope = %T::class", scopeSubcomponent)
                  .build()
              )
              .addFunction(
                FunSpec
                  .builder("injectedInterface$index")
                  .addModifiers(ABSTRACT)
                  .returns(injectedInterface)
                  .build()
              )
              .build()
          )
        }
      }
      .addType(
        TypeSpec
          .objectBuilder("BoundInterfaceImpl$index")
          .addAnnotation(
            AnnotationSpec
              .builder(contributesBinding)
              .addMember("scope = %T::class", scopeComponent)
              .apply {
                if (!isFirstTenModule) {
                  val count = index / 10
                  val template = (1..count).joinToString(separator = ", ") { "%T::class" }

                  val replacedImpls = (0 until count).map {
                    val replacedIndex = it * 10 + group
                    ClassName("$packageBase$replacedIndex", "BoundInterfaceImpl$replacedIndex")
                  }
                  addMember("replaces = [$template]", *replacedImpls.toTypedArray())
                }
              }
              .build()
          )
          .addSuperinterface(boundInterface)
          .build()
      )
      .addType(
        TypeSpec
          .classBuilder(injectedInterfaceImpl)
          .addSuperinterface(injectedInterface)
          .primaryConstructor(
            FunSpec.constructorBuilder()
              .addAnnotation(injectAnnotation)
              .addParameter(
                ParameterSpec
                  .builder("boundInterface", boundInterface)
                  .addAnnotation(
                    AnnotationSpec
                      .builder(Suppress::class)
                      .addMember("\"UNUSED_PARAMETER\"")
                      .build()
                  )
                  .build()
              )
              .build()
          )
          .build()
      )
      .addType(
        TypeSpec
          .objectBuilder("DaggerModule$index")
          .addAnnotation(moduleAnnotation)
          .addAnnotation(
            AnnotationSpec
              .builder(contributesTo)
              .addMember("scope = %T::class", scopeSubcomponent)
              .build()
          )
          .addFunction(
            FunSpec
              .builder("provideInjectedInterface")
              .addAnnotation(providesAnnotation)
              .addParameter("injectedInterfaceImpl", injectedInterfaceImpl)
              .addStatement("return injectedInterfaceImpl")
              .returns(injectedInterface)
              .build()
          )
          .build()
      )
      .build()
      .let { fileSpec ->
        StringBuilder().also { fileSpec.writeTo(it) }.toString()
      }

    val dir = File(moduleDir, "src/main/kotlin/${packageBase.replace('.', '/')}$index")
    File(dir, "Source.kt").writeTextSafely(content)
  }

  private fun createAppModule(
    module: AppModule,
    allLibraryModules: Map<Int, LibraryModule>
  ) {
    val moduleDir = File(rootDir, module.name)
    createAppBuildGradleFile(allLibraryModules, moduleDir)
    createAppModuleSourceFiles(allLibraryModules, moduleDir)
  }

  private fun createAppBuildGradleFile(
    allLibraryModules: Map<Int, LibraryModule>,
    moduleDir: File
  ) {
    val libDependencies = allLibraryModules.keys.sorted()
      .let { it.subList(it.size - 10, it.size) }
      .map { allLibraryModules.getValue(it) }
      .joinToString(separator = "\n") { "|  api project('${it.path}')" }

    val content = """
      |apply plugin: 'org.jetbrains.kotlin.jvm'
      |apply plugin: 'com.squareup.anvil'
      |apply plugin: 'org.jetbrains.kotlin.kapt'
      |
      |dependencies {
      |  api deps.dagger2.dagger
      |  kapt deps.dagger2.compiler
      $libDependencies
      |}
    """.trimMargin()

    File(moduleDir, "build.gradle").writeTextSafely(content)
  }

  private fun createAppModuleSourceFiles(
    allLibraryModules: Map<Int, LibraryModule>,
    moduleDir: File
  ) {
    val packageName = "com.squareup.anvil.benchmark.app"

    val scopeComponent = Unit::class.asClassName()
    val scopeSubcomponent = Any::class.asClassName()

    val mergeComponent = ClassName("com.squareup.anvil.annotations", "MergeComponent")
    val contributesTo = ClassName("com.squareup.anvil.annotations", "ContributesTo")
    val moduleAnnotation = ClassName("dagger", "Module")

    val content = FileSpec.builder(packageName, "Source.kt")
      .apply {
        (1..10).forEach { componentIndex ->
          addType(
            TypeSpec
              .interfaceBuilder("Component$componentIndex")
              .addAnnotation(
                AnnotationSpec
                  .builder(mergeComponent)
                  .addMember("scope = %T::class", scopeComponent)
                  .build()
              )
              .build()
          )
        }
      }
      .addType(
        TypeSpec
          .objectBuilder("ReplacingDaggerModule")
          .addAnnotation(moduleAnnotation)
          .addAnnotation(
            AnnotationSpec
              .builder(contributesTo)
              .addMember("scope = %T::class", scopeSubcomponent)
              .apply {
                val range = 0 until (allLibraryModules.size - 10)
                val template = range.joinToString(separator = ", ") { "%T::class" }

                val replacedModules = range.map { index ->
                  ClassName("com.squareup.anvil.benchmark$index", "DaggerModule$index")
                }
                addMember("replaces = [$template]", *replacedModules.toTypedArray())
              }
              .build()
          )
          .build()
      )
      .build()
      .let { fileSpec ->
        StringBuilder().also { fileSpec.writeTo(it) }.toString()
      }

    val dir = File(moduleDir, "src/main/kotlin/${packageName.replace('.', '/')}")
    File(dir, "Source.kt").writeTextSafely(content)
  }

  private fun File.writeTextSafely(text: String) {
    check((parentFile.exists() && parentFile.isDirectory) || parentFile.mkdirs())
    check((exists() && isFile) || createNewFile())

    writeText(text)
  }
}

package com.squareup.anvil.benchmark

sealed interface Module {
  val name: String
  val path: String

  data class LibraryModule(
    override val name: String,
    override val path: String,
    val index: Int,
  ) : Module

  data class AppModule(
    override val name: String,
    override val path: String,
  ) : Module
}

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.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.CommandLineOptions.Companion.commandLineOptions
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.reference.RealAnvilModuleDescriptor
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 {

  private val manuallyAddedCodeGenerators = mutableListOf<CodeGenerator>()

  override fun registerProjectComponents(
    project: MockProject,
    configuration: CompilerConfiguration
  ) {
    val sourceGenFolder = File(configuration.getNotNull(srcGenDirKey))
    val scanner = ClassScanner()
    val commandLineOptions = configuration.commandLineOptions

    val codeGenerators = loadCodeGenerators() +
      manuallyAddedCodeGenerators +
      // We special case these due to the ClassScanner requirement.
      BindingModuleGenerator(scanner) +
      ContributesSubcomponentHandlerGenerator(scanner)

    val moduleDescriptorFactory = RealAnvilModuleDescriptor.Factory()

    // 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,
        commandLineOptions = commandLineOptions,
        moduleDescriptorFactory = moduleDescriptorFactory
      )
    )

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

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

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

  // This function is used in tests. It must be called before the test code is being compiled.
  @ExperimentalAnvilApi
  fun addCodeGenerators(codeGenerators: List<CodeGenerator>) {
    manuallyAddedCodeGenerators += codeGenerators
  }
}

package com.squareup.anvil.compiler

import com.squareup.anvil.compiler.GeneratedProperty.ReferenceProperty
import com.squareup.anvil.compiler.GeneratedProperty.ScopeProperty
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.argumentType
import com.squareup.anvil.compiler.internal.classDescriptor
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.toClassReference
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

internal class ClassScanner {

  private val cache = mutableMapOf<CacheKey, Collection<List<GeneratedProperty>>>()

  /**
   * Returns a sequence of contributed classes from the dependency graph. Note that the result
   * includes inner classes already.
   */
  fun findContributedClasses(
    module: ModuleDescriptor,
    annotation: FqName,
    scope: FqName?
  ): Sequence<ClassReference.Descriptor> {
    val propertyGroups = cache.getOrPut(CacheKey(annotation, module.hashCode())) {
      val packageName = when (annotation) {
        contributesToFqName -> HINT_CONTRIBUTES_PACKAGE_PREFIX
        contributesBindingFqName -> HINT_BINDING_PACKAGE_PREFIX
        contributesMultibindingFqName -> HINT_MULTIBINDING_PACKAGE_PREFIX
        contributesSubcomponentFqName -> HINT_SUBCOMPONENTS_PACKAGE_PREFIX
        else -> throw AnvilCompilationException(message = "Cannot find hints for $annotation.")
      }

      generateSequence(listOf(module.getPackage(FqName(packageName)))) { subPackages ->
        subPackages
          .flatMap { it.subPackages() }
          .ifEmpty { null }
      }
        .flatMap { it.asSequence() }
        .flatMap {
          it.memberScope.getContributedDescriptors(DescriptorKindFilter.VALUES)
        }
        .filterIsInstance<PropertyDescriptor>()
        .mapNotNull { GeneratedProperty.fromDescriptor(it) }
        .groupBy { property -> property.baseName }
        .values
    }

    return propertyGroups
      .asSequence()
      .mapNotNull { properties ->
        val reference = properties.filterIsInstance<ReferenceProperty>()
          // In some rare cases we can see a generated property for the same identifier.
          // Filter them just in case, see https://github.com/square/anvil/issues/460 and
          // https://github.com/square/anvil/issues/565
          .distinctBy { it.baseName }
          .singleOrEmpty()
          ?: throw AnvilCompilationException(
            message = "Couldn't find the reference for a generated hint: ${properties[0].baseName}."
          )

        val scopes = properties.filterIsInstance<ScopeProperty>()
          .ifEmpty {
            throw AnvilCompilationException(
              message = "Couldn't find any scope for a generated hint: ${properties[0].baseName}."
            )
          }
          .map { it.descriptor.type.argumentType().classDescriptor().fqNameSafe }

        // Look for the right scope even before resolving the class and resolving all its super
        // types.
        if (scope != null && scope !in scopes) return@mapNotNull null

        reference.descriptor.type.argumentType()
          .classDescriptor()
          .toClassReference(module)
      }
      .filter { clazz ->
        // 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.
        clazz.annotations.any {
          it.fqName == annotation && (scope == null || it.scope().fqName == scope)
        }
      }
  }
}

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

private sealed class GeneratedProperty(
  val descriptor: PropertyDescriptor,
  val baseName: String
) {
  class ReferenceProperty(
    descriptor: PropertyDescriptor,
    baseName: String
  ) : GeneratedProperty(descriptor, baseName)

  class ScopeProperty(
    descriptor: PropertyDescriptor,
    baseName: String
  ) : GeneratedProperty(descriptor, baseName)

  companion object {
    fun fromDescriptor(descriptor: PropertyDescriptor): GeneratedProperty? {
      // For each contributed hint there are several properties, e.g. the reference itself
      // and the scopes. Group them by their common name without the suffix.
      val name = descriptor.name.asString()

      return when {
        name.endsWith(REFERENCE_SUFFIX) ->
          ReferenceProperty(descriptor, name.substringBeforeLast(REFERENCE_SUFFIX))
        name.contains(SCOPE_SUFFIX) -> {
          // The old scope hint didn't have a number. Now that there can be multiple scopes
          // we append a number for all scopes, but we still need to support the old format.
          val indexString = name.substringAfterLast(SCOPE_SUFFIX)
          if (indexString.toIntOrNull() != null || indexString.isEmpty()) {
            ScopeProperty(descriptor, name.substringBeforeLast(SCOPE_SUFFIX))
          } else {
            null
          }
        }
        else -> null
      }
    }
  }
}

private data class CacheKey(
  val fqName: FqName,
  val moduleHash: Int
)

package com.squareup.anvil.compiler

import com.squareup.anvil.compiler.codegen.reference.ClassReferenceIr
import com.squareup.anvil.compiler.codegen.reference.RealAnvilModuleDescriptor
import com.squareup.anvil.compiler.codegen.reference.toClassReference
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.name.FqName

/**
 * 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,
  annotation: FqName,
  scope: ClassReferenceIr?,
  moduleDescriptorFactory: RealAnvilModuleDescriptor.Factory
): Sequence<ClassReferenceIr> {
  val module = moduleDescriptorFactory.create(moduleFragment.descriptor)

  return findContributedClasses(module, annotation, scope?.fqName)
    .map {
      pluginContext.requireReferenceClass(it.fqName).toClassReference(pluginContext)
    }
}

package com.squareup.anvil.compiler

import org.jetbrains.kotlin.config.CompilerConfiguration

class CommandLineOptions private constructor(
  val generateFactories: Boolean,
  val generateFactoriesOnly: Boolean,
  val disableComponentMerging: Boolean,
) {
  companion object {
    val CompilerConfiguration.commandLineOptions: CommandLineOptions
      get() = CommandLineOptions(
        generateFactories = get(generateDaggerFactoriesKey, false),
        generateFactoriesOnly = get(generateDaggerFactoriesOnlyKey, false),
        disableComponentMerging = get(disableComponentMergingKey, false)
      )
  }
}

package com.squareup.anvil.compiler

import com.squareup.anvil.compiler.codegen.atLeastOneAnnotation
import com.squareup.anvil.compiler.codegen.find
import com.squareup.anvil.compiler.codegen.findAll
import com.squareup.anvil.compiler.codegen.generatedAnvilSubcomponent
import com.squareup.anvil.compiler.codegen.parentScope
import com.squareup.anvil.compiler.codegen.reference.RealAnvilModuleDescriptor
import com.squareup.anvil.compiler.internal.classDescriptor
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC
import com.squareup.anvil.compiler.internal.reference.allSuperTypeClassReferences
import com.squareup.anvil.compiler.internal.reference.toClassReference
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.Name
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,
  private val moduleDescriptorFactory: RealAnvilModuleDescriptor.Factory
) : SyntheticResolveExtension {
  override fun addSyntheticSupertypes(
    thisDescriptor: ClassDescriptor,
    supertypes: MutableList<KotlinType>
  ) {
    if (thisDescriptor.shouldIgnore()) return

    val module = moduleDescriptorFactory.create(thisDescriptor.module)
    val mergeAnnotatedClass = thisDescriptor.toClassReference(module)

    val mergeAnnotations = mergeAnnotatedClass.annotations
      .findAll(mergeComponentFqName, mergeSubcomponentFqName, mergeInterfacesFqName)
      .ifEmpty { return }

    if (!mergeAnnotatedClass.isInterface()) {
      throw AnvilCompilationExceptionClassReference(
        classReference = mergeAnnotatedClass,
        message = "Dagger components must be interfaces."
      )
    }

    val scopes = mergeAnnotations.map {
      try {
        it.scope()
      } catch (e: AssertionError) {
        // In some scenarios this error is thrown. Throw a new exception with a better explanation.
        // Caused by: java.lang.AssertionError: Recursion detected in a lazy value under
        // LockBasedStorageManager@420989af (TopDownAnalyzer for JVM)
        throw AnvilCompilationExceptionClassReference(
          classReference = mergeAnnotatedClass,
          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
        )
      }
    }

    val contributesAnnotations = mergeAnnotations
      .flatMap { annotation ->
        classScanner
          .findContributedClasses(
            module = module,
            annotation = contributesToFqName,
            scope = annotation.scope().fqName
          )
      }
      .filter { clazz ->
        clazz.isInterface() && clazz.annotations.find(daggerModuleFqName).singleOrNull() == null
      }
      .flatMap { clazz ->
        clazz.annotations
          .find(annotationName = contributesToFqName)
          .filter { it.scope() in scopes }
      }
      .onEach { contributeAnnotation ->
        val contributedClass = contributeAnnotation.declaringClass()
        if (contributedClass.visibility() != PUBLIC) {
          throw AnvilCompilationExceptionClassReference(
            classReference = contributedClass,
            message = "${contributedClass.fqName} 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 = contributesAnnotations
      .flatMap { contributeAnnotation ->
        val contributedClass = contributeAnnotation.declaringClass()
        contributedClass
          .atLeastOneAnnotation(contributeAnnotation.fqName)
          .flatMap { it.replaces() }
          .onEach { classToReplace ->
            // 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 (!classToReplace.isInterface()) {
              throw AnvilCompilationExceptionClassReference(
                classReference = contributedClass,
                message = "${contributedClass.fqName} wants to replace " +
                  "${classToReplace.fqName}, but the class being " +
                  "replaced is not an interface."
              )
            }

            val contributesToOurScope = classToReplace.annotations
              .findAll(
                contributesToFqName, contributesBindingFqName, contributesMultibindingFqName
              )
              .map { it.scope() }
              .any { scope -> scope in scopes }

            if (!contributesToOurScope) {
              throw AnvilCompilationExceptionClassReference(
                classReference = contributedClass,
                message = "${contributedClass.fqName} with scopes " +
                  "${scopes.joinToString(prefix = "[", postfix = "]") { it.fqName.asString() }} " +
                  "wants to replace ${classToReplace.fqName}, but the replaced class isn't " +
                  "contributed to the same scope."
              )
            }
          }
      }
      .toSet()

    val excludedClasses = mergeAnnotations
      .flatMap { it.exclude() }
      .filter { it.isInterface() }
      .onEach { excludedClass ->
        // Verify that the replaced classes use the same scope.
        val contributesToOurScope = excludedClass.annotations
          .findAll(contributesToFqName, contributesBindingFqName, contributesMultibindingFqName)
          .map { it.scope() }
          .plus(
            excludedClass.annotations.find(contributesSubcomponentFqName).map { it.parentScope() }
          )
          .any { scope -> scope in scopes }

        if (!contributesToOurScope) {
          throw AnvilCompilationExceptionClassReference(
            message = "${mergeAnnotatedClass.fqName} with scopes " +
              "${scopes.joinToString(prefix = "[", postfix = "]") { it.fqName.asString() }} " +
              "wants to exclude ${excludedClass.fqName}, but the excluded class isn't " +
              "contributed to the same scope.",
            classReference = mergeAnnotatedClass
          )
        }
      }

    if (excludedClasses.isNotEmpty()) {
      val intersect = supertypes
        .map { it.classDescriptor().toClassReference(module) }
        .flatMap { it.allSuperTypeClassReferences(includeSelf = true) }
        .intersect(excludedClasses.toSet())

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

    @Suppress("ConvertCallChainIntoSequence")
    supertypes += contributesAnnotations
      .map { it.declaringClass() }
      .filter { clazz ->
        clazz !in replacedClasses && clazz !in excludedClasses
      }
      .plus(findContributedSubcomponentParentInterfaces(mergeAnnotatedClass, scopes, module))
      // Avoids an error for repeated interfaces.
      .distinct()
      .map { it.clazz.defaultType }
  }

  private fun findContributedSubcomponentParentInterfaces(
    clazz: ClassReference,
    scopes: Collection<ClassReference>,
    module: ModuleDescriptor
  ): Sequence<ClassReference.Descriptor> {
    return classScanner
      .findContributedClasses(
        module = module,
        annotation = contributesSubcomponentFqName,
        scope = null
      )
      .filter {
        it.atLeastOneAnnotation(contributesSubcomponentFqName).single().parentScope() in scopes
      }
      .mapNotNull { contributedSubcomponent ->
        contributedSubcomponent.classId
          .generatedAnvilSubcomponent(clazz.classId)
          .createNestedClassId(Name.identifier(PARENT_COMPONENT))
          .classReferenceOrNull(module)
      }
  }
}

package com.squareup.anvil.compiler

import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.codegen.reference.ClassReferenceIr
import com.squareup.anvil.compiler.codegen.reference.toClassReference
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.expressions.IrExpression
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.classifierOrNull
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.isAnonymousObject
import org.jetbrains.kotlin.name.ClassId
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 ClassId.irClass(context: IrPluginContext): IrClassSymbol =
  context.requireReferenceClass(asSingleFqName())

internal fun ClassId.referenceClassOrNull(context: IrPluginContext): ClassReferenceIr? =
  context.referenceClass(asSingleFqName())?.toClassReference(context)

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 val IrExpression.kclassUnwrapped: IrClassifierSymbol
  get() = (type as? IrSimpleType)?.arguments?.get(0)?.typeOrNull?.classifierOrNull
    ?: throw AnvilCompilationException(
      message = "Couldn't resolve wrapped class.",
      element = this
    )

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

// If we're evaluating an anonymous inner class, it cannot merge anything and will cause
// a failure if we try to resolve its [ClassId]
internal fun IrClass.shouldIgnore(): Boolean {
  return classId == null || isAnonymousObject
}

package com.squareup.anvil.compiler

import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.compiler.codegen.atLeastOneAnnotation
import com.squareup.anvil.compiler.codegen.checkClassIsPublic
import com.squareup.anvil.compiler.codegen.find
import com.squareup.anvil.compiler.codegen.findAll
import com.squareup.anvil.compiler.codegen.generatedAnvilSubcomponent
import com.squareup.anvil.compiler.codegen.parentScope
import com.squareup.anvil.compiler.codegen.reference.RealAnvilModuleDescriptor
import com.squareup.anvil.compiler.internal.argumentType
import com.squareup.anvil.compiler.internal.reference.AnnotationReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.argumentAt
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.reference.toClassReference
import com.squareup.anvil.compiler.internal.safePackageString
import dagger.Component
import dagger.Module
import dagger.Subcomponent
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.codegen.asmType
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.descriptorUtil.module
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,
  private val moduleDescriptorFactory: RealAnvilModuleDescriptor.Factory
) : ExpressionCodegenExtension {

  override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
    if (codegen.descriptor.shouldIgnore()) return

    val module = moduleDescriptorFactory.create(codegen.descriptor.module)
    val clazz = codegen.descriptor.toClassReference(module)

    val annotations = clazz.annotations
      .findAll(mergeComponentFqName, mergeSubcomponentFqName, mergeModulesFqName)
      .ifEmpty { return }

    val daggerAnnotationClass = annotations[0].daggerAnnotationClass
    val daggerModulesKeyword = annotations[0].modulesKeyword
    val scopes = annotations.map { it.scope() }

    val predefinedModules = annotations.flatMap {
      it.argumentAt(it.modulesKeyword, index = 1)?.value<List<ClassReference>>().orEmpty()
    }
    val anvilModuleName = createAnvilModuleName(clazz)

    val contributesAnnotations = scopes
      .flatMap { scope ->
        classScanner
          .findContributedClasses(
            module = module,
            annotation = contributesToFqName,
            scope = scope.fqName
          )
      }
      .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.
        !it.fqName.isAnvilModule() || it.fqName == anvilModuleName
      }
      .flatMap { contributedClass ->
        contributedClass.annotations
          .find(contributesToFqName)
          .filter { it.scope() in scopes }
      }
      .filter { contributesAnnotation ->
        val contributedClass = contributesAnnotation.declaringClass()
        val moduleAnnotation = contributedClass.annotations.find(daggerModuleFqName).singleOrNull()
        val mergeModulesAnnotation =
          contributedClass.annotations.find(mergeModulesFqName).singleOrNull()

        if (!contributedClass.isInterface() &&
          moduleAnnotation == null &&
          mergeModulesAnnotation == null
        ) {
          throw AnvilCompilationExceptionClassReference(
            contributedClass,
            "${contributedClass.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}?"
          )
        }

        contributedClass.checkClassIsPublic {
          "${contributedClass.fqName} is contributed to the Dagger graph, but the " +
            "module is not public. Only public modules are supported."
        }

        moduleAnnotation != null || mergeModulesAnnotation != null
      }
      // 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 = annotations.flatMap { it.exclude() }
      .onEach { excludedClass ->
        // Verify that the excluded classes use the same scope.
        val contributesToOurScope = excludedClass.annotations
          .findAll(contributesToFqName, contributesBindingFqName, contributesMultibindingFqName)
          .map { it.scope() }
          .plus(
            excludedClass.annotations
              .find(contributesSubcomponentFqName)
              .map { it.parentScope() }
          )
          .any { scope -> scope in scopes }

        if (!contributesToOurScope) {
          throw AnvilCompilationExceptionClassReference(
            message = "${clazz.fqName} with scopes " +
              "${scopes.joinToString(prefix = "[", postfix = "]") { it.fqName.asString() }} " +
              "wants to exclude ${excludedClass.fqName}, but the excluded class isn't " +
              "contributed to the same scope.",
            classReference = clazz
          )
        }
      }

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

            checkSameScope(contributedClass, classToReplace, scopes)
          }
      }

    fun replacedModulesByContributedBinding(
      annotationFqName: FqName
    ): Sequence<ClassReference> {
      return scopes.asSequence()
        .flatMap { scope ->
          classScanner
            .findContributedClasses(
              module = module,
              annotation = annotationFqName,
              scope = scope.fqName
            )
        }
        .flatMap { contributedClass ->
          contributedClass.annotations
            .find(annotationName = annotationFqName)
            .filter { it.scope() in scopes }
            .flatMap { it.replaces() }
            .onEach { classToReplace ->
              checkSameScope(contributedClass, classToReplace, scopes)
            }
        }
    }

    val replacedModulesByContributedBindings = replacedModulesByContributedBinding(
      annotationFqName = contributesBindingFqName
    )

    val replacedModulesByContributedMultibindings = replacedModulesByContributedBinding(
      annotationFqName = contributesMultibindingFqName
    )

    val intersect = predefinedModules.intersect(excludedModules.toSet())
    if (intersect.isNotEmpty()) {
      throw AnvilCompilationExceptionClassReference(
        clazz,
        "${clazz.clazz.name} includes and excludes modules " +
          "at the same time: ${intersect.joinToString { it.classId.relativeClassName.toString() }}"
      )
    }

    val contributedSubcomponentModules =
      findContributedSubcomponentModules(clazz, scopes, module)

    val contributedModuleTypes = contributesAnnotations
      .asSequence()
      .map { it.declaringClass() }
      .minus(replacedModules.toSet())
      .minus(replacedModulesByContributedBindings.toSet())
      .minus(replacedModulesByContributedMultibindings.toSet())
      .minus(excludedModules.toSet())
      .plus(contributedSubcomponentModules)
      .toSet()
      .types(codegen)

    codegen.v
      .newAnnotation("L${daggerAnnotationClass.java.canonicalName.replace('.', '/')};", true)
      .use {
        visitArray(daggerModulesKeyword)
          .use {
            predefinedModules.types(codegen).forEach { visit(daggerModulesKeyword, it) }
            contributedModuleTypes.forEach { visit(daggerModulesKeyword, it) }
          }

        if (annotations[0].fqName == mergeComponentFqName) {
          copyArrayValue(codegen, module, annotations, "dependencies")
        }

        if (annotations[0].fqName == mergeModulesFqName) {
          copyArrayValue(codegen, module, annotations, "subcomponents")
        }
      }
  }

  @Suppress("UNCHECKED_CAST")
  private fun Collection<ClassReference>.types(codegen: ImplementationBodyCodegen): List<Type> {
    return (this as Collection<ClassReference.Descriptor>)
      .map { codegen.typeMapper.mapType(it.clazz) }
  }

  private fun AnnotationVisitor.copyArrayValue(
    codegen: ImplementationBodyCodegen,
    module: ModuleDescriptor,
    annotations: List<AnnotationReference.Descriptor>,
    name: String
  ) {
    val predefinedValues = annotations
      .flatMap { annotation ->
        (annotation.arguments.singleOrNull { it.name == name }?.argument as? ArrayValue)
          ?.value
          .orEmpty()
      }
      .map { it.toType(codegen, module) }
      .ifEmpty { null }

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

  private fun ConstantValue<*>.toType(
    codegen: ImplementationBodyCodegen,
    module: ModuleDescriptor
  ): Type = argumentType(module).asmType(codegen.typeMapper)

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

  private fun createAnvilModuleName(clazz: ClassReference): FqName {
    val name = "$MODULE_PACKAGE_PREFIX." +
      clazz.packageFqName.safePackageString() +
      clazz.generateClassName(
        separator = "",
        suffix = ANVIL_MODULE_SUFFIX
      ).relativeClassName.toString()
    return FqName(name)
  }

  private fun checkSameScope(
    contributedClass: ClassReference,
    classToReplace: ClassReference,
    scopes: List<ClassReference>
  ) {
    val contributesToOurScope = classToReplace.annotations
      .findAll(contributesToFqName, contributesBindingFqName, contributesMultibindingFqName)
      .map { it.scope() }
      .any { scope -> scope in scopes }

    if (!contributesToOurScope) {
      throw AnvilCompilationExceptionClassReference(
        classReference = contributedClass,
        message = "${contributedClass.fqName} with scopes " +
          "${scopes.joinToString(prefix = "[", postfix = "]") { it.fqName.asString() }} " +
          "wants to replace ${classToReplace.fqName}, but the replaced class isn't " +
          "contributed to the same scope."
      )
    }
  }

  private fun findContributedSubcomponentModules(
    clazz: ClassReference.Descriptor,
    scopes: List<ClassReference>,
    module: ModuleDescriptor
  ): Sequence<ClassReference> {
    return classScanner
      .findContributedClasses(
        module = module,
        annotation = contributesSubcomponentFqName,
        scope = null
      )
      .filter { contributedClass ->
        contributedClass
          .atLeastOneAnnotation(contributesSubcomponentFqName)
          .any { it.parentScope() in scopes }
      }
      .mapNotNull { contributedSubcomponent ->
        contributedSubcomponent.classId
          .generatedAnvilSubcomponent(clazz.classId)
          .createNestedClassId(Name.identifier(SUBCOMPONENT_MODULE))
          .classReferenceOrNull(module)
      }
  }
}

private val AnnotationReference.daggerAnnotationClass: KClass<*>
  get() = when (fqName) {
    mergeComponentFqName -> Component::class
    mergeSubcomponentFqName -> Subcomponent::class
    mergeModulesFqName -> Module::class
    else -> throw NotImplementedError("Don't know how to handle $this.")
  }

private val AnnotationReference.modulesKeyword: String
  get() = when (fqName) {
    mergeComponentFqName, mergeSubcomponentFqName -> "modules"
    mergeModulesFqName -> "includes"
    else -> throw NotImplementedError("Don't know how to handle $this.")
  }

package com.squareup.anvil.compiler

import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.compiler.codegen.generatedAnvilSubcomponent
import com.squareup.anvil.compiler.codegen.reference.AnnotationReferenceIr
import com.squareup.anvil.compiler.codegen.reference.AnvilCompilationExceptionClassReferenceIr
import com.squareup.anvil.compiler.codegen.reference.ClassReferenceIr
import com.squareup.anvil.compiler.codegen.reference.RealAnvilModuleDescriptor
import com.squareup.anvil.compiler.codegen.reference.find
import com.squareup.anvil.compiler.codegen.reference.findAll
import com.squareup.anvil.compiler.codegen.reference.toClassReference
import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC
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.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.IrVararg
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.types.defaultType
import org.jetbrains.kotlin.ir.types.impl.IrDynamicTypeImpl
import org.jetbrains.kotlin.ir.util.defaultType
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(
  private val classScanner: ClassScanner,
  private val moduleDescriptorFactory: RealAnvilModuleDescriptor.Factory
) : IrGenerationExtension {
  override fun generate(
    moduleFragment: IrModuleFragment,
    pluginContext: IrPluginContext
  ) {
    moduleFragment.transform(
      object : IrElementTransformerVoid() {
        override fun visitClass(declaration: IrClass): IrStatement {
          if (declaration.shouldIgnore()) return super.visitClass(declaration)

          val declarationReference = declaration.symbol.toClassReference(pluginContext)

          val annotations = declarationReference.annotations
            .findAll(mergeComponentFqName, mergeSubcomponentFqName, mergeModulesFqName)
            .ifEmpty { return super.visitClass(declaration) }

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

  private fun generateDaggerAnnotation(
    annotations: List<AnnotationReferenceIr>,
    moduleFragment: IrModuleFragment,
    pluginContext: IrPluginContext,
    declaration: ClassReferenceIr
  ) {
    val daggerAnnotationFqName = annotations[0].daggerAnnotationFqName

    val scopes = annotations.map { it.scope }
    val predefinedModules = annotations.flatMap {
      it.argumentOrNull(it.modulesKeyword)?.value<List<ClassReferenceIr>>().orEmpty()
    }

    val anvilModuleName = createAnvilModuleName(declaration)

    val contributesAnnotations = annotations
      .flatMap { annotation ->
        classScanner
          .findContributedClasses(
            pluginContext = pluginContext,
            moduleFragment = moduleFragment,
            annotation = contributesToFqName,
            scope = annotation.scope,
            moduleDescriptorFactory = moduleDescriptorFactory
          )
      }
      .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.
        !it.fqName.isAnvilModule() || it.fqName == anvilModuleName
      }
      .flatMap { contributedClass ->
        contributedClass.annotations
          .find(annotationName = contributesToFqName)
          .filter { it.scope in scopes }
      }
      .filter { contributesAnnotation ->
        val contributedClass = contributesAnnotation.declaringClass
        val moduleAnnotation = contributedClass.annotations.find(daggerModuleFqName).singleOrNull()
        val mergeModulesAnnotation =
          contributedClass.annotations.find(mergeModulesFqName).singleOrNull()

        if (!contributedClass.isInterface &&
          moduleAnnotation == null &&
          mergeModulesAnnotation == null
        ) {
          throw AnvilCompilationExceptionClassReferenceIr(
            message = "${contributedClass.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}?",
            classReference = contributedClass
          )
        }

        moduleAnnotation != null || mergeModulesAnnotation != null
      }
      .onEach { contributesAnnotation ->
        val contributedClass = contributesAnnotation.declaringClass
        if (contributedClass.visibility != PUBLIC) {
          throw AnvilCompilationExceptionClassReferenceIr(
            message = "${contributedClass.fqName} is contributed to the Dagger graph, but the " +
              "module is not public. Only public modules are supported.",
            classReference = contributedClass
          )
        }
      }
      // 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 = annotations
      .flatMap { it.excludedClasses }
      .onEach { excludedClass ->

        // Verify that the replaced classes use the same scope.
        val contributesToOurScope = excludedClass.annotations
          .findAll(contributesToFqName, contributesBindingFqName, contributesMultibindingFqName)
          .map { it.scope }
          .plus(
            excludedClass.annotations
              .find(contributesSubcomponentFqName)
              .map { it.parentScope }
          )
          .any { scope -> scope in scopes }

        if (!contributesToOurScope) {
          throw AnvilCompilationExceptionClassReferenceIr(
            message = "${declaration.fqName} with scopes " +
              "${scopes.joinToString(prefix = "[", postfix = "]") { it.fqName.asString() }} " +
              "wants to exclude ${excludedClass.fqName}, but the excluded class isn't " +
              "contributed to the same scope.",
            classReference = declaration
          )
        }
      }

    val replacedModules = contributesAnnotations
      // Ignore replaced modules or bindings specified by excluded modules.
      .filter { contributesAnnotation ->
        contributesAnnotation.declaringClass !in excludedModules
      }
      .flatMap { contributesAnnotation ->
        val contributedClass = contributesAnnotation.declaringClass
        contributesAnnotation.replacedClasses
          .onEach { classToReplace ->
            // Verify has @Module annotation. It doesn't make sense for a Dagger module to
            // replace a non-Dagger module.
            if (!classToReplace.isAnnotatedWith(daggerModuleFqName) &&
              !classToReplace.isAnnotatedWith(contributesBindingFqName) &&
              !classToReplace.isAnnotatedWith(contributesMultibindingFqName)
            ) {
              throw AnvilCompilationExceptionClassReferenceIr(
                message = "${contributedClass.fqName} wants to replace " +
                  "${classToReplace.fqName}, but the class being " +
                  "replaced is not a Dagger module.",
                classReference = contributedClass
              )
            }

            checkSameScope(contributedClass, classToReplace, scopes)
          }
      }

    fun replacedModulesByContributedBinding(
      annotationFqName: FqName
    ): Sequence<ClassReferenceIr> {
      return scopes.asSequence()
        .flatMap { scope ->
          classScanner
            .findContributedClasses(
              pluginContext = pluginContext,
              moduleFragment = moduleFragment,
              annotation = annotationFqName,
              scope = scope,
              moduleDescriptorFactory = moduleDescriptorFactory
            )
        }
        .flatMap { contributedClass ->
          contributedClass.annotations
            .find(annotationName = annotationFqName)
            .filter { it.scope in scopes }
            .flatMap { it.replacedClasses }
            .onEach { classToReplace ->
              checkSameScope(contributedClass, classToReplace, scopes)
            }
        }
    }

    val replacedModulesByContributedBindings = replacedModulesByContributedBinding(
      annotationFqName = contributesBindingFqName
    )

    val replacedModulesByContributedMultibindings = replacedModulesByContributedBinding(
      annotationFqName = contributesMultibindingFqName
    )

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

    val contributedSubcomponentModules =
      findContributedSubcomponentModules(
        declaration,
        scopes,
        pluginContext,
        moduleFragment
      )

    val contributedModules = contributesAnnotations
      .asSequence()
      .map { it.declaringClass }
      .minus(replacedModules.toSet())
      .minus(replacedModulesByContributedBindings.toSet())
      .minus(replacedModulesByContributedMultibindings.toSet())
      .minus(excludedModules.toSet())
      .plus(predefinedModules)
      .plus(contributedSubcomponentModules)
      .distinct()
      .map { it.clazz.owner }

    val annotationConstructorCall = IrConstructorCallImpl
      .fromSymbolOwner(
        startOffset = UNDEFINED_OFFSET,
        endOffset = UNDEFINED_OFFSET,
        type = pluginContext.requireReferenceClass(daggerAnnotationFqName).defaultType,
        constructorSymbol = pluginContext
          .referenceConstructors(daggerAnnotationFqName)
          .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) {
          val varargArguments = annotations
            .mapNotNull { it.argumentOrNull(name)?.argumentExpression as? IrVararg }
            .ifEmpty { return }

          putValueArgument(
            index = 1,
            valueArgument = IrVarargImpl(
              startOffset = 0,
              endOffset = 0,
              type = varargArguments[0].type,
              varargElementType = varargArguments[0].varargElementType,
              elements = varargArguments.flatMap { it.elements }
            )
          )
        }

        if (annotations[0].fqName == mergeComponentFqName) {
          copyArrayValue("dependencies")
        }

        if (annotations[0].fqName == mergeModulesFqName) {
          copyArrayValue("subcomponents")
        }
      }

    // Since we are modifying the state of the code here, this does not need to be reflected in
    // the associated [ClassReferenceIr] which is more of an initial snapshot.
    declaration.clazz.owner.annotations += annotationConstructorCall
  }

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

    val name = "$MODULE_PACKAGE_PREFIX.$packageName" +
      declaration.enclosingClassesWithSelf
        .joinToString(separator = "", postfix = ANVIL_MODULE_SUFFIX) {
          it.shortName
        }
    return FqName(name)
  }

  private fun checkSameScope(
    contributedClass: ClassReferenceIr,
    classToReplace: ClassReferenceIr,
    scopes: List<ClassReferenceIr>
  ) {
    val contributesToOurScope = classToReplace.annotations
      .findAll(contributesToFqName, contributesBindingFqName, contributesMultibindingFqName)
      .map { it.scope }
      .any { scope -> scope in scopes }

    if (!contributesToOurScope) {
      throw AnvilCompilationExceptionClassReferenceIr(
        classReference = contributedClass,
        message = "${contributedClass.fqName} with scopes " +
          "${scopes.joinToString(prefix = "[", postfix = "]") { it.fqName.asString() }} " +
          "wants to replace ${classToReplace.fqName}, but the replaced class isn't " +
          "contributed to the same scope."
      )
    }
  }

  private fun findContributedSubcomponentModules(
    declaration: ClassReferenceIr,
    scopes: List<ClassReferenceIr>,
    pluginContext: IrPluginContext,
    moduleFragment: IrModuleFragment
  ): Sequence<ClassReferenceIr> {
    return classScanner
      .findContributedClasses(
        pluginContext = pluginContext,
        moduleFragment = moduleFragment,
        annotation = contributesSubcomponentFqName,
        scope = null,
        moduleDescriptorFactory = moduleDescriptorFactory
      )
      .filter { clazz ->
        clazz.annotations.find(contributesSubcomponentFqName).any { it.parentScope in scopes }
      }
      .mapNotNull { contributedSubcomponent ->
        contributedSubcomponent.classId
          .generatedAnvilSubcomponent(declaration.classId)
          .createNestedClassId(Name.identifier(SUBCOMPONENT_MODULE))
          .referenceClassOrNull(pluginContext)
      }
  }
}

private val AnnotationReferenceIr.daggerAnnotationFqName: FqName
  get() = when (fqName) {
    mergeComponentFqName -> daggerComponentFqName
    mergeSubcomponentFqName -> daggerSubcomponentFqName
    mergeModulesFqName -> daggerModuleFqName
    else -> throw NotImplementedError("Don't know how to handle $this.")
  }

private val AnnotationReferenceIr.modulesKeyword: String
  get() = when (fqName) {
    mergeComponentFqName, mergeSubcomponentFqName -> "modules"
    mergeModulesFqName -> "includes"
    else -> throw NotImplementedError("Don't know how to handle $this.")
  }

package com.squareup.anvil.compiler

import com.squareup.anvil.annotations.ContributesBinding
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.fqName
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.toClassReferenceOrNull
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.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
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 SUBCOMPONENT_MODULE = "SubcomponentModule"

internal const val REFERENCE_SUFFIX = "_reference"
internal const val SCOPE_SUFFIX = "_scope"

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

@Suppress("UNCHECKED_CAST")
internal fun <T : ClassReference> ClassId.classReferenceOrNull(
  module: ModuleDescriptor
): T? = asSingleFqName().toClassReferenceOrNull(module) as T?

// If we're evaluating an anonymous inner class, it cannot merge anything and will cause
// a failure if we try to resolve its [ClassId]
internal fun ClassDescriptor.shouldIgnore(): Boolean {
  return classId == null || DescriptorUtils.isAnonymousObject(this)
}

/**
 * Returns the single element matching the given [predicate], or `null` if element was not found.
 * Unlike [singleOrNull] this method throws an exception if more than one element is found.
 */
internal inline fun <T> Iterable<T>.singleOrEmpty(predicate: (T) -> Boolean): T? {
  var single: T? = null
  var found = false
  for (element in this) {
    if (predicate(element)) {
      if (found) throw IllegalArgumentException(
        "Collection contains more than one matching element."
      )
      single = element
      found = true
    }
  }
  return single
}

private val truePredicate: (Any?) -> Boolean = { true }

/**
 * Returns single element, or `null` if the collection is empty. Unlike [singleOrNull] this
 * method throws an exception if more than one element is found.
 */
internal fun <T> Iterable<T>.singleOrEmpty(): T? = singleOrEmpty(truePredicate)

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

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

@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 org.gradle.api.Task

internal fun Task.log(message: String) {
  logger.info(message)
}

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

abstract class AppScope private constructor()

package com.squareup.scopes

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

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

package com.squareup.scopes

abstract class LoggedInScope private constructor()

package com.squareup.scopes

import javax.inject.Scope
import kotlin.reflect.KClass

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class SingleIn(val clazz: KClass<*>)

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)
@Repeatable
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)
@Repeatable
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.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
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(
      functionDescriptor: FunctionDescriptor,
      message: String,
      cause: Throwable? = null
    ): AnvilCompilationException {
      return AnvilCompilationException(
        message = message,
        cause = cause,
        element = functionDescriptor.identifier
      )
    }

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

    public operator fun invoke(
      propertyDescriptor: PropertyDescriptor,
      message: String,
      cause: Throwable? = null
    ): AnvilCompilationException {
      return AnvilCompilationException(
        message = message,
        cause = cause,
        element = propertyDescriptor.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 DeclarationDescriptorWithSource.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

import org.jetbrains.kotlin.descriptors.ModuleDescriptor

/**
 * 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

  /**
   * The module of the current compilation.
   */
  public val module: ModuleDescriptor
}

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
)

@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.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.NormalClass
import org.jetbrains.kotlin.types.ErrorType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils

// 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.classDescriptor(): ClassDescriptor {
  return classDescriptorOrNull()
    ?: throw AnvilCompilationException(
      "Unable to resolve type for $this."
    )
}

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

/**
 * Assumes that the FqName is a top-level property, e.g. com.squareup.CONSTANT.
 */
@ExperimentalAnvilApi
public fun FqName.getContributedPropertyOrNull(module: ModuleDescriptor): PropertyDescriptor? {
  return module.getPackage(parent()).memberScope
    .getContributedVariables(shortName(), NoLookupLocation.FROM_BACKEND)
    .singleOrNull()
}

package com.squareup.anvil.compiler.internal

import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesMultibinding
import com.squareup.anvil.annotations.ContributesSubcomponent
import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.annotations.ExperimentalAnvilApi
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 dagger.Lazy
import dagger.MapKey
import dagger.Provides
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import javax.inject.Inject
import javax.inject.Qualifier
import javax.inject.Scope
import kotlin.reflect.KClass

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

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 mergeComponentFqName = MergeComponent::class.fqName
internal val mergeSubcomponentFqName = MergeSubcomponent::class.fqName
internal val mergeInterfacesFqName = MergeInterfaces::class.fqName
internal val mergeModulesFqName = MergeModules::class.fqName

@ExperimentalAnvilApi
public fun FqName.descendant(segments: String): FqName =
  if (isRoot) FqName(segments) else FqName("${asString()}.$segments")

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

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

@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.reference.AnnotatedReference
import com.squareup.anvil.compiler.internal.reference.TypeReference
import com.squareup.anvil.compiler.internal.reference.canResolveFqName
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.jvm.jvmSuppressWildcards
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import java.io.ByteArrayOutputStream

@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 = FqName.fromSegments(packageSegments + classSegments)
      .canResolveFqName(module)

    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 ClassId.asClassName(): ClassName {
  return ClassName(
    packageName = packageFqName.asString(),
    simpleNames = relativeClassName.pathSegments().map { it.asString() }
  )
}

@ExperimentalAnvilApi
public fun TypeName.withJvmSuppressWildcardsIfNeeded(
  annotatedReference: AnnotatedReference,
  typeReference: TypeReference
): 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 = annotatedReference.isAnnotatedWith(jvmSuppressWildcardsFqName)

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

  // Same for functions.
  val isFunctionType = typeReference.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)
      // Suppress deprecation warnings.
      .addMember("\"DEPRECATION\"")
      // Suppress errors for experimental features in generated code.
      .apply {
        if (KotlinVersion.CURRENT > KotlinVersion(1, 6, 10)) {
          addMember("\"OPT_IN_USAGE\"")
          addMember("\"OPT_IN_USAGE_ERROR\"")
        } else {
          addMember("\"EXPERIMENTAL_API_USAGE_ERROR\"")
          addMember("\"EXPERIMENTAL_API_USAGE\"")
        }
      }
      .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()

@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.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.allSuperTypeClassReferences
import com.squareup.anvil.compiler.internal.reference.canResolveFqName
import com.squareup.anvil.compiler.internal.reference.toClassReference
import com.squareup.anvil.compiler.internal.reference.toClassReferenceOrNull
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
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.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtFunctionType
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNullableType
import org.jetbrains.kotlin.psi.KtPureElement
import org.jetbrains.kotlin.psi.KtSuperTypeListEntry
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf

private val kotlinAnnotations = listOf(jvmSuppressWildcardsFqName, publishedApiFqName)

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

@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 -> {
      FqName(text).takeIf { it.canResolveFqName(module) }
        ?.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.
          FqName("$qualifierText.$className")
            .takeIf { it.canResolveFqName(module) }
            ?.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()) {
          FqName(text).takeIf { it.canResolveFqName(module) }
            ?.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()
    is KtFunctionType -> {
      // KtFunctionType is a lambda. The compiler will translate lambdas to one of the function
      // interfaces. More details are available here:
      // https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md
      val parameterCount = parameters.size
      if (parameterCount !in 0..22) {
        throw AnvilCompilationException(
          element = this,
          message = "Couldn't find function type for $parameterCount parameters."
        )
      }
      return FqName("kotlin.jvm.functions.Function$parameterCount")
    }
    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 ->
            importPath.fqName.canResolveFqName(module)
          }.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 ->
            // Note that we must use the parent of the import FqName. An import is `com.abc.A` and
            // the classReference `A.Inner`, so we must try to resolve `com.abc.A.Inner` and not
            // `com.abc.A.A.Inner`.
            importPath.fqName.parent()
              .descendant(classReference)
              .canResolveFqName(module)
          }.fqName
      }
    }

  // If there's an import alias, then we know the FqName.
  importPaths
    .singleOrNull { it.alias?.asString() == classReference }
    ?.let { return it.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.
        FqName("kotlin.collections.$classReference")
          .takeIf { it.canResolveFqName(module) }
          ?.let { return it }
      }

      importFqName.descendant(classReference)
        .takeIf { it.canResolveFqName(module) }
        ?.let { return it }
    }

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

  // If the referenced type is declared within the same scope, it doesn't need to be imported.
  parents
    .filterIsInstance<KtClassOrObject>()
    .flatMap { it.collectDescendantsOfType<KtClassOrObject>() }
    .firstOrNull { it.nameAsSafeName.asString() == classReference }
    ?.let { return it.requireFqName() }

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

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

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

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

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

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

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

private fun PsiElement.findClassReferenceInSuperTypes(
  module: ModuleDescriptor,
  className: String
): ClassReference? {
  return parents
    .filterIsInstance<KtClassOrObject>()
    .map { it.toClassReference(module) }
    .flatMap { it.allSuperTypeClassReferences(includeSelf = true) }
    .mapNotNull { clazz ->
      clazz.fqName.descendant(className).toClassReferenceOrNull(module)
    }
    .firstOrNull()
}

package com.squareup.anvil.compiler.internal

import com.squareup.anvil.annotations.ExperimentalAnvilApi

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

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

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.annotations.ContributesBinding.Priority
import com.squareup.anvil.annotations.ContributesBinding.Priority.NORMAL
import com.squareup.anvil.compiler.contributesBindingFqName
import com.squareup.anvil.compiler.contributesMultibindingFqName
import com.squareup.anvil.compiler.contributesSubcomponentFqName
import com.squareup.anvil.compiler.daggerComponentFqName
import com.squareup.anvil.compiler.daggerModuleFqName
import com.squareup.anvil.compiler.daggerSubcomponentFqName
import com.squareup.anvil.compiler.internal.reference.AnnotationReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionAnnotationReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.argumentAt
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.mergeModulesFqName
import com.squareup.anvil.compiler.mergeSubcomponentFqName
import org.jetbrains.kotlin.name.FqName

internal fun AnnotationReference.parentScope(): ClassReference {
  return argumentAt("parentScope", index = 1)
    ?.value()
    ?: throw AnvilCompilationExceptionAnnotationReference(
      message = "Couldn't find parentScope for $fqName.",
      annotationReference = this
    )
}

internal fun AnnotationReference.ignoreQualifier(): Boolean {
  return argumentAt(
    name = "ignoreQualifier",
    index = when (fqName) {
      contributesBindingFqName -> 4
      contributesMultibindingFqName -> 3
      else -> return false
    }
  )
    ?.value()
    ?: false
}

internal fun AnnotationReference.priority(): Priority {
  return argumentAt("priority", index = 4)
    ?.value<FqName>()
    ?.let { Priority.valueOf(it.shortName().asString()) }
    ?: NORMAL
}

internal fun AnnotationReference.modules(
  parameterIndex: Int = modulesIndex(fqName)
): List<ClassReference> {
  return argumentAt(modulesName(fqName), parameterIndex)
    ?.value<List<ClassReference>>()
    .orEmpty()
}

private fun modulesIndex(annotationFqName: FqName): Int {
  return when (annotationFqName) {
    mergeComponentFqName, mergeSubcomponentFqName, mergeModulesFqName -> 1
    contributesSubcomponentFqName -> 2
    else -> throw NotImplementedError(
      "Couldn't find index of modules argument for $annotationFqName."
    )
  }
}

private fun modulesName(annotationFqName: FqName): String {
  return when (annotationFqName) {
    mergeComponentFqName, mergeSubcomponentFqName, contributesSubcomponentFqName -> "modules"
    mergeModulesFqName -> "includes"
    else -> throw NotImplementedError(
      "Couldn't find name of modules argument for $annotationFqName."
    )
  }
}

internal fun <T : AnnotationReference> List<T>.find(
  annotationName: FqName,
  scope: ClassReference? = null
): List<T> {
  return filter {
    it.fqName == annotationName && (scope == null || it.scopeOrNull() == scope)
  }
}

internal fun <T : AnnotationReference> List<T>.findAll(
  vararg annotationNames: FqName,
  scope: ClassReference? = null
): List<T> {
  return filter {
    it.fqName in annotationNames && (scope == null || it.scopeOrNull() == scope)
  }
}

internal fun <T : AnnotationReference> List<T>.checkNoDuplicateScope(
  contributeAnnotation: Boolean
) {
  // Exit early to avoid allocating additional collections.
  if (size < 2) return
  if (size == 2 && this[0].scope() != this[1].scope()) return

  // Check for duplicate scopes. Multiple contributions to the same scope are forbidden.
  val duplicates = groupBy { it.scope() }.filterValues { it.size > 1 }

  if (duplicates.isNotEmpty()) {
    val clazz = this[0].declaringClass()
    throw AnvilCompilationExceptionClassReference(
      classReference = clazz,
      message = if (contributeAnnotation) {
        "${clazz.fqName} contributes multiple times to the same scope: " +
          "${duplicates.keys.joinToString(prefix = "[", postfix = "]") { it.shortName }}. " +
          "Contributing multiple times to the same scope is forbidden and all scopes must " +
          "be distinct."
      } else {
        "${clazz.fqName} merges multiple times to the same scope: " +
          "${duplicates.keys.joinToString(prefix = "[", postfix = "]") { it.shortName }}. " +
          "Merging multiple times to the same scope is forbidden and all scopes must " +
          "be distinct."
      }
    )
  }
}

internal fun <T : AnnotationReference> List<T>.checkNoDuplicateScopeAndBoundType() {
  // Exit early to avoid allocating additional collections.
  if (size < 2) return
  if (size == 2 && this[0].scope() != this[1].scope()) return

  val duplicateScopes = groupBy { it.scope() }
    .filterValues { it.size > 1 }
    .ifEmpty { return }

  duplicateScopes.values.forEach { duplicateScopeAnnotations ->
    val duplicateBoundTypes = duplicateScopeAnnotations.groupBy { it.boundTypeOrNull() }
      .filterValues { it.size > 1 }
      .ifEmpty { return }
      .keys

    val clazz = this[0].declaringClass()
    throw AnvilCompilationExceptionClassReference(
      classReference = clazz,
      message = "${clazz.fqName} contributes multiple times to the same scope using the same " +
        "bound type: " +
        duplicateBoundTypes.joinToString(prefix = "[", postfix = "]") {
          it?.shortName ?: clazz.directSuperClassReferences().first().shortName
        } +
        ". Contributing multiple times to the same scope with the same bound type is forbidden " +
        "and all scope - bound type combinations must be distinct."
    )
  }
}

internal val AnnotationReference.daggerAnnotationFqName: FqName
  get() = when (fqName) {
    mergeComponentFqName -> daggerComponentFqName
    mergeSubcomponentFqName -> daggerSubcomponentFqName
    mergeModulesFqName -> daggerModuleFqName
    else -> throw NotImplementedError("Don't know how to handle $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.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.buildFile
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.decapitalize
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.mergeModulesFqName
import com.squareup.anvil.compiler.mergeSubcomponentFqName
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.KtFile
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 = !context.generateFactoriesOnly

  // 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<ClassReference>>()

  private val contributedBindingClasses = mutableListOf<ClassReference>()
  private val contributedMultibindingClasses = mutableListOf<ClassReference>()
  private val contributedModuleClasses = mutableListOf<ClassReference>()

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    val classes = projectFiles.classAndInnerClassReferences(module).toList()

    // 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 += classes.filter {
      it.isAnnotatedWith(contributesBindingFqName)
    }
    contributedMultibindingClasses += classes.filter {
      it.isAnnotatedWith(contributesMultibindingFqName)
    }

    // Similar to the explanation above, we must track contributed modules.
    contributedModuleClasses += classes.filter {
      it.isAnnotatedWith(contributesToFqName) && it.isAnnotatedWith(daggerModuleFqName)
    }

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

        // Remember for which scopes which types were excluded so that we later don't generate
        // a binding method for these types.
        mergeAnnotation
          .exclude()
          .takeIf { it.isNotEmpty() }
          ?.let { excludedReferences ->
            excludedTypesForClass[mergedClassKey] = excludedReferences
          }

        val packageName = generatePackageName(clazz)
        val className = clazz.generateClassName().relativeClassName.asString()

        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.fqName.asString(),
          clazz = clazz,
          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()
        .plus(
          classScanner
            .findContributedClasses(
              module = module,
              annotation = contributesToFqName,
              scope = scope.fqName
            )
            .filter { it.isAnnotatedWith(daggerModuleFqName) }
        )
        // Ignore replaced bindings specified by excluded modules for this scope.
        .filter { clazz -> clazz !in excludedNames }
        .flatMap { clazz ->
          clazz.annotations
            .find(annotationName = contributesToFqName, scope = scope)
            .flatMap { it.replaces() }
        }
        .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<ClassReference>,
        annotationFqName: FqName,
        isMultibinding: Boolean
      ): List<GeneratedMethod> {

        val contributedBindingsDependencies = classScanner.findContributedClasses(
          module,
          annotation = annotationFqName,
          scope = scope.fqName
        )

        val allContributedClasses = collectedClasses
          .plus(contributedBindingsDependencies)

        val replacedBindings = allContributedClasses
          .flatMap { classReference ->
            classReference.annotations
              .find(annotationName = annotationFqName, scope = scope)
              .flatMap { it.replaces() }
          }

        return allContributedClasses
          .asSequence()
          .filterNot { it in replacedBindings }
          .filterNot { it in bindingsReplacedInDaggerModules }
          .filterNot { it in excludedNames }
          .flatMap { clazz ->
            clazz.annotations
              .find(annotationName = annotationFqName, scope = scope)
              .map { it.toContributedBinding(isMultibinding) }
          }
          .let { bindings ->
            if (isMultibinding) {
              bindings
            } else {
              bindings.groupBy { it.qualifiersKeyLazy.value }
                .map { it.value.findHighestPriorityBinding() }
                .asSequence()
            }
          }
          .map { binding -> binding.toGeneratedMethod(isMultibinding) }
          .toList()
      }

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

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

      daggerModuleFile.writeText(content)

      GeneratedFile(daggerModuleFile, content)
    }
  }

  private fun daggerModuleContent(
    scope: String,
    clazz: ClassReference,
    generatedMethods: List<GeneratedMethod>
  ): String {
    val className = clazz.generateClassName(separator = "", suffix = ANVIL_MODULE_SUFFIX)
      .relativeClassName
      .asString()

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

    return FileSpec.buildFile(generatePackageName(clazz), 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 fun generatePackageName(clazz: ClassReference): String = MODULE_PACKAGE_PREFIX +
  clazz.packageFqName.safePackageString(dotPrefix = true)

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.contributedClass }

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

  return bindings[0]
}

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

  val isMapMultibinding = mapKeys.isNotEmpty()

  val methodNameSubString = contributedClass.fqName.pathSegments()
    .map { it.asString() }
    .plus(boundType.shortName)

  return if (contributedClass.isObject()) {
    ProviderMethod(
      FunSpec.builder(
        name = methodNameSubString
          .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(boundType.asClassName())
        .addStatement("return %T", contributedClass.asClassName())
        .build()
    )
  } else {
    BindingMethod(
      FunSpec.builder(
        name = methodNameSubString
          .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 = contributedClass.shortName.decapitalize(),
          type = contributedClass.asClassName()
        )
        .returns(boundType.asClassName())
        .build()
    )
  }
}

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

private data class MergedClassKey(
  val scope: ClassReference,
  val clazz: ClassReference
)

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.assistedInjectFqName
import com.squareup.anvil.compiler.contributesMultibindingFqName
import com.squareup.anvil.compiler.injectFqName
import com.squareup.anvil.compiler.internal.reference.AnnotationReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.FunctionReference
import com.squareup.anvil.compiler.internal.reference.Visibility
import com.squareup.anvil.compiler.internal.reference.allSuperTypeClassReferences
import org.jetbrains.kotlin.name.FqName

internal fun ClassReference.checkNotMoreThanOneQualifier(
  annotationFqName: FqName
) {
  // The class is annotated with @ContributesBinding, @ContributesMultibinding, or another Anvil annotation.
  // If there is less than 2 further annotations, then there can't be more than two qualifiers.
  if (annotations.size <= 2) return

  val qualifierCount = annotations.count { it.isQualifier() }
  if (qualifierCount > 1) {
    throw AnvilCompilationExceptionClassReference(
      message = "Classes annotated with @${annotationFqName.shortName()} may not use more " +
        "than one @Qualifier.",
      classReference = this
    )
  }
}

internal inline fun ClassReference.checkClassIsPublic(message: () -> String) {
  if (visibility() != Visibility.PUBLIC) {
    throw AnvilCompilationExceptionClassReference(
      classReference = this,
      message = message()
    )
  }
}

internal fun ClassReference.checkNotMoreThanOneMapKey() {
  // 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 (annotations.size <= 2) return

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

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

internal fun ClassReference.checkSingleSuperType(
  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 (annotations.find { it.fqName == annotationFqName }?.boundTypeOrNull() != null) return

  if (directSuperClassReferences().count() != 1) {
    throw AnvilCompilationExceptionClassReference(
      message = "$fqName 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.",
      classReference = this
    )
  }
}

internal fun ClassReference.checkClassExtendsBoundType(
  annotationFqName: FqName
) {
  val boundType = annotations.find { it.fqName == annotationFqName }?.boundTypeOrNull()
    ?: directSuperClassReferences().singleOrNull()
    ?: throw AnvilCompilationExceptionClassReference(
      message = "Couldn't find the bound type.",
      classReference = this
    )

  if (allSuperTypeClassReferences().none { it.fqName == boundType.fqName }) {
    throw AnvilCompilationExceptionClassReference(
      message = "${this.fqName} contributes a binding for ${boundType.fqName}, but doesn't " +
        "extend this type.",
      classReference = this
    )
  }
}

internal fun ClassReference.atLeastOneAnnotation(
  annotationName: FqName,
  scope: ClassReference? = null
): List<AnnotationReference> {
  return annotations.find(annotationName = annotationName, scope = scope)
    .ifEmpty {
      throw AnvilCompilationExceptionClassReference(
        classReference = this,
        message = "Class $fqName is not annotated with $annotationName" +
          "${if (scope == null) "" else " with scope ${scope.fqName}"}."
      )
    }
}

/**
 * Returns the constructor annotated with `@Inject` or `@AssistedInject` for this class.
 * 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 <T : FunctionReference> Collection<T>.injectConstructor(): T? {
  val constructors = filter {
    it.isAnnotatedWith(injectFqName) || it.isAnnotatedWith(assistedInjectFqName)
  }

  return when (constructors.size) {
    0 -> null
    1 -> constructors[0]
    else -> throw AnvilCompilationExceptionClassReference(
      classReference = constructors[0].declaringClass,
      message = "Types may only contain one injected constructor."
    )
  }
}

package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.compiler.CommandLineOptions
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.codegen.reference.RealAnvilModuleDescriptor
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>,
  private val commandLineOptions: CommandLineOptions,
  private val moduleDescriptorFactory: RealAnvilModuleDescriptor.Factory
) : 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) AnalysisResult.EMPTY else null
  }

  override fun analysisCompleted(
    project: Project,
    module: ModuleDescriptor,
    bindingTrace: BindingTrace,
    files: Collection<KtFile>
  ): AnalysisResult? {
    if (didRecompile) 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 = moduleDescriptorFactory.create(module)
    anvilModule.addFiles(files)

    val anvilContext = commandLineOptions.toAnvilContext(anvilModule)

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

    val generatedFiles = mutableMapOf<String, GeneratedFile>()

    val (privateCodeGenerators, nonPrivateCodeGenerators) =
      codeGenerators
        .filter { it.isApplicable(anvilContext) }
        .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
            val alreadyGeneratedFile = generatedFiles[path]
            if (
              alreadyGeneratedFile != null &&
              alreadyGeneratedFile.content != generatedFile.content
            ) {
              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.name } +
                  "\n\nThe content of the already generated file is:\n\n" +
                  alreadyGeneratedFile.content.lines().joinToString(separator = "\n") {
                    "\t$it"
                  } +
                  "\n\nThe content of the new file is:\n\n" +
                  generatedFile.content.lines().joinToString(separator = "\n") {
                    "\t$it"
                  }
              )
            }

            generatedFiles[path] = generatedFile
          }
          .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.toList())

    // 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.internal.reference.AnnotationReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi
import com.squareup.anvil.compiler.internal.reference.allSuperTypeClassReferences
import com.squareup.kotlinpoet.AnnotationSpec
import org.jetbrains.kotlin.types.KotlinType
import kotlin.LazyThreadSafetyMode.NONE

internal data class ContributedBinding(
  val contributedClass: ClassReference,
  val mapKeys: List<AnnotationSpec>,
  val qualifiers: List<AnnotationSpec>,
  val boundType: ClassReference,
  val priority: Priority,
  val qualifiersKeyLazy: Lazy<String>
)

internal fun AnnotationReference.toContributedBinding(
  isMultibinding: Boolean
): ContributedBinding {
  val boundType = requireBoundType()

  val mapKeys = if (isMultibinding) {
    declaringClass().annotations.filter { it.isMapKey() }.map { it.toAnnotationSpec() }
  } else {
    emptyList()
  }

  val ignoreQualifier = ignoreQualifier()
  val qualifiers = if (ignoreQualifier) {
    emptyList()
  } else {
    declaringClass().annotations.filter { it.isQualifier() }.map { it.toAnnotationSpec() }
  }

  return ContributedBinding(
    contributedClass = declaringClass(),
    mapKeys = mapKeys,
    qualifiers = qualifiers,
    boundType = boundType,
    priority = priority(),
    qualifiersKeyLazy = declaringClass().qualifiersKeyLazy(boundType, ignoreQualifier)
  )
}

private fun AnnotationReference.requireBoundType(): ClassReference {
  val boundFromAnnotation = boundTypeOrNull()

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

    boundType.checkNotGeneric(contributedClass = declaringClass())
    return boundType
  }

  // If there's no bound type in the annotation,
  // it must be the only supertype of the contributing class
  val boundType = declaringClass().directSuperClassReferences().singleOrNull()
    ?: throw AnvilCompilationException(
      message = "$fqName 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 @$shortName annotation."
    )

  boundType.checkNotGeneric(contributedClass = declaringClass())
  return boundType
}

private fun ClassReference.checkNotGeneric(
  contributedClass: ClassReference
) {
  fun exceptionText(typeString: String): String {
    return "Class ${contributedClass.fqName} 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.qualifiersKeyLazy(
  boundType: ClassReference,
  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 { boundType.fqName.asString() }
  }

  return lazy(NONE) { boundType.fqName.asString() + qualifiersKey() }
}

private fun ClassReference.qualifiersKey(): String {
  return annotations
    .filter { it.isQualifier() }
    // Note that we sort all elements. That's important for a stable string comparison.
    .sortedBy { it.classReference }
    .joinToString(separator = "") { annotation ->
      annotation.fqName.asString() +
        annotation.arguments.joinToString(separator = "") { argument ->
          val valueString = when (val value = argument.value<Any>()) {
            is ClassReference -> value.fqName.asString()
            else -> value.toString()
          }

          argument.resolvedName + valueString
        }
    }
}

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.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.buildFile
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
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.KtFile
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
      .classAndInnerClassReferences(module)
      .filter { it.isAnnotatedWith(contributesBindingFqName) }
      .onEach { clazz ->
        clazz.checkClassIsPublic {
          "${clazz.fqName} is binding a type, but the class is not public. " +
            "Only public types are supported."
        }
        clazz.checkNotMoreThanOneQualifier(contributesBindingFqName)
        clazz.checkSingleSuperType(contributesBindingFqName)
        clazz.checkClassExtendsBoundType(contributesBindingFqName)
      }
      .map { clazz ->
        val fileName = clazz.generateClassName().relativeClassName.asString()
        val generatedPackage = HINT_BINDING_PACKAGE_PREFIX +
          clazz.packageFqName.safePackageString(dotPrefix = true)
        val className = clazz.asClassName()
        val classFqName = clazz.fqName.toString()
        val propertyName = classFqName.replace('.', '_')

        val scopes = clazz.annotations
          .find(contributesBindingFqName)
          .also { it.checkNoDuplicateScopeAndBoundType() }
          .distinctBy { it.scope() }
          // Give it a stable sort.
          .sortedBy { it.scope() }
          .map { it.scope().asClassName() }

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

            scopes.forEachIndexed { index, scope ->
              addProperty(
                PropertySpec
                  .builder(
                    name = propertyName + SCOPE_SUFFIX + index,
                    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.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.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.buildFile
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
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.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
      .classAndInnerClassReferences(module)
      .filter { it.isAnnotatedWith(contributesMultibindingFqName) }
      .onEach { clazz ->
        clazz.checkClassIsPublic {
          "${clazz.fqName} is binding a type, but the class is not public. " +
            "Only public types are supported."
        }
        clazz.checkNotMoreThanOneQualifier(contributesMultibindingFqName)
        clazz.checkNotMoreThanOneMapKey()
        clazz.checkSingleSuperType(contributesMultibindingFqName)
        clazz.checkClassExtendsBoundType(contributesMultibindingFqName)
      }
      .map { clazz ->
        val fileName = clazz.generateClassName().relativeClassName.asString()
        val generatedPackage = HINT_MULTIBINDING_PACKAGE_PREFIX +
          clazz.packageFqName.safePackageString(dotPrefix = true)
        val className = clazz.asClassName()
        val classFqName = clazz.fqName.toString()
        val propertyName = classFqName.replace('.', '_')

        val scopes = clazz.annotations
          .find(contributesMultibindingFqName)
          .also { it.checkNoDuplicateScopeAndBoundType() }
          .distinctBy { it.scope() }
          // Give it a stable sort.
          .sortedBy { it.scope() }
          .map { it.scope().asClassName() }

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

            scopes.forEachIndexed { index, scope ->
              addProperty(
                PropertySpec
                  .builder(
                    name = propertyName + SCOPE_SUFFIX + index,
                    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.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.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.buildFile
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.Visibility
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
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.KtFile
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
      .classAndInnerClassReferences(module)
      .filter { it.isAnnotatedWith(contributesSubcomponentFqName) }
      .onEach { clazz ->
        if (!clazz.isInterface() && !clazz.isAbstract()) {
          throw AnvilCompilationExceptionClassReference(
            message = "${clazz.fqName} is annotated with " +
              "@${ContributesSubcomponent::class.simpleName}, but this class is not an interface.",
            classReference = clazz
          )
        }

        if (clazz.visibility() != Visibility.PUBLIC) {
          throw AnvilCompilationExceptionClassReference(
            message = "${clazz.fqName} is contributed to the Dagger graph, but the " +
              "interface is not public. Only public interfaces are supported.",
            classReference = clazz
          )
        }

        clazz.annotations
          .filter { it.fqName == contributesSubcomponentFqName }
          .forEach { annotation ->
            annotation.replaces().forEach {
              it.checkUsesSameScope(annotation.scope(), clazz)
            }
          }
      }
      .map { clazz ->
        val fileName = clazz.generateClassName().relativeClassName.asString()
        val generatedPackage = HINT_SUBCOMPONENTS_PACKAGE_PREFIX +
          clazz.packageFqName.safePackageString(dotPrefix = true)
        val className = clazz.asClassName()
        val classFqName = clazz.fqName.toString()
        val propertyName = classFqName.replace('.', '_')
        val parentScopeReference = clazz.annotations
          .single { it.fqName == contributesSubcomponentFqName }
          .parentScope()
        val parentScope = parentScopeReference.asClassName()

        clazz.checkParentComponentInterface(clazz.innerClasses(), parentScopeReference)
        clazz.checkFactory(clazz.innerClasses())

        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 ClassReference.checkParentComponentInterface(
    innerClasses: List<ClassReference>,
    parentScope: ClassReference
  ) {
    val parentComponents = innerClasses
      .filter {
        it.annotations.any { annotation ->
          annotation.fqName == contributesToFqName && annotation.scope() == parentScope
        }
      }

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

    val functions = componentInterface.functions
      .filter { it.returnType().asClassReference() == this }

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

  private fun ClassReference.checkFactory(innerClasses: List<ClassReference>) {
    innerClasses
      .firstOrNull { it.isAnnotatedWith(daggerSubcomponentFactoryFqName) }
      ?.let { factoryClass ->
        throw AnvilCompilationExceptionClassReference(
          classReference = factoryClass,
          message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " +
            "must use $contributesSubcomponentFactoryFqName and not " +
            "$daggerSubcomponentFactoryFqName."
        )
      }

    innerClasses
      .firstOrNull { it.isAnnotatedWith(daggerSubcomponentBuilderFqName) }
      ?.let { factoryClass ->
        throw AnvilCompilationExceptionClassReference(
          classReference = 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.isAnnotatedWith(contributesSubcomponentFactoryFqName) }

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

    if (!factory.isInterface() && !factory.isAbstract()) {
      throw AnvilCompilationExceptionClassReference(
        classReference = factory,
        message = "A factory must be an interface or an abstract class."
      )
    }

    val functions = factory.functions
      .let { functions ->
        if (factory.isInterface()) {
          functions
        } else {
          functions.filter { it.isAbstract() }
        }
      }

    if (functions.size != 1 || functions[0].returnType().asClassReference() != this) {
      throw AnvilCompilationExceptionClassReference(
        classReference = factory,
        message = "A factory must have exactly one abstract function returning the " +
          "subcomponent $fqName."
      )
    }
  }

  private fun ClassReference.checkUsesSameScope(
    scope: ClassReference,
    subcomponent: ClassReference
  ) {
    annotations
      .filter { it.fqName == contributesSubcomponentFqName }
      .ifEmpty {
        throw AnvilCompilationExceptionClassReference(
          classReference = subcomponent,
          message = "Couldn't find the annotation @ContributesSubcomponent for $fqName."
        )
      }
      .forEach { annotation ->
        val otherScope = annotation.scope()
        if (otherScope != scope) {
          throw AnvilCompilationExceptionClassReference(
            classReference = subcomponent,
            message = "${subcomponent.fqName} with scope ${scope.fqName} wants to replace " +
              "$fqName with scope ${otherScope.fqName}. The replacement must use the same scope."
          )
        }
      }
  }
}

package com.squareup.anvil.compiler.codegen

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.PARENT_COMPONENT
import com.squareup.anvil.compiler.SUBCOMPONENT_FACTORY
import com.squareup.anvil.compiler.SUBCOMPONENT_MODULE
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.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.reference.AnnotationReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.FunctionReference
import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.safePackageString
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.Binds
import dagger.Module
import dagger.Subcomponent
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.KtFile
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 = mutableSetOf<Contribution>()
  private val replacedReferences = mutableSetOf<ClassReference>()
  private val processedEvents = mutableSetOf<GenerateCodeEvent>()

  private var isFirstRound = true

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

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    if (isFirstRound) {
      isFirstRound = false
      populateInitialContributions(module)
    }

    // Find new @MergeComponent (and similar triggers) that would cause generating new code.
    triggers += projectFiles
      .classAndInnerClassReferences(module)
      .flatMap { it.annotations }
      .filter { it.fqName in generationTrigger }
      .map { Trigger(it) }

    // Find new contributed subcomponents in this module. If there's a trigger for them, then we
    // also need to generate code for them later.
    contributions += projectFiles
      .classAndInnerClassReferences(module)
      .flatMap { it.annotations }
      .filter { it.fqName == contributesSubcomponentFqName }
      .map { Contribution(it) }
      .toList()
      .also { contributions ->
        // Find all replaced subcomponents and remember them.
        replacedReferences += contributions.flatMap { contribution ->
          contribution.annotation
            .replaces()
            .also {
              checkReplacedSubcomponentWasNotAlreadyGenerated(contribution.clazz, it)
            }
        }
      }

    // Remove any contribution, if it was replaced by another contribution.
    contributions.removeAll { contribution ->
      contribution.clazz in replacedReferences
    }

    return contributions
      .flatMap { contribution ->
        triggers
          .filter { trigger ->
            trigger.scope == contribution.parentScope && contribution.clazz !in trigger.exclusions
          }
          .map { trigger ->
            GenerateCodeEvent(trigger, contribution)
          }
      }
      // Don't generate code for the same event twice.
      .minus(processedEvents)
      .also {
        processedEvents += it
      }
      .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)

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

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

                  addClassArrayMember("modules", contribution.annotation.modules())
                  addClassArrayMember("exclude", contribution.annotation.exclude())
                }
                .build()
            )
            .addAnnotations(
              contribution.clazz.annotations
                .filter { it.isDaggerScope() }
                .map { it.toAnnotationSpec() }
            )
            .apply {
              if (factoryClass != null) {
                addType(generateFactory(factoryClass.originalReference))
                addType(generateDaggerModule(factoryClass.originalReference))
              }
            }
            .addType(
              generateParentComponent(
                contribution, generatedAnvilSubcomponent, factoryClass
              )
            )
            .build()
            .also { addType(it) }
        }

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

  private fun generateFactory(
    factoryReference: ClassReference
  ): TypeSpec {
    val superclass = factoryReference.asClassName()

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

    return builder
      .addAnnotation(Subcomponent.Factory::class)
      .build()
  }

  private fun generateDaggerModule(
    factoryReference: ClassReference
  ): 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(factoryReference.asClassName())
          .build()
      )
      .build()
  }

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

    return TypeSpec
      .interfaceBuilder(PARENT_COMPONENT)
      .apply {
        if (parentComponentInterface != null) {
          addSuperinterface(parentComponentInterface.componentInterface)
        }
      }
      .addFunction(
        FunSpec
          .builder(
            name = parentComponentInterface
              ?.functionName
              ?: factoryClass?.let { "create${it.originalReference.fqName.shortName()}" }
              ?: "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,
    factoryClass: ClassReference?
  ): ParentComponentInterfaceHolder? {
    val contributedInnerComponentInterfaces = contribution.clazz
      .innerClasses()
      .filter { it.isInterface() }
      .filter { classReference ->
        classReference.annotations
          .any {
            it.fqName == contributesToFqName && it.scope() == contribution.parentScope
          }
      }
      .toList()

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

    val functions = componentInterface.functions
      .filter { it.isAbstract() && it.visibility() == PUBLIC }
      .filter {
        val returnType = it.returnType().asClassReference()
        returnType == contribution.clazz || (factoryClass != null && returnType == factoryClass)
      }

    val function = when (functions.size) {
      0 -> return null
      1 -> functions[0]
      else -> throw AnvilCompilationExceptionClassReference(
        classReference = contribution.clazz,
        message = "Expected zero or one function returning the " +
          "subcomponent ${contribution.clazz.fqName}."
      )
    }

    return ParentComponentInterfaceHolder(componentInterface, function)
  }

  private fun findFactoryClass(
    contribution: Contribution,
    generatedAnvilSubcomponent: ClassId
  ): FactoryClassHolder? {
    val contributionFqName = contribution.clazz.fqName

    val contributedFactories = contribution.clazz
      .innerClasses()
      .filter { classReference ->
        classReference.isAnnotatedWith(contributesSubcomponentFactoryFqName)
      }
      .onEach { factory ->
        if (!factory.isInterface() && !factory.isAbstract()) {
          throw AnvilCompilationExceptionClassReference(
            classReference = factory,
            message = "A factory must be an interface or an abstract class."
          )
        }

        val createComponentFunctions = factory.functions
          .filter { it.isAbstract() }
          .filter { it.returnType().asClassReference().fqName == contributionFqName }

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

    val classReference = when (contributedFactories.size) {
      0 -> return null
      1 -> contributedFactories[0]
      else -> throw AnvilCompilationExceptionClassReference(
        classReference = contribution.clazz,
        message = "Expected zero or one factory within $contributionFqName."
      )
    }

    return FactoryClassHolder(
      originalReference = classReference,
      generatedFactoryName = generatedAnvilSubcomponent
        .createNestedClassId(Name.identifier(SUBCOMPONENT_FACTORY))
        .asClassName()
    )
  }

  private fun checkReplacedSubcomponentWasNotAlreadyGenerated(
    contributedReference: ClassReference,
    replacedReferences: List<ClassReference>
  ) {
    replacedReferences.forEach { replacedReference ->
      if (processedEvents.any { it.contribution.clazz == replacedReference }) {
        throw AnvilCompilationExceptionClassReference(
          classReference = contributedReference,
          message = "${contributedReference.fqName} tries to replace " +
            "${replacedReference.fqName}, but the code for ${replacedReference.fqName} was " +
            "already generated. This is not supported."
        )
      }
    }
  }

  private fun populateInitialContributions(
    module: ModuleDescriptor
  ) {
    // Find all contributed subcomponents from precompiled dependencies and generate the
    // necessary code eventually if there's a trigger.
    contributions += classScanner
      .findContributedClasses(
        module = module,
        annotation = contributesSubcomponentFqName,
        scope = null
      )
      .map { clazz ->
        Contribution(
          annotation = clazz.annotations.single { it.fqName == contributesSubcomponentFqName }
        )
      }

    // Find all replaced subcomponents from precompiled dependencies.
    replacedReferences += contributions
      .flatMap { contribution ->
        contribution.clazz
          .annotations.single { it.fqName == contributesSubcomponentFqName }
          .replaces()
      }
  }

  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(
    annotation: AnnotationReference
  ) {
    val clazz = annotation.declaringClass()
    val scope = annotation.scope()
    val exclusions = annotation.exclude()
    val clazzFqName = clazz.fqName

    override fun toString(): String {
      return "Trigger(clazz=$clazzFqName, scope=$scope)"
    }

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

      other as Trigger

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

      return true
    }

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

  private class Contribution(
    val annotation: AnnotationReference
  ) {
    val clazz = annotation.declaringClass()
    val scope = annotation.scope()
    val parentScope = annotation.parentScope()

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

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

      other as Contribution

      if (scope != other.scope) return false
      if (parentScope != other.parentScope) return false
      if (clazz != other.clazz) return false

      return true
    }

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

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

  private class ParentComponentInterfaceHolder(
    componentInterface: ClassReference,
    function: FunctionReference
  ) {
    val componentInterface = componentInterface.asClassName()
    val functionName = function.fqName.shortName().asString()
  }

  private class FactoryClassHolder(
    val originalReference: ClassReference,
    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.pathSegments()
    .joinToString(separator = "_", postfix = ANVIL_SUBCOMPONENT_SUFFIX)

  return ClassId(FqName(packageFqName), FqName(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.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.buildFile
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.Visibility
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
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.psi.KtFile
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
      .classAndInnerClassReferences(module)
      .filter { it.isAnnotatedWith(contributesToFqName) }
      .onEach { clazz ->
        if (!clazz.isInterface() &&
          !clazz.isAnnotatedWith(daggerModuleFqName) &&
          !clazz.isAnnotatedWith(mergeModulesFqName)
        ) {
          throw AnvilCompilationExceptionClassReference(
            classReference = clazz,
            message = "${clazz.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}?",
          )
        }

        if (clazz.visibility() != Visibility.PUBLIC) {
          throw AnvilCompilationExceptionClassReference(
            classReference = clazz,
            message = "${clazz.fqName} is contributed to the Dagger graph, but the " +
              "module is not public. Only public modules are supported.",
          )
        }
      }
      .map { clazz ->
        val fileName = clazz.generateClassName().relativeClassName.asString()
        val generatedPackage = HINT_CONTRIBUTES_PACKAGE_PREFIX +
          clazz.packageFqName.safePackageString(dotPrefix = true)
        val className = clazz.asClassName()
        val classFqName = clazz.fqName.toString()
        val propertyName = classFqName.replace('.', '_')

        val scopes = clazz.annotations
          .find(contributesToFqName)
          .also { it.checkNoDuplicateScope(contributeAnnotation = true) }
          // Give it a stable sort.
          .sortedBy { it.scope() }
          .map { it.scope().asClassName() }

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

            scopes.forEachIndexed { index, scope ->
              addProperty(
                PropertySpec
                  .builder(
                    name = propertyName + SCOPE_SUFFIX + index,
                    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.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.google.auto.service.AutoService
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.internal.reference.AnnotationReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
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 MergeAnnotationsCheckGenerator : PrivateCodeGenerator() {
  override fun isApplicable(context: AnvilContext): Boolean = !context.disableComponentMerging

  override fun generateCodePrivate(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ) {
    projectFiles
      .classAndInnerClassReferences(module)
      .forEach { clazz ->
        val annotations = clazz.annotations
          .findAll(
            mergeComponentFqName,
            mergeSubcomponentFqName,
            mergeModulesFqName,
            mergeInterfacesFqName
          )
          .ifEmpty { return@forEach }

        annotations.checkSingleAnnotation()
        annotations.checkNoDuplicateScope(contributeAnnotation = false)

        // Note that we only allow a single type of `@Merge*` annotation through the check above.
        // The same class can't merge a component and subcomponent at the same time. Therefore,
        // all annotations must have the same FqName and we can use the first annotation to check
        // for the Dagger annotation.
        annotations
          .firstOrNull { it.fqName != mergeInterfacesFqName }
          ?.checkNotAnnotatedWithDaggerAnnotation()
      }
  }

  private fun List<AnnotationReference>.checkSingleAnnotation() {
    val distinctAnnotations = distinctBy { it.fqName }
    if (distinctAnnotations.size > 1) {
      throw AnvilCompilationExceptionClassReference(
        classReference = this[0].declaringClass(),
        message = "It's only allowed to have one single type of @Merge* annotation, " +
          "however multiple instances of the same annotation are allowed. You mix " +
          distinctAnnotations.joinToString(prefix = "[", postfix = "]") {
            it.fqName.shortName().asString()
          } +
          " and this is forbidden."
      )
    }
  }

  private fun AnnotationReference.checkNotAnnotatedWithDaggerAnnotation() {
    if (declaringClass().isAnnotatedWith(daggerAnnotationFqName)) {
      throw AnvilCompilationExceptionClassReference(
        classReference = declaringClass(),
        message = "When using @${fqName.shortName()} it's not allowed to " +
          "annotate the same class with @${daggerAnnotationFqName.shortName()}. " +
          "The Dagger annotation will be generated."
      )
    }
  }
}

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.compiler.CommandLineOptions
import com.squareup.anvil.compiler.api.AnvilContext
import org.jetbrains.kotlin.descriptors.ModuleDescriptor

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

internal fun CommandLineOptions.toAnvilContext(
  module: ModuleDescriptor
): RealAnvilContext = RealAnvilContext(
  generateFactories = generateFactories,
  generateFactoriesOnly = generateFactoriesOnly,
  disableComponentMerging = disableComponentMerging,
  module = module
)

package com.squareup.anvil.sample

import android.app.Application
import com.squareup.scopes.ComponentHolder

class App : Application() {
  override fun onCreate() {
    super.onCreate()

    ComponentHolder.components += DaggerAppComponent.create()
  }
}

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.sample

import com.squareup.anvil.sample.Parent.FATHER
import com.squareup.anvil.sample.Parent.MOTHER

enum class God(val parent: Parent) {
  ZEUS(FATHER),
  HERA(MOTHER),
  HEPHAESTUS(FATHER),
}

enum class Parent {
  FATHER, MOTHER
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import org.jetbrains.kotlin.name.FqName

/**
 * This marks any code reference that can be annotated, such as a [ClassReference] or
 * [FunctionReference].
 */
@ExperimentalAnvilApi
public interface AnnotatedReference {
  public val annotations: List<AnnotationReference>

  public fun isAnnotatedWith(fqName: FqName): Boolean = annotations.any { it.fqName == fqName }
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.internal.argumentType
import com.squareup.anvil.compiler.internal.classDescriptor
import com.squareup.anvil.compiler.internal.descendant
import com.squareup.anvil.compiler.internal.fqNameOrNull
import com.squareup.anvil.compiler.internal.getContributedPropertyOrNull
import com.squareup.anvil.compiler.internal.reference.AnnotationArgumentReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.AnnotationArgumentReference.Psi
import com.squareup.anvil.compiler.internal.requireFqName
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtPrefixExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtStringTemplateEntry
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
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.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.BooleanValue
import org.jetbrains.kotlin.resolve.constants.ByteValue
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.constants.DoubleValue
import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.resolve.constants.FloatValue
import org.jetbrains.kotlin.resolve.constants.IntValue
import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.resolve.constants.LongValue
import org.jetbrains.kotlin.resolve.constants.ShortValue
import org.jetbrains.kotlin.resolve.constants.StringValue
import kotlin.LazyThreadSafetyMode.NONE

@ExperimentalAnvilApi
public sealed class AnnotationArgumentReference {

  public abstract val annotation: AnnotationReference
  public abstract val name: String?
  public abstract val resolvedName: String

  public val module: AnvilModuleDescriptor get() = annotation.module

  protected abstract val value: Any

  // Maybe we need to make the return type nullable and allow for a default value.
  @Suppress("UNCHECKED_CAST")
  public fun <T : Any> value(): T = value as T

  protected fun parameterFqName(): String {
    return annotation.classReference
      .constructors
      .single()
      .parameters
      .single { it.name == resolvedName }
      .type()
      .asClassReference()
      .fqName
      .asString()
  }

  protected fun List<Any>.convertToArrayIfNeeded(): Any {
    return when (parameterFqName()) {
      BooleanArray::class.qualifiedName ->
        filterIsInstance<Boolean>().toBooleanArray()
      IntArray::class.qualifiedName ->
        filterIsInstance<Int>().toIntArray()
      LongArray::class.qualifiedName ->
        filterIsInstance<Long>().toLongArray()
      DoubleArray::class.qualifiedName ->
        filterIsInstance<Double>().toDoubleArray()
      ByteArray::class.qualifiedName ->
        filterIsInstance<Byte>().toByteArray()
      ShortArray::class.qualifiedName ->
        filterIsInstance<Short>().toShortArray()
      FloatArray::class.qualifiedName ->
        filterIsInstance<Float>().toFloatArray()
      else -> this
    }
  }

  override fun toString(): String {
    return "${AnnotationArgumentReference::class.simpleName}(name=$name, value=$value, " +
      "resolvedName=$resolvedName)"
  }

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

    if (name != other.name) return false
    if (value != other.value) return false
    if (resolvedName != other.resolvedName) return false

    return true
  }

  override fun hashCode(): Int {
    var result = name.hashCode()
    result = 31 * result + value.hashCode()
    result = 31 * result + resolvedName.hashCode()
    return result
  }

  public class Psi internal constructor(
    public val argument: KtValueArgument,
    override val annotation: AnnotationReference.Psi,
    override val name: String?,
    override val resolvedName: String
  ) : AnnotationArgumentReference() {
    protected override val value: Any by lazy(NONE) {
      fun fail(): Nothing {
        throw NotImplementedError("Don't know how to handle ${argument.children.last().text}.")
      }

      fun List<KtProperty>.findConstPropertyWithName(name: String): KtProperty? {
        return singleOrNull { property ->
          property.hasModifier(KtTokens.CONST_KEYWORD) && property.name == name
        }
      }

      fun PsiElement.findConstantDefinitionInScope(name: String): String? {
        when (this) {
          is KtProperty, is KtNamedFunction -> {
            // Function or Property, traverse up
            return (this as KtElement).containingClass()?.findConstantDefinitionInScope(name)
              ?: containingFile.findConstantDefinitionInScope(name)
          }
          is KtObjectDeclaration -> {
            toClassReference(module)
              .properties
              .map { it.property }
              .findConstPropertyWithName(name)
              ?.initializer
              ?.text
              ?.let { return it }

            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 -> {
            children.filterIsInstance<KtProperty>()
              .findConstPropertyWithName(name)
              ?.initializer
              ?.text
              ?.let { return it }
          }
          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)
      }

      fun parsePrimitiveType(text: String): Any {
        // We need to check the parameter for what type exactly.
        return when (parameterFqName()) {
          Boolean::class.qualifiedName, BooleanArray::class.qualifiedName ->
            text.toBooleanStrictOrNull()
          Int::class.qualifiedName, IntArray::class.qualifiedName ->
            text.toIntOrNull()
          Long::class.qualifiedName, LongArray::class.qualifiedName ->
            text.toLongOrNull()
          Double::class.qualifiedName, DoubleArray::class.qualifiedName ->
            text.toDoubleOrNull()
          Byte::class.qualifiedName, ByteArray::class.qualifiedName ->
            text.toByteOrNull()
          Short::class.qualifiedName, ShortArray::class.qualifiedName ->
            text.toShortOrNull()
          Float::class.qualifiedName, FloatArray::class.qualifiedName ->
            text.toFloatOrNull()
          String::class.qualifiedName -> {
            if (text.startsWith("\"") && text.endsWith("\"")) {
              text.substring(1, text.length - 1)
            } else {
              text
            }
          }
          else -> fail()
        } ?: fail()
      }

      fun resolvePrimitiveConstant(fqName: FqName): Any? {
        // This could be a constant from a primitive type, e.g. Int.MAX_VALUE
        val classFqName = fqName.parent()
        val constantName = fqName.shortName().asString()

        // If this constant is coming from a companion object, then we'll find it this way.
        classFqName.toClassReferenceOrNull(module)
          ?.let { if (it.isObject()) listOf(it) else it.companionObjects() }
          ?.flatMap { it.properties }
          ?.singleOrNull { it.name == constantName }
          ?.let { property ->
            return when (property) {
              is PropertyReference.Descriptor ->
                property.property.compileTimeInitializer?.value
              is PropertyReference.Psi ->
                property.property.initializer?.let { parsePrimitiveType(it.text) }
            }
          }

        // Is the constant a top-level constant?
        return fqName.getContributedPropertyOrNull(module)
          ?.compileTimeInitializer
          ?.value
      }

      fun resolveConstant(fqName: FqName): Any? {
        return if (fqName.toClassReferenceOrNull(module) != null) {
          // That's an enum constant.
          fqName
        } else {
          // That's hopefully a constant for a primitive type.
          resolvePrimitiveConstant(fqName)
        }
      }

      fun resolveConstant(psiElement: PsiElement): Any? {
        val fqName = psiElement.fqNameOrNull(module)
          ?: (psiElement as? KtDotQualifiedExpression)
            // That's necessary for constants like kotlin.Int.MAX_VALUE, where the real FqName is
            // actually Kotlin.Int.Companion.MAX_VALUE.
            ?.firstChild
            ?.fqNameOrNull(module)
            ?.descendant(psiElement.children.last().text)
          ?: return null

        return resolveConstant(fqName)
      }

      fun parsePsiElement(psiElement: PsiElement): Any {
        return when (psiElement) {
          is KtClassLiteralExpression -> psiElement.requireFqName(module).toClassReference(module)
          is KtCollectionLiteralExpression ->
            psiElement.children.map { parsePsiElement(it) }.convertToArrayIfNeeded()
          is KtConstantExpression, is KtPrefixExpression -> parsePrimitiveType(psiElement.text)
          is KtStringTemplateExpression ->
            psiElement
              .getChildOfType<KtStringTemplateEntry>()
              ?.text
              ?: fail()
          is KtNameReferenceExpression -> {
            // Those are likely enum values or another primitive constant.
            resolveConstant(psiElement)?.let { return it }

            // Check if it's a constant. Note that we don't return the constant FqName directly,
            // but return the actual value from the constant instead. That's also what the
            // descriptor APIs do. They never see the constant itself, but the
            // resolved / inlined value instead.
            psiElement
              .findConstantDefinitionInScope(psiElement.getReferencedName())
              ?.let { return parsePrimitiveType(it) }

            // 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.
            return psiElement.containingKtFile.packageFqName
              .descendant(psiElement.getReferencedName())
          }
          is KtDotQualifiedExpression -> {
            // Maybe it's a fully qualified name, so check this too.
            return resolveConstant(psiElement)
              ?: resolveConstant(FqName(psiElement.text))
              ?: fail()
          }
          else -> fail()
        }
      }

      parsePsiElement(argument.children.last())
    }
  }

  public class Descriptor internal constructor(
    public val argument: ConstantValue<*>,
    override val annotation: AnnotationReference.Descriptor,
    override val name: String,
    override val resolvedName: String = name
  ) : AnnotationArgumentReference() {

    protected override val value: Any by lazy(NONE) {
      fun fail(): Nothing {
        throw NotImplementedError("Don't know how to handle $argument.")
      }

      fun parseConstantValue(value: ConstantValue<*>): Any {
        return when (value) {
          is KClassValue -> value.toClassReference()
          is ArrayValue -> value.value.map { parseConstantValue(it) }.convertToArrayIfNeeded()
          is StringValue -> value.value
          is EnumValue ->
            value.enumClassId.asSingleFqName()
              .descendant(value.enumEntryName.asString())
          is BooleanValue -> value.value
          is IntValue -> value.value
          is LongValue -> value.value
          is DoubleValue -> value.value
          is ByteValue -> value.value
          is ShortValue -> value.value
          is FloatValue -> value.value
          else -> fail()
        }
      }

      parseConstantValue(argument)
    }

    private fun ConstantValue<*>.toClassReference(): ClassReference =
      argumentType(module).classDescriptor().toClassReference(module)
  }
}

@ExperimentalAnvilApi
public fun KtValueArgument.toAnnotationArgumentReference(
  annotationReference: AnnotationReference.Psi,
  indexOfArgument: Int
): Psi {
  val children = children
  val name = (children.firstOrNull() as? KtValueArgumentName)?.asName?.asString()

  // If no name is specified, then look up the name in the annotation class.
  val resolvedName = name ?: annotationReference.classReference
    .constructors
    .single()
    .parameters[indexOfArgument]
    .name

  return Psi(
    argument = this,
    annotation = annotationReference,
    name = name,
    resolvedName = resolvedName
  )
}

@ExperimentalAnvilApi
public fun Pair<Name, ConstantValue<*>>.toAnnotationArgumentReference(
  annotationReference: AnnotationReference.Descriptor
): Descriptor {
  return Descriptor(
    argument = second,
    annotation = annotationReference,
    name = first.asString()
  )
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.contributesBindingFqName
import com.squareup.anvil.compiler.internal.contributesMultibindingFqName
import com.squareup.anvil.compiler.internal.contributesSubcomponentFqName
import com.squareup.anvil.compiler.internal.contributesToFqName
import com.squareup.anvil.compiler.internal.daggerScopeFqName
import com.squareup.anvil.compiler.internal.mapKeyFqName
import com.squareup.anvil.compiler.internal.mergeComponentFqName
import com.squareup.anvil.compiler.internal.mergeInterfacesFqName
import com.squareup.anvil.compiler.internal.mergeModulesFqName
import com.squareup.anvil.compiler.internal.mergeSubcomponentFqName
import com.squareup.anvil.compiler.internal.qualifierFqName
import com.squareup.anvil.compiler.internal.reference.AnnotationReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.AnnotationReference.Psi
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.MemberName
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import kotlin.LazyThreadSafetyMode.NONE

private const val DEFAULT_SCOPE_INDEX = 0

/**
 * Used to create a common type between [KtAnnotationEntry] class references and
 * [AnnotationDescriptor] references, to streamline parsing.
 */
@ExperimentalAnvilApi
public sealed class AnnotationReference {

  /**
   * Refers to the annotation class itself and not the annotated class.
   */
  public abstract val classReference: ClassReference

  /**
   * Refers to the class that is annotated with this annotation reference. Note that annotations
   * can be used at different places, e.g. properties, constructors, functions, etc., therefore
   * this field must be nullable.
   */
  protected abstract val declaringClass: ClassReference?

  public val fqName: FqName get() = classReference.fqName
  public val shortName: String get() = fqName.shortName().asString()
  public val module: AnvilModuleDescriptor get() = classReference.module

  public abstract val arguments: List<AnnotationArgumentReference>

  public fun declaringClassOrNull(): ClassReference? = declaringClass
  public open fun declaringClass(): ClassReference = declaringClass
    ?: throw AnvilCompilationExceptionAnnotationReference(
      annotationReference = this,
      message = "The declaring class was null, this means the annotation wasn't used on a class."
    )

  public abstract fun scopeOrNull(parameterIndex: Int = DEFAULT_SCOPE_INDEX): ClassReference?
  public fun scope(parameterIndex: Int = DEFAULT_SCOPE_INDEX): ClassReference =
    scopeOrNull(parameterIndex)
      ?: throw AnvilCompilationExceptionAnnotationReference(
        annotationReference = this,
        message = "Couldn't find scope for $fqName."
      )

  public fun boundTypeOrNull(): ClassReference? = argumentAt("boundType", 1)?.value()

  public fun replaces(parameterIndex: Int = replacesIndex(fqName)): List<ClassReference> =
    argumentAt("replaces", parameterIndex)?.value<List<ClassReference>>().orEmpty()

  public fun exclude(parameterIndex: Int = excludeIndex(fqName)): List<ClassReference> =
    argumentAt("exclude", parameterIndex)?.value<List<ClassReference>>().orEmpty()

  public fun isQualifier(): Boolean = classReference.isAnnotatedWith(qualifierFqName)
  public fun isMapKey(): Boolean = classReference.isAnnotatedWith(mapKeyFqName)
  public fun isDaggerScope(): Boolean = classReference.isAnnotatedWith(daggerScopeFqName)

  public fun toAnnotationSpec(): AnnotationSpec {
    return AnnotationSpec
      .builder(classReference.asClassName())
      .apply {
        arguments.forEach { argument ->
          when (val value = argument.value<Any>()) {
            is ClassReference -> addMember(
              "${argument.resolvedName} = %T::class",
              value.asClassName()
            )
            is FqName -> {
              val clazz = value.toClassReferenceOrNull(module)
              if (clazz != null) {
                // That's an enum value!
                val enumMember = MemberName(
                  enclosingClassName = clazz.enclosingClass()!!.asClassName(),
                  simpleName = clazz.shortName
                )
                addMember("${argument.resolvedName} = %M", enumMember)
              } else {
                addMember("${argument.resolvedName} = ${value.asString()}")
              }
            }
            is String -> addMember("${argument.resolvedName} = \"$value\"")
            is BooleanArray -> addMember(
              "${argument.resolvedName} = ${value.joinToString(prefix = "[", postfix = "]")}"
            )
            is IntArray -> addMember(
              "${argument.resolvedName} = ${value.joinToString(prefix = "[", postfix = "]")}"
            )
            is LongArray -> addMember(
              "${argument.resolvedName} = ${value.joinToString(prefix = "[", postfix = "]")}"
            )
            is DoubleArray -> addMember(
              "${argument.resolvedName} = ${value.joinToString(prefix = "[", postfix = "]")}"
            )
            is ByteArray -> addMember(
              "${argument.resolvedName} = ${value.joinToString(prefix = "[", postfix = "]")}"
            )
            is ShortArray -> addMember(
              "${argument.resolvedName} = ${value.joinToString(prefix = "[", postfix = "]")}"
            )
            is FloatArray -> addMember(
              "${argument.resolvedName} = ${value.joinToString(prefix = "[", postfix = "]")}"
            )
            is List<*> -> {
              when {
                value.isEmpty() -> addMember("${argument.resolvedName} = []")
                value[0] is ClassReference -> {
                  val classes = value.filterIsInstance<ClassReference>()
                  val template = classes.joinToString { "%T::class" }

                  addMember(
                    "${argument.resolvedName} = [$template]",
                    *classes.map { it.asClassName() }.toTypedArray()
                  )
                }
                value[0] is FqName -> {
                  val args = value.filterIsInstance<FqName>().joinToString { it.asString() }
                  addMember("${argument.resolvedName} = [$args]")
                }
              }
            }
            // int, long, ... other primitives.
            else -> addMember("${argument.resolvedName} = $value")
          }
        }
      }
      .build()
  }

  override fun toString(): String = "@$fqName"

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

    if (fqName != other.fqName) return false
    if (arguments != other.arguments) return false

    return true
  }

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

  public class Psi internal constructor(
    public val annotation: KtAnnotationEntry,
    override val classReference: ClassReference,
    override val declaringClass: ClassReference.Psi?,
  ) : AnnotationReference() {

    override val arguments: List<AnnotationArgumentReference.Psi> by lazy(NONE) {
      annotation.valueArguments
        .filterIsInstance<KtValueArgument>()
        .mapIndexed { index, argument ->
          argument.toAnnotationArgumentReference(this, index)
        }
    }

    private val defaultScope by lazy(NONE) { computeScope(DEFAULT_SCOPE_INDEX) }

    // We need the scope so often that it's better to cache the value. Since the index could be
    // potentially different, only cache the value for the default index.
    override fun scopeOrNull(parameterIndex: Int): ClassReference? =
      if (parameterIndex == DEFAULT_SCOPE_INDEX) defaultScope else computeScope(parameterIndex)

    private fun computeScope(parameterIndex: Int): ClassReference? {
      return argumentAt("scope", parameterIndex)?.value()
    }
  }

  public class Descriptor internal constructor(
    public val annotation: AnnotationDescriptor,
    override val classReference: ClassReference,
    override val declaringClass: ClassReference.Descriptor?,
  ) : AnnotationReference() {

    override val arguments: List<AnnotationArgumentReference.Descriptor> by lazy(NONE) {
      annotation.allValueArguments.toList().map { it.toAnnotationArgumentReference(this) }
    }

    private val scope by lazy(NONE) {
      arguments.singleOrNull { it.name == "scope" }?.value<ClassReference>()
    }

    override fun declaringClass(): ClassReference.Descriptor {
      return super.declaringClass() as ClassReference.Descriptor
    }

    override fun scopeOrNull(parameterIndex: Int): ClassReference? = scope
  }
}

@ExperimentalAnvilApi
public fun KtAnnotationEntry.toAnnotationReference(
  declaringClass: ClassReference.Psi?,
  module: ModuleDescriptor
): Psi {
  return Psi(
    annotation = this,
    classReference = requireFqName(module).toClassReference(module),
    declaringClass = declaringClass
  )
}

@ExperimentalAnvilApi
public fun AnnotationDescriptor.toAnnotationReference(
  declaringClass: ClassReference.Descriptor?,
  module: ModuleDescriptor
): Descriptor {
  val annotationClass = annotationClass ?: throw AnvilCompilationException(
    message = "Couldn't find the annotation class for $fqName",
  )

  return Descriptor(
    annotation = this,
    classReference = annotationClass.toClassReference(module),
    declaringClass = declaringClass
  )
}

@ExperimentalAnvilApi
public fun AnnotationReference.argumentAt(
  name: String,
  index: Int
): AnnotationArgumentReference? {
  return arguments.singleOrNull { it.name == name }
    ?: arguments.elementAtOrNull(index)?.takeIf { it.name == null }
}

@ExperimentalAnvilApi
@Suppress("FunctionName")
public fun AnvilCompilationExceptionAnnotationReference(
  annotationReference: AnnotationReference,
  message: String,
  cause: Throwable? = null
): AnvilCompilationException = when (annotationReference) {
  is Psi -> AnvilCompilationException(
    element = annotationReference.annotation,
    message = message,
    cause = cause
  )
  is Descriptor -> AnvilCompilationException(
    annotationDescriptor = annotationReference.annotation,
    message = message,
    cause = cause
  )
}

private fun replacesIndex(annotationFqName: FqName): Int {
  return when (annotationFqName) {
    contributesToFqName -> 1
    contributesBindingFqName, contributesMultibindingFqName -> 2
    contributesSubcomponentFqName -> 4
    else -> throw NotImplementedError(
      "Couldn't find index of replaces argument for $annotationFqName."
    )
  }
}

private fun excludeIndex(annotationFqName: FqName): Int {
  return when (annotationFqName) {
    mergeInterfacesFqName -> 1
    mergeSubcomponentFqName -> 2
    mergeComponentFqName, mergeModulesFqName, contributesSubcomponentFqName -> 3
    else -> throw NotImplementedError(
      "Couldn't find index of exclude argument for $annotationFqName."
    )
  }
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.internal.classIdBestGuess
import com.squareup.anvil.compiler.internal.reference.ClassReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.incremental.components.LookupLocation
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

@ExperimentalAnvilApi
public interface AnvilModuleDescriptor : ModuleDescriptor {
  public fun resolveClassIdOrNull(classId: ClassId): FqName?

  public fun resolveFqNameOrNull(
    fqName: FqName,
    lookupLocation: LookupLocation = FROM_BACKEND
  ): ClassDescriptor?

  public fun getClassAndInnerClassReferences(ktFile: KtFile): List<Psi>

  public fun getClassReference(clazz: KtClassOrObject): Psi

  public fun getClassReference(descriptor: ClassDescriptor): Descriptor

  /**
   * Attempts to find the [KtClassOrObject] for the [FqName] first, then falls back to the
   * [ClassDescriptor] if the Psi element cannot be found. This will happen if the class for
   * [FqName] is not part of this compilation unit.
   */
  public fun getClassReferenceOrNull(fqName: FqName): ClassReference?
}

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

@ExperimentalAnvilApi
public fun FqName.canResolveFqName(
  module: ModuleDescriptor
): Boolean = module.asAnvilModuleDescriptor().resolveClassIdOrNull(classIdBestGuess()) != null

@ExperimentalAnvilApi
public fun Collection<KtFile>.classAndInnerClassReferences(
  module: ModuleDescriptor
): Sequence<Psi> {
  return asSequence().flatMap {
    module.asAnvilModuleDescriptor().getClassAndInnerClassReferences(it)
  }
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.reference.ClassReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi
import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL
import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE
import com.squareup.anvil.compiler.internal.reference.Visibility.PROTECTED
import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DECLARATION
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality.ABSTRACT
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INTERNAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PRIVATE_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PROTECTED_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PUBLIC_KEYWORD
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassBody
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.allConstructors
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import kotlin.LazyThreadSafetyMode.NONE

/**
 * Used to create a common type between [KtClassOrObject] class references and [ClassDescriptor]
 * references, to streamline parsing.
 *
 * @see toClassReference
 */
@ExperimentalAnvilApi
public sealed class ClassReference : Comparable<ClassReference>, AnnotatedReference {

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

  public val shortName: String get() = fqName.shortName().asString()
  public val packageFqName: FqName get() = classId.packageFqName

  public abstract val constructors: List<FunctionReference>
  public abstract val functions: List<FunctionReference>
  public abstract val properties: List<PropertyReference>
  public abstract val typeParameters: List<TypeParameterReference>

  protected abstract val innerClassesAndObjects: List<ClassReference>

  public abstract fun isInterface(): Boolean
  public abstract fun isAbstract(): Boolean
  public abstract fun isObject(): Boolean
  public abstract fun isCompanion(): Boolean
  public abstract fun isGenericClass(): Boolean
  public abstract fun visibility(): Visibility

  /**
   * Returns only the super class (excluding [Any]) and implemented interfaces declared directly by
   * this class.
   */
  public abstract fun directSuperClassReferences(): List<ClassReference>

  /**
   * Returns all outer classes including this class. Imagine the inner class `Outer.Middle.Inner`,
   * then the returned list would contain `[Outer, Middle, Inner]` in that order.
   */
  public abstract fun enclosingClassesWithSelf(): List<ClassReference>

  public fun enclosingClass(): ClassReference? {
    val classes = enclosingClassesWithSelf()
    val index = classes.indexOf(this)
    return if (index == 0) null else classes[index - 1]
  }

  public open fun innerClasses(): List<ClassReference> =
    innerClassesAndObjects.filterNot { it.isCompanion() }

  /**
   * @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.
   */
  internal abstract fun indexOfTypeParameter(parameterName: String): Int

  public open fun companionObjects(): List<ClassReference> =
    innerClassesAndObjects.filter { it.isCompanion() && it.enclosingClass() == this }

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

  override fun compareTo(other: ClassReference): Int {
    return fqName.asString().compareTo(other.fqName.asString())
  }

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

    override val constructors: List<FunctionReference.Psi> by lazy(NONE) {
      clazz.allConstructors.map { it.toFunctionReference(this) }
    }

    override val functions: List<FunctionReference.Psi> by lazy(NONE) {
      clazz
        .children
        .filterIsInstance<KtClassBody>()
        .flatMap { it.functions }
        .map { it.toFunctionReference(this) }
    }

    override val annotations: List<AnnotationReference.Psi> by lazy(NONE) {
      clazz.annotationEntries.map { it.toAnnotationReference(this, module) }
    }

    override val properties: List<PropertyReference.Psi> by lazy(NONE) {
      clazz
        .children
        .filterIsInstance<KtClassBody>()
        .flatMap { it.properties }
        .map { it.toPropertyReference(this) }
    }

    override val typeParameters: List<TypeParameterReference.Psi> by lazy(NONE) {
      getTypeParameterReferences()
    }

    private val directSuperClassReferences: List<ClassReference> by lazy(NONE) {
      clazz.superTypeListEntries.map { it.requireFqName(module).toClassReference(module) }
    }

    private val enclosingClassesWithSelf by lazy(NONE) {
      clazz.parents
        .filterIsInstance<KtClassOrObject>()
        .map { it.toClassReference(module) }
        .toList()
        .reversed()
        .plus(this)
    }

    override val innerClassesAndObjects: List<Psi> by lazy(NONE) {
      generateSequence(clazz.declarations.filterIsInstance<KtClassOrObject>()) { classes ->
        classes
          .flatMap { it.declarations }
          .filterIsInstance<KtClassOrObject>()
          .ifEmpty { null }
      }
        .flatten()
        .map { it.toClassReference(module) }
        .toList()
    }

    override fun isInterface(): Boolean = clazz is KtClass && clazz.isInterface()

    override fun isAbstract(): Boolean = clazz.hasModifier(ABSTRACT_KEYWORD)

    override fun isObject(): Boolean = clazz is KtObjectDeclaration

    override fun isCompanion(): Boolean = clazz is KtObjectDeclaration && clazz.isCompanion()

    override fun isGenericClass(): Boolean = clazz.typeParameterList != null

    override fun visibility(): Visibility {
      return when (val visibility = clazz.visibilityModifierTypeOrDefault()) {
        PUBLIC_KEYWORD -> PUBLIC
        INTERNAL_KEYWORD -> INTERNAL
        PROTECTED_KEYWORD -> PROTECTED
        PRIVATE_KEYWORD -> PRIVATE
        else -> throw AnvilCompilationExceptionClassReference(
          classReference = this,
          message = "Couldn't get visibility $visibility for class $fqName."
        )
      }
    }

    override fun directSuperClassReferences(): List<ClassReference> = directSuperClassReferences

    override fun enclosingClassesWithSelf(): List<Psi> = enclosingClassesWithSelf

    @Suppress("UNCHECKED_CAST")
    override fun innerClasses(): List<Psi> =
      super.innerClasses() as List<Psi>

    @Suppress("UNCHECKED_CAST")
    override fun companionObjects(): List<Psi> =
      super.companionObjects() as List<Psi>

    override fun indexOfTypeParameter(parameterName: String): Int =
      clazz.typeParameters.indexOfFirst { it.identifyingElement?.text == parameterName }
  }

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

    override val constructors: List<FunctionReference.Descriptor> by lazy(NONE) {
      clazz.constructors.map { it.toFunctionReference(this) }
    }

    override val functions: List<FunctionReference.Descriptor> by lazy(NONE) {
      clazz.unsubstitutedMemberScope
        .getContributedDescriptors(kindFilter = DescriptorKindFilter.FUNCTIONS)
        .filterIsInstance<FunctionDescriptor>()
        .filterNot { it is ConstructorDescriptor }
        .map { it.toFunctionReference(this) }
    }

    override val annotations: List<AnnotationReference.Descriptor> by lazy(NONE) {
      clazz.annotations.map { it.toAnnotationReference(this, module) }
    }

    override val properties: List<PropertyReference.Descriptor> by lazy(NONE) {
      clazz.unsubstitutedMemberScope
        .getDescriptorsFiltered(kindFilter = DescriptorKindFilter.VARIABLES)
        .filterIsInstance<PropertyDescriptor>()
        .filter {
          // Remove inherited properties that aren't overridden in this class.
          it.kind == DECLARATION
        }
        .map { it.toPropertyReference(this) }
    }

    private val directSuperClassReferences: List<ClassReference> by lazy(NONE) {
      listOfNotNull(clazz.getSuperClassNotAny())
        .plus(clazz.getSuperInterfaces())
        .map { it.toClassReference(module) }
    }

    private val enclosingClassesWithSelf by lazy(NONE) {
      clazz.parents
        .filterIsInstance<ClassDescriptor>()
        .map { it.toClassReference(module) }
        .toList()
        .reversed()
        .plus(this)
    }

    override val innerClassesAndObjects: List<Descriptor> by lazy(NONE) {
      clazz.unsubstitutedMemberScope
        .getContributedDescriptors(kindFilter = DescriptorKindFilter.CLASSIFIERS)
        .filterIsInstance<ClassDescriptor>()
        .map { it.toClassReference(module) }
    }

    override val typeParameters: List<TypeParameterReference.Descriptor> by lazy(NONE) {
      clazz.declaredTypeParameters
        .map { typeParameter ->
          typeParameter.toTypeParameterReference(this)
        }
    }

    override fun isInterface(): Boolean = clazz.kind == ClassKind.INTERFACE

    override fun isAbstract(): Boolean = clazz.modality == ABSTRACT

    override fun isObject(): Boolean = DescriptorUtils.isObject(clazz)

    override fun isCompanion(): Boolean = DescriptorUtils.isCompanionObject(clazz)

    override fun isGenericClass(): Boolean = clazz.declaredTypeParameters.isNotEmpty()

    override fun visibility(): Visibility {
      return when (val visibility = clazz.visibility) {
        DescriptorVisibilities.PUBLIC -> PUBLIC
        DescriptorVisibilities.INTERNAL -> INTERNAL
        DescriptorVisibilities.PROTECTED -> PROTECTED
        DescriptorVisibilities.PRIVATE -> PRIVATE
        else -> throw AnvilCompilationExceptionClassReference(
          classReference = this,
          message = "Couldn't get visibility $visibility for class $fqName."
        )
      }
    }

    override fun directSuperClassReferences(): List<ClassReference> = directSuperClassReferences

    override fun enclosingClassesWithSelf(): List<Descriptor> = enclosingClassesWithSelf

    @Suppress("UNCHECKED_CAST")
    override fun innerClasses(): List<Descriptor> =
      super.innerClasses() as List<Descriptor>

    @Suppress("UNCHECKED_CAST")
    override fun companionObjects(): List<Descriptor> =
      super.companionObjects() as List<Descriptor>

    override fun indexOfTypeParameter(parameterName: String): Int =
      clazz.declaredTypeParameters.indexOfFirst { it.name.asString() == parameterName }
  }
}

@ExperimentalAnvilApi
public fun ClassDescriptor.toClassReference(module: ModuleDescriptor): Descriptor =
  module.asAnvilModuleDescriptor().getClassReference(this)

@ExperimentalAnvilApi
public fun KtClassOrObject.toClassReference(module: ModuleDescriptor): Psi =
  module.asAnvilModuleDescriptor().getClassReference(this)

/**
 * Attempts to find the [KtClassOrObject] for the [FqName] first, then falls back to the
 * [ClassDescriptor] if the Psi element cannot be found. This will happen if the class for
 * [FqName] is not part of this compilation unit.
 */
@ExperimentalAnvilApi
public fun FqName.toClassReferenceOrNull(module: ModuleDescriptor): ClassReference? =
  module.asAnvilModuleDescriptor().getClassReferenceOrNull(this)

@ExperimentalAnvilApi
public fun FqName.toClassReference(module: ModuleDescriptor): ClassReference {
  return toClassReferenceOrNull(module)
    ?: throw AnvilCompilationException("Couldn't resolve ClassReference for $this.")
}

@ExperimentalAnvilApi
public fun ClassReference.generateClassName(
  separator: String = "_",
  suffix: String = ""
): ClassId {
  val className = enclosingClassesWithSelf().joinToString(separator = separator) { it.shortName }
  return ClassId(packageFqName, FqName(className + suffix), false)
}

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

@ExperimentalAnvilApi
public fun ClassReference.asTypeName(): TypeName {
  return if (!isGenericClass()) {
    asClassName()
  } else {
    asClassName().parameterizedBy(typeParameters.map { it.typeVariableName })
  }
}

/**
 * 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(
  includeSelf: Boolean = false
): Sequence<ClassReference> {
  return generateSequence(listOf(this)) { superTypes ->
    superTypes
      .flatMap { classRef -> classRef.directSuperClassReferences() }
      .takeIf { it.isNotEmpty() }
  }
    .drop(if (includeSelf) 0 else 1)
    .flatten()
    .distinct()
}

@ExperimentalAnvilApi
@Suppress("FunctionName")
public fun AnvilCompilationExceptionClassReference(
  classReference: ClassReference,
  message: String,
  cause: Throwable? = null
): AnvilCompilationException = when (classReference) {
  is Psi -> AnvilCompilationException(
    element = classReference.clazz.identifyingElement ?: classReference.clazz,
    message = message,
    cause = cause
  )
  is Descriptor -> AnvilCompilationException(
    classDescriptor = classReference.clazz,
    message = message,
    cause = cause
  )
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.reference.FunctionReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.FunctionReference.Psi
import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL
import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE
import com.squareup.anvil.compiler.internal.reference.Visibility.PROTECTED
import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC
import com.squareup.anvil.compiler.internal.requireFqName
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality.ABSTRACT
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PUBLIC_KEYWORD
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import kotlin.LazyThreadSafetyMode.NONE

/**
 * Used to create a common type between [KtNamedFunction] class references and
 * [FunctionDescriptor] references, to streamline parsing.
 */
@ExperimentalAnvilApi
public sealed class FunctionReference : AnnotatedReference {

  public abstract val fqName: FqName
  public abstract val declaringClass: ClassReference
  public val name: String get() = fqName.shortName().asString()

  public val module: AnvilModuleDescriptor get() = declaringClass.module

  public abstract val parameters: List<ParameterReference>

  protected abstract val returnType: TypeReference?

  public fun returnTypeOrNull(): TypeReference? = returnType
  public fun returnType(): TypeReference = returnType
    ?: throw AnvilCompilationExceptionFunctionReference(
      functionReference = this,
      message = "Unable to get the return type for function $fqName."
    )

  public abstract fun isAbstract(): Boolean
  public abstract fun isConstructor(): Boolean
  public abstract fun visibility(): Visibility

  public fun resolveGenericReturnTypeOrNull(
    implementingClass: ClassReference
  ): ClassReference? {
    return when (implementingClass) {
      // For Psi classes the implementation is different depending on whether this function is a
      // Psi or Descriptor function, so let each concrete class implement it.
      is ClassReference.Psi ->
        returnType
          ?.resolveGenericTypeOrNull(implementingClass)
          ?.asClassReferenceOrNull()
      is ClassReference.Descriptor -> {
        // If the implementing class is a Descriptor, then the function keeps track of all
        // functions it's overriding in the hierarchy. We use that information to find the function
        // in the implementing that implements THIS function reference for which we're trying to
        // resolve the generic return type. Once we found the function in the implementing class,
        // we can take its return, because that's what we're looking for.
        //
        // The implementation for Psi is completely different. Imagine this class:
        //
        //   class Abc : Def<String>
        //
        // The Descriptor APIs don't allow us to query the generic type <String>, but we can get
        // the type from the actual function as described above. Psi can't do this and has to walk
        // the hierarchy of classes to make the connections.
        val implementingFunction = implementingClass.functions
          .singleOrNull { function ->
            function.overriddenFunctions.any { it.fqNameSafe == fqName }
          }

        implementingFunction?.returnTypeOrNull()?.asClassReference()
      }
    }
  }

  public fun resolveGenericReturnType(implementingClass: ClassReference): ClassReference =
    resolveGenericReturnTypeOrNull(implementingClass)
      ?: throw AnvilCompilationExceptionFunctionReference(
        functionReference = this,
        message = "Unable to resolve return type for function $fqName with the implementing " +
          "class ${implementingClass.fqName}."
      )

  override fun toString(): String = "$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()
  }

  public class Psi internal constructor(
    public val function: KtFunction,
    override val declaringClass: ClassReference.Psi,
    override val fqName: FqName
  ) : FunctionReference() {

    override val annotations: List<AnnotationReference.Psi> by lazy(NONE) {
      function.annotationEntries.map {
        it.toAnnotationReference(declaringClass = null, module)
      }
    }

    override val returnType: TypeReference.Psi? by lazy(NONE) {
      function.typeReference?.toTypeReference(declaringClass)
    }

    override val parameters: List<ParameterReference.Psi> by lazy(NONE) {
      function.valueParameters.map { it.toParameterReference(this) }
    }

    override fun isAbstract(): Boolean =
      function.hasModifier(ABSTRACT_KEYWORD) ||
        (declaringClass.isInterface() && !function.hasBody())

    override fun isConstructor(): Boolean = function is KtConstructor<*>

    override fun visibility(): Visibility {
      return when (val visibility = function.visibilityModifierTypeOrDefault()) {
        PUBLIC_KEYWORD -> PUBLIC
        KtTokens.INTERNAL_KEYWORD -> INTERNAL
        KtTokens.PROTECTED_KEYWORD -> PROTECTED
        KtTokens.PRIVATE_KEYWORD -> PRIVATE
        else -> throw AnvilCompilationExceptionClassReference(
          classReference = declaringClass,
          message = "Couldn't get visibility $visibility for function $fqName."
        )
      }
    }
  }

  public class Descriptor internal constructor(
    public val function: FunctionDescriptor,
    override val declaringClass: ClassReference.Descriptor,
    override val fqName: FqName = function.fqNameSafe
  ) : FunctionReference() {

    override val annotations: List<AnnotationReference.Descriptor> by lazy(NONE) {
      function.annotations.map {
        it.toAnnotationReference(declaringClass = null, module)
      }
    }

    override val parameters: List<ParameterReference.Descriptor> by lazy(NONE) {
      function.valueParameters.map { it.toParameterReference(this) }
    }

    override val returnType: TypeReference.Descriptor? by lazy(NONE) {
      function.returnType?.toTypeReference(declaringClass)
    }

    internal val overriddenFunctions by lazy(NONE) {
      generateSequence(
        function.overriddenDescriptors
      ) { overriddenFunctions ->
        overriddenFunctions
          .flatMap { it.overriddenDescriptors }
          .takeIf { it.isNotEmpty() }
      }.flatten().toList()
    }

    override fun isAbstract(): Boolean = function.modality == ABSTRACT

    override fun isConstructor(): Boolean = function is ClassConstructorDescriptor

    override fun visibility(): Visibility {
      return when (val visibility = function.visibility) {
        DescriptorVisibilities.PUBLIC -> PUBLIC
        DescriptorVisibilities.INTERNAL -> INTERNAL
        DescriptorVisibilities.PROTECTED -> PROTECTED
        DescriptorVisibilities.PRIVATE -> PRIVATE
        else -> throw AnvilCompilationExceptionClassReference(
          classReference = declaringClass,
          message = "Couldn't get visibility $visibility for function $fqName."
        )
      }
    }
  }
}

@ExperimentalAnvilApi
public fun KtFunction.toFunctionReference(
  declaringClass: ClassReference.Psi
): Psi {
  val fqName = if (this is KtConstructor<*>) {
    declaringClass.fqName.child(Name.identifier("<init>"))
  } else {
    requireFqName()
  }

  return Psi(this, declaringClass, fqName)
}

@ExperimentalAnvilApi
public fun FunctionDescriptor.toFunctionReference(
  declaringClass: ClassReference.Descriptor,
): Descriptor {
  return Descriptor(this, declaringClass)
}

@ExperimentalAnvilApi
@Suppress("FunctionName")
public fun AnvilCompilationExceptionFunctionReference(
  functionReference: FunctionReference,
  message: String,
  cause: Throwable? = null
): AnvilCompilationException = when (functionReference) {
  is Psi -> AnvilCompilationException(
    element = functionReference.function,
    message = message,
    cause = cause
  )
  is Descriptor -> AnvilCompilationException(
    functionDescriptor = functionReference.function,
    message = message,
    cause = cause
  )
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.reference.ParameterReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.ParameterReference.Psi
import com.squareup.kotlinpoet.TypeName
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import kotlin.LazyThreadSafetyMode.NONE

@ExperimentalAnvilApi
public sealed class ParameterReference : AnnotatedReference {

  public abstract val name: String
  public abstract val declaringFunction: FunctionReference
  public val module: AnvilModuleDescriptor get() = declaringFunction.module

  protected abstract val type: TypeReference?

  /**
   * The type can be null for generic type parameters like `T`. In this case try to resolve the
   * type with [TypeReference.resolveGenericTypeOrNull].
   */
  public fun typeOrNull(): TypeReference? = type
  public fun type(): TypeReference = type
    ?: throw AnvilCompilationExceptionParameterReference(
      parameterReference = this,
      message = "Unable to get type for the parameter with name $name of " +
        "function ${declaringFunction.fqName}"
    )

  public fun resolveTypeNameOrNull(
    implementingClass: ClassReference
  ): TypeName? {
    return when (implementingClass) {
      is ClassReference.Psi -> {
        type?.resolveGenericTypeNameOrNull(implementingClass)
      }
      is ClassReference.Descriptor -> {
        // This implementation is very similar to the return type implementation in
        // FunctionReference.

        val implementingFunction = implementingClass.functions
          .singleOrNull { function ->
            function.overriddenFunctions.any { it.fqNameSafe == declaringFunction.fqName }
          }

        implementingFunction?.parameters
          ?.singleOrNull { it.name == name }
          ?.type()
          ?.asTypeNameOrNull()
      }
    }
  }

  public fun resolveTypeName(implementingClass: ClassReference): TypeName =
    resolveTypeNameOrNull(implementingClass)
      ?: throw AnvilCompilationExceptionParameterReference(
        message = "Unable to resolve type name for parameter with name $name with the " +
          "implementing class ${implementingClass.fqName}.",
        parameterReference = this
      )

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

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

    if (name != other.name) return false
    if (declaringFunction != other.declaringFunction) return false

    return true
  }

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

  public class Psi(
    public val parameter: KtParameter,
    override val declaringFunction: FunctionReference.Psi
  ) : ParameterReference() {
    override val name: String = parameter.nameAsSafeName.asString()

    override val annotations: List<AnnotationReference.Psi> by lazy(NONE) {
      parameter.annotationEntries.map {
        it.toAnnotationReference(declaringClass = null, module)
      }
    }

    override val type: TypeReference.Psi? by lazy(NONE) {
      parameter.typeReference?.toTypeReference(declaringFunction.declaringClass)
    }
  }

  public class Descriptor(
    public val parameter: ValueParameterDescriptor,
    override val declaringFunction: FunctionReference.Descriptor
  ) : ParameterReference() {
    override val name: String = parameter.name.asString()

    override val annotations: List<AnnotationReference.Descriptor> by lazy(NONE) {
      parameter.annotations.map {
        it.toAnnotationReference(declaringClass = null, module)
      }
    }

    override val type: TypeReference.Descriptor? by lazy(NONE) {
      parameter.type.toTypeReference(declaringFunction.declaringClass)
    }
  }
}

@ExperimentalAnvilApi
public fun KtParameter.toParameterReference(
  declaringFunction: FunctionReference.Psi
): Psi {
  return Psi(this, declaringFunction)
}

@ExperimentalAnvilApi
public fun ValueParameterDescriptor.toParameterReference(
  declaringFunction: FunctionReference.Descriptor
): Descriptor {
  return Descriptor(this, declaringFunction)
}

@ExperimentalAnvilApi
@Suppress("FunctionName")
public fun AnvilCompilationExceptionParameterReference(
  parameterReference: ParameterReference,
  message: String,
  cause: Throwable? = null
): AnvilCompilationException = when (parameterReference) {
  is Psi -> AnvilCompilationException(
    element = parameterReference.parameter,
    message = message,
    cause = cause
  )
  is Descriptor -> AnvilCompilationException(
    parameterDescriptor = parameterReference.parameter,
    message = message,
    cause = cause
  )
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.reference.PropertyReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.PropertyReference.Psi
import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL
import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE
import com.squareup.anvil.compiler.internal.reference.Visibility.PROTECTED
import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.kotlinpoet.MemberName
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget.PROPERTY_GETTER
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget.PROPERTY_SETTER
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import kotlin.LazyThreadSafetyMode.NONE

@ExperimentalAnvilApi
public sealed class PropertyReference : AnnotatedReference {

  public abstract val fqName: FqName
  public abstract val declaringClass: ClassReference

  public val module: AnvilModuleDescriptor get() = declaringClass.module

  public abstract val name: String
  public val memberName: MemberName get() = MemberName(declaringClass.asClassName(), name)

  protected abstract val type: TypeReference?

  public abstract val setterAnnotations: List<AnnotationReference>
  public abstract val getterAnnotations: List<AnnotationReference>

  public abstract fun visibility(): Visibility

  public fun typeOrNull(): TypeReference? = type
  public fun type(): TypeReference = type
    ?: throw AnvilCompilationExceptionPropertyReference(
      propertyReference = this,
      message = "Unable to get type for property $fqName."
    )

  override fun toString(): String = "$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()
  }

  public class Psi internal constructor(
    public val property: KtProperty,
    override val declaringClass: ClassReference.Psi,
    override val fqName: FqName = property.requireFqName(),
    override val name: String = fqName.shortName().asString()
  ) : PropertyReference() {

    override val annotations: List<AnnotationReference.Psi> by lazy(NONE) {
      property.annotationEntries
        .filter {
          val annotationUseSiteTarget = it.useSiteTarget?.getAnnotationUseSiteTarget()
          annotationUseSiteTarget != PROPERTY_SETTER && annotationUseSiteTarget != PROPERTY_GETTER
        }
        .map { it.toAnnotationReference(declaringClass, module) }
        .plus(setterAnnotations)
        .plus(getterAnnotations)
    }

    override val type: TypeReference? by lazy(NONE) {
      property.typeReference?.toTypeReference(declaringClass)
    }

    override val setterAnnotations: List<AnnotationReference.Psi> by lazy(NONE) {
      property.annotationEntries
        .filter { it.useSiteTarget?.getAnnotationUseSiteTarget() == PROPERTY_SETTER }
        .plus(property.setter?.annotationEntries ?: emptyList())
        .map { it.toAnnotationReference(declaringClass, module) }
    }

    override val getterAnnotations: List<AnnotationReference.Psi> by lazy(NONE) {
      property.annotationEntries
        .filter { it.useSiteTarget?.getAnnotationUseSiteTarget() == PROPERTY_GETTER }
        .plus(property.getter?.annotationEntries ?: emptyList())
        .map { it.toAnnotationReference(declaringClass, module) }
    }

    override fun visibility(): Visibility {
      return when (val visibility = property.visibilityModifierTypeOrDefault()) {
        KtTokens.PUBLIC_KEYWORD -> PUBLIC
        KtTokens.INTERNAL_KEYWORD -> INTERNAL
        KtTokens.PROTECTED_KEYWORD -> PROTECTED
        KtTokens.PRIVATE_KEYWORD -> PRIVATE
        else -> throw AnvilCompilationExceptionClassReference(
          classReference = declaringClass,
          message = "Couldn't get visibility $visibility for property $fqName."
        )
      }
    }
  }

  public class Descriptor internal constructor(
    public val property: PropertyDescriptor,
    override val declaringClass: ClassReference.Descriptor,
    override val fqName: FqName = property.fqNameSafe,
    override val name: String = fqName.shortName().asString()
  ) : PropertyReference() {

    override val annotations: List<AnnotationReference.Descriptor> by lazy(NONE) {
      property.annotations
        .plus(property.backingField?.annotations ?: emptyList())
        .map { it.toAnnotationReference(declaringClass, module) }
        .plus(setterAnnotations)
        .plus(getterAnnotations)
    }

    override val setterAnnotations: List<AnnotationReference.Descriptor> by lazy(NONE) {
      property.setter
        ?.annotations
        ?.map { it.toAnnotationReference(declaringClass, module) }
        .orEmpty()
    }

    override val getterAnnotations: List<AnnotationReference.Descriptor> by lazy(NONE) {
      property.getter
        ?.annotations
        ?.map { it.toAnnotationReference(declaringClass, module) }
        .orEmpty()
    }

    override val type: TypeReference by lazy(NONE) {
      property.type.toTypeReference(declaringClass)
    }

    override fun visibility(): Visibility {
      return when (val visibility = property.visibility) {
        DescriptorVisibilities.PUBLIC -> PUBLIC
        DescriptorVisibilities.INTERNAL -> INTERNAL
        DescriptorVisibilities.PROTECTED -> PROTECTED
        DescriptorVisibilities.PRIVATE -> PRIVATE
        else -> throw AnvilCompilationExceptionClassReference(
          classReference = declaringClass,
          message = "Couldn't get visibility $visibility for property $fqName."
        )
      }
    }
  }
}

@ExperimentalAnvilApi
public fun KtProperty.toPropertyReference(
  declaringClass: ClassReference.Psi
): Psi = Psi(this, declaringClass)

@ExperimentalAnvilApi
public fun PropertyDescriptor.toPropertyReference(
  declaringClass: ClassReference.Descriptor
): Descriptor = Descriptor(this, declaringClass)

@ExperimentalAnvilApi
@Suppress("FunctionName")
public fun AnvilCompilationExceptionPropertyReference(
  propertyReference: PropertyReference,
  message: String,
  cause: Throwable? = null
): AnvilCompilationException = when (propertyReference) {
  is Psi -> AnvilCompilationException(
    element = propertyReference.property,
    message = message,
    cause = cause
  )
  is Descriptor -> AnvilCompilationException(
    propertyDescriptor = propertyReference.property,
    message = message,
    cause = cause
  )
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.internal.fqNameOrNull
import com.squareup.anvil.compiler.internal.reference.TypeParameterReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.TypeParameterReference.Psi
import com.squareup.kotlinpoet.TypeVariableName
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import kotlin.LazyThreadSafetyMode.NONE

@ExperimentalAnvilApi
public sealed class TypeParameterReference {

  public abstract val name: String
  public abstract val upperBounds: List<TypeReference>
  public abstract val declaringClass: ClassReference

  public val typeVariableName: TypeVariableName by lazy(NONE) {
    TypeVariableName(name, upperBounds.map { it.asTypeName() })
  }

  public class Psi internal constructor(
    override val name: String,
    override val upperBounds: List<TypeReference.Psi>,
    override val declaringClass: ClassReference.Psi
  ) : TypeParameterReference()

  public class Descriptor internal constructor(
    override val name: String,
    override val upperBounds: List<TypeReference.Descriptor>,
    override val declaringClass: ClassReference.Descriptor
  ) : TypeParameterReference()

  override fun toString(): String {
    return "${this::class.qualifiedName}(name='$name', declaringClass=$declaringClass)"
  }
}

@ExperimentalAnvilApi
public fun ClassReference.Psi.getTypeParameterReferences(): List<Psi> {
  // 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 = clazz.typeParameterList
    ?.parameters
    ?.filter { it.fqNameOrNull(module) == null }
    ?.associateTo(mutableMapOf()) { parameter ->
      val variableName = parameter.nameAsSafeName.asString()
      val extendsBound = parameter.extendsBound?.toTypeReference(this)

      variableName to listOfNotNull(extendsBound).toMutableList()
    } ?: mutableMapOf()

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

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

  return boundsByVariableName.map { (name, types) ->
    Psi(name, types, this)
  }
}

@ExperimentalAnvilApi
public fun TypeParameterDescriptor.toTypeParameterReference(
  declaringClass: ClassReference.Descriptor
): Descriptor {
  return Descriptor(
    name = name.asString(),
    upperBounds = upperBounds.map { it.toTypeReference(declaringClass) },
    declaringClass = declaringClass
  )
}

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.classDescriptor
import com.squareup.anvil.compiler.internal.classDescriptorOrNull
import com.squareup.anvil.compiler.internal.fqNameOrNull
import com.squareup.anvil.compiler.internal.reference.TypeReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.TypeReference.Psi
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.ParameterSpec
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 org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFunctionType
import org.jetbrains.kotlin.psi.KtNullableType
import org.jetbrains.kotlin.psi.KtProjectionKind
import org.jetbrains.kotlin.psi.KtSuperTypeListEntry
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.KtUserType
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.types.DefinitelyNotNullType
import org.jetbrains.kotlin.types.FlexibleType
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.isNullable
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import kotlin.LazyThreadSafetyMode.NONE

@ExperimentalAnvilApi
public sealed class TypeReference {

  public abstract val declaringClass: ClassReference

  /**
   * If the type refers to class and is not a generic type, then the returned reference
   * is non-null.
   */
  protected abstract val classReference: ClassReference?

  protected abstract val typeNameOrNull: TypeName?
  protected abstract val typeName: TypeName

  /**
   * For `Lazy<String>` this will return `String`.
   */
  public abstract val unwrappedFirstType: TypeReference

  public val module: AnvilModuleDescriptor get() = declaringClass.module

  public fun asClassReferenceOrNull(): ClassReference? = classReference
  public fun asClassReference(): ClassReference = classReference
    ?: throw AnvilCompilationExceptionTypReference(
      typeReference = this,
      message = "Unable to convert a type reference to a class reference."
    )

  public fun asTypeNameOrNull(): TypeName? = typeNameOrNull
  public fun asTypeName(): TypeName = typeName

  // Keep this internal, because we need help from other classes for the descriptor
  // implementation, e.g. FunctionReference resolves the return type or ParameterReference.
  internal abstract fun resolveGenericTypeOrNull(
    implementingClass: ClassReference.Psi
  ): TypeReference?

  internal fun resolveGenericTypeNameOrNull(
    implementingClass: ClassReference.Psi
  ): TypeName? {
    asClassReferenceOrNull()?.let {
      // Then it's a normal type and not a generic type.
      return asTypeName()
    }

    return resolveGenericTypeOrNull(implementingClass)
      ?.asTypeNameOrNull()
      ?: asTypeNameOrNull()
  }

  public fun isGenericType(): Boolean = asClassReferenceOrNull()?.isGenericClass() ?: true
  public abstract fun isFunctionType(): Boolean
  public abstract fun isNullable(): Boolean

  override fun toString(): String {
    return "${this::class.qualifiedName}(declaringClass=$declaringClass, " +
      "classReference=$classReference)"
  }

  public class Psi internal constructor(
    public val type: KtTypeReference,
    override val declaringClass: ClassReference.Psi
  ) : TypeReference() {
    override val classReference: ClassReference? by lazy(NONE) {
      resolveGenericTypeOrNull(declaringClass)
        ?.type
        ?.fqNameOrNull(module)
        ?.toClassReference(module)
    }

    override val typeName: TypeName by lazy {
      type.requireTypeName().lambdaFix()
    }

    override val typeNameOrNull: TypeName?
      get() = try {
        typeName
      } catch (e: AnvilCompilationException) {
        // Usually the method should return a nullable value, but throwing an error gives us
        // better hints which element exactly fails to be converted to a TypeName while debugging.
        // So it's better to keep that mechanism in place and catch the exception to move on.
        null
      }

    override val unwrappedFirstType: Psi by lazy(NONE) {
      type.typeElement!!.children
        .filterIsInstance<KtTypeArgumentList>()
        .single()
        .children
        .filterIsInstance<KtTypeProjection>()
        .first()
        .children
        .filterIsInstance<KtTypeReference>()
        .single()
        .toTypeReference(declaringClass)
    }

    override fun resolveGenericTypeOrNull(implementingClass: ClassReference.Psi): Psi? {
      return resolveTypeReference(implementingClass)
    }

    /**
     * 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.
     *
     * @param implementingClass The class which actually references the type. In the above
     * example, this would be `ServiceFactory`.
     */
    internal fun resolveTypeReference(
      implementingClass: ClassReference.Psi,
    ): Psi? {
      // If the element isn't a type variable name like `T`, it can be resolved through imports.
      type.typeElement?.fqNameOrNull(module)
        ?.let { return this }

      return resolveGenericTypeReference(implementingClass, declaringClass, type.text)
    }

    override fun isFunctionType(): Boolean = type.typeElement is KtFunctionType

    override fun isNullable(): Boolean = type.typeElement is KtNullableType

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

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

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

      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()
                      .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(),
              parameters = parameterList
                ?.parameters
                ?.map { parameter ->
                  val parameterReference = parameter.typeReference ?: parameter.fail()
                  ParameterSpec.unnamed(parameterReference.requireTypeName())
                }
                ?: emptyList(),
              returnType = (returnTypeReference ?: fail())
                .requireTypeName()
            )
          is KtNullableType -> {
            (innerType ?: fail()).requireTypeName().copy(nullable = true)
          }
          else -> fail()
        }
      }

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

  public class Descriptor internal constructor(
    public val type: KotlinType,
    override val declaringClass: ClassReference.Descriptor
  ) : TypeReference() {
    override val classReference: ClassReference? by lazy(NONE) {
      type.classDescriptorOrNull()?.toClassReference(module)
    }

    override val typeNameOrNull: TypeName? by lazy(NONE) {
      type.asTypeNameOrNull()?.lambdaFix()
    }

    override val typeName: TypeName
      get() = typeNameOrNull ?: throw AnvilCompilationExceptionTypReference(
        typeReference = this,
        message = "Unable to convert the Kotlin type $type to a type name for declaring class " +
          "${declaringClass.fqName}."
      )

    override val unwrappedFirstType: Descriptor by lazy(NONE) {
      type.arguments
        .first()
        .type
        .toTypeReference(declaringClass)
    }

    override fun resolveGenericTypeOrNull(implementingClass: ClassReference.Psi): TypeReference? {
      return resolveGenericKotlinTypeOrNull(implementingClass)
    }

    /**
     * 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`
     *
     * @param implementingClass The class which actually references the type. In the above example,
     * this would be `SomeFactory`.
     */
    private fun resolveGenericKotlinTypeOrNull(
      implementingClass: ClassReference.Psi
    ): Psi? {
      val parameterKotlinType = when (type) {
        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 (type.lowerBound.isMarkedNullable) {
            type.upperBound
          } else {
            type.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
          type.original
        }
        else -> type
      }

      return resolveGenericTypeReference(
        implementingClass = implementingClass,
        declaringClass = declaringClass,
        parameterName = parameterKotlinType.toString()
      )
    }

    override fun isFunctionType(): Boolean = type.isFunctionType

    override fun isNullable(): Boolean = type.isNullable()

    private fun KotlinType.asTypeNameOrNull(): TypeName? {
      if (isTypeParameter()) return TypeVariableName(toString())

      val className = classDescriptor().asClassName()
      if (arguments.isEmpty()) return className.copy(nullable = isMarkedNullable)

      val argumentTypeNames = arguments.map { typeProjection ->
        if (typeProjection.isStarProjection) {
          STAR
        } else {
          val typeName = typeProjection.type.asTypeNameOrNull() ?: return null
          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 KtTypeReference.toTypeReference(declaringClass: ClassReference.Psi): Psi {
  return Psi(this, declaringClass)
}

@ExperimentalAnvilApi
public fun KotlinType.toTypeReference(declaringClass: ClassReference.Descriptor): Descriptor {
  return Descriptor(this, declaringClass)
}

@ExperimentalAnvilApi
@Suppress("FunctionName")
public fun AnvilCompilationExceptionTypReference(
  typeReference: TypeReference,
  message: String,
  cause: Throwable? = null
): AnvilCompilationException = when (typeReference) {
  is Psi -> AnvilCompilationException(
    element = typeReference.type,
    message = message,
    cause = cause
  )
  is Descriptor -> {
    AnvilCompilationException(
      classDescriptor = typeReference.declaringClass.clazz,
      message = message + " Hint: ${typeReference.type}",
      cause = cause
    )
  }
}

private fun resolveGenericTypeReference(
  implementingClass: ClassReference.Psi,
  declaringClass: ClassReference,
  parameterName: String
): Psi? {
  // 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 (implementingClass == declaringClass) 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 = implementingClass.superTypeListEntryOrNull(declaringClass.fqName)
    ?.typeReference
    ?.typeElement
    ?.getChildOfType<KtTypeArgumentList>()
    ?.arguments
    ?.get(indexOfType)
    ?.typeReference

  return resolvedTypeReference
    ?.containingClass()
    ?.toClassReference(implementingClass.module)
    ?.let { resolvedDeclaringClass ->
      resolvedTypeReference
        .toTypeReference(resolvedDeclaringClass)
        .resolveTypeReference(implementingClass)
    }
}

/**
 * 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`.
 */
private fun ClassReference.superTypeListEntryOrNull(
  superTypeFqName: FqName
): KtSuperTypeListEntry? {
  return allSuperTypeClassReferences(includeSelf = true)
    .filterIsInstance<ClassReference.Psi>()
    .firstNotNullOfOrNull { classReference ->
      classReference.clazz
        .superTypeListEntries
        .firstOrNull { it.requireFqName(module) == superTypeFqName }
    }
}

/**
 * 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>`
 */
private fun TypeName.lambdaFix(): 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
  val lambdaTypeName = this as? LambdaTypeName ?: return this

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

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

package com.squareup.anvil.compiler.internal.reference

import com.squareup.anvil.annotations.ExperimentalAnvilApi

@ExperimentalAnvilApi
public enum class Visibility {
  PUBLIC,
  INTERNAL,
  PROTECTED,
  PRIVATE
}

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

import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.AnvilCommandLineProcessor
import com.squareup.anvil.compiler.AnvilComponentRegistrar
import com.squareup.anvil.compiler.api.CodeGenerator
import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.KotlinCompilation.Result
import com.tschuchort.compiletesting.PluginOption
import com.tschuchort.compiletesting.SourceFile
import com.tschuchort.compiletesting.addPreviousResultToClasspath
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.nio.file.Files

/**
 * 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

  /** 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,
    codeGenerators: List<CodeGenerator> = emptyList(),
    enableAnvil: Boolean = true,
  ) = apply {
    checkNotCompiled()
    check(!anvilConfigured) { "Anvil should not be configured twice." }

    anvilConfigured = true

    if (!enableAnvil) return@apply

    kotlinCompilation.apply {
      compilerPlugins = listOf(
        AnvilComponentRegistrar().also { it.addCodeGenerators(codeGenerators) }
      )
      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) {
        // TODO: remove this property with Kotlin 1.6.20.
        kotlincArguments += if (KotlinVersion.CURRENT > KotlinVersion(1, 6, 10)) {
          listOf(
            "-opt-in=kotlin.RequiresOptIn",
            "-opt-in=com.squareup.anvil.annotations.ExperimentalAnvilApi"
          )
        } else {
          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.addPreviousResultToClasspath(result)
  }

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

  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
 * [AnvilCompilation.configureAnvil]. 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,
  codeGenerators: List<CodeGenerator> = emptyList(),
  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,
      codeGenerators = codeGenerators,
    )
    .useIR(useIR)
    .compile(*sources)
    .also(block)
}

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 com.squareup.anvil.annotations.compat.MergeModules
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()
    }
    MergeModules::class -> object : AnyDaggerComponent {
      override val modules: List<KClass<*>> = daggerModule.includes.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
  }
}

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

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.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

private var counter = 0

fun simpleCodeGenerator(
  mapper: CodeGenerator.(clazz: ClassReference.Psi) -> String?
): CodeGenerator = object : CodeGenerator {
  override fun isApplicable(context: AnvilContext): Boolean = true

  override fun generateCode(
    codeGenDir: File,
    module: ModuleDescriptor,
    projectFiles: Collection<KtFile>
  ): Collection<GeneratedFile> {
    return projectFiles
      .classAndInnerClassReferences(module)
      .mapNotNull {
        mapper.invoke(this, it)
      }
      .map { content ->
        val packageName = content.lines()
          .map { it.trim() }
          .firstNotNullOfOrNull { line ->
            line
              .takeIf { it.startsWith("package ") }
              ?.substringAfter("package ")
          }
          ?: "tempPackage"

        val fileName = content.lines()
          .map { it.trim() }
          .firstNotNullOfOrNull { line ->
            // Try finding the class name.
            line
              .takeIf { it.startsWith("class ") || it.contains(" class ") }
              ?.substringAfter("class ")
              ?.trim()
              ?.substringBefore(" ")
              ?: line
                // Check for interfaces, too.
                .takeIf { it.startsWith("interface ") || it.contains(" interface ") }
                ?.substringAfter("interface ")
                ?.trim()
                ?.substringBefore(" ")
          }
          ?: "NewFile${counter++}"

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

@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.internal.capitalize
import dagger.Component
import dagger.Module
import dagger.Subcomponent
import kotlin.reflect.KClass

@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.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.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
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
      .classAndInnerClassReferences(module)
      .firstOrNull {
        it.isAnnotatedWith(mergeComponentFqName) ||
          it.isAnnotatedWith(mergeSubcomponentFqName) ||
          it.isAnnotatedWith(mergeInterfacesFqName) ||
          it.isAnnotatedWith(mergeModulesFqName) ||
          it.isAnnotatedWith(contributesToFqName) ||
          it.isAnnotatedWith(contributesSubcomponentFqName) ||
          it.isAnnotatedWith(contributesBindingFqName)
      }

    if (clazz != null) {
      throw AnvilCompilationExceptionClassReference(
        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.",
        classReference = clazz
      )
    }
  }
}

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.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
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
      .classAndInnerClassReferences(module)
      .firstOrNull {
        it.isAnnotatedWith(mergeComponentFqName) ||
          it.isAnnotatedWith(mergeSubcomponentFqName) ||
          it.isAnnotatedWith(mergeInterfacesFqName) ||
          it.isAnnotatedWith(mergeModulesFqName)
      }

    if (clazz != null) {
      throw AnvilCompilationExceptionClassReference(
        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.",
        classReference = 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.toAssistedParameterKey
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionFunctionReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.FunctionReference
import com.squareup.anvil.compiler.internal.reference.ParameterReference
import com.squareup.anvil.compiler.internal.reference.Visibility
import com.squareup.anvil.compiler.internal.reference.allSuperTypeClassReferences
import com.squareup.anvil.compiler.internal.reference.argumentAt
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
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.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.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import javax.inject.Provider

@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
      .classAndInnerClassReferences(module)
      .filter { it.isAnnotatedWith(assistedFactoryFqName) }
      .forEach { clazz ->
        generateFactoryClass(codeGenDir, clazz)
      }
  }

  private fun generateFactoryClass(
    codeGenDir: File,
    clazz: ClassReference.Psi
  ): GeneratedFile {
    val packageName = clazz.packageFqName.safePackageString()
    val delegateFactoryName = "delegateFactory"

    val function = clazz.requireSingleAbstractFunction()

    val returnType = try {
      function.function.resolveGenericReturnType(clazz)
    } catch (e: AnvilCompilationException) {
      // Catch the exception and throw the same error that Dagger would.
      throw AnvilCompilationExceptionFunctionReference(
        message = "Invalid return type: ${clazz.fqName}. An assisted factory's " +
          "abstract method must return a type with an @AssistedInject-annotated constructor.",
        functionReference = function.function,
        cause = e
      )
    }

    // The return type of the function must have an @AssistedInject constructor.
    val constructor = returnType
      .constructors
      .singleOrNull { it.isAnnotatedWith(assistedInjectFqName) }
      ?: throw AnvilCompilationExceptionClassReference(
        message = "Invalid return type: ${returnType.fqName}. An assisted factory's abstract " +
          "method must return a type with an @AssistedInject-annotated constructor.",
        classReference = clazz
      )

    val functionParameters = function.parameterKeys
    val assistedParameters = constructor.parameters.filter { parameter ->
      parameter.annotations.any { it.fqName == assistedFqName }
    }

    // Check that the parameters of the function match the @Assisted parameters of the constructor.
    if (assistedParameters.size != functionParameters.size) {
      throw AnvilCompilationExceptionClassReference(
        message = "The parameters in the factory method must match the @Assisted parameters in " +
          "${returnType.fqName}.",
        classReference = clazz
      )
    }

    // Compute for each parameter its key.
    val functionParameterKeys = function.parameterKeys
    val assistedParameterKeys = assistedParameters.map { it.toAssistedParameterKey(clazz) }

    // 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 AnvilCompilationExceptionClassReference(
        message = buildString {
          append("@AssistedFactory method has duplicate @Assisted types: ")
          if (key.identifier.isNotEmpty()) {
            append("@Assisted(\"${key.identifier}\") ")
          }
          append(key.typeName)
        },
        classReference = 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 AnvilCompilationExceptionClassReference(
        message = "The parameters in the factory method must match the @Assisted parameters in " +
          "${returnType.fqName}.",
        classReference = clazz
      )
    }

    val typeParameters = clazz.typeParameters

    val generatedFactoryTypeName = returnType.generateClassName(suffix = "_Factory")
      .asClassName()
      .optionallyParameterizedBy(typeParameters)

    val baseFactoryTypeName = clazz.asClassName().optionallyParameterizedBy(typeParameters)

    val returnTypeName = returnType.asClassName().optionallyParameterizedBy(typeParameters)

    val implClassName = clazz.generateClassName(suffix = "_Impl").asClassName()
    val implParameterizedTypeName = implClassName.optionallyParameterizedBy(typeParameters)
    val functionName = function.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.typeVariableName) }

          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.forEach { parameter ->
                addParameter(parameter.first.name, 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[functionIndex].first.name
              }

              addStatement("return $delegateFactoryName.get($argumentList)")
            }
            .build()
        )
        .apply {
          TypeSpec.companionObjectBuilder()
            .addFunction(
              FunSpec.builder("create")
                .jvmStatic()
                .addTypeVariables(typeParameters.map { it.typeVariableName })
                .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)
  }

  private fun ClassReference.Psi.requireSingleAbstractFunction(): AssistedFactoryFunction {
    // `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 = allSuperTypeClassReferences(includeSelf = true)
      .distinctBy { it.fqName }
      .flatMap { clazz ->
        clazz.functions
          .filter {
            it.isAbstract() &&
              (it.visibility() == Visibility.PUBLIC || it.visibility() == Visibility.PROTECTED)
          }
      }
      .distinctBy { it.name }
      .map { it.toAssistedFactoryFunction(this) }
      .toList()

    // Check for exact number of functions.
    return when (assistedFunctions.size) {
      0 -> throw AnvilCompilationExceptionClassReference(
        message = "The @AssistedFactory-annotated type is missing an abstract, non-default " +
          "method whose return type matches the assisted injection type.",
        classReference = this
      )
      1 -> assistedFunctions[0]
      else -> {
        val foundFunctions = assistedFunctions
          .sortedBy { it.function.name }
          .joinToString { func ->
            "${func.function.name}(${func.parameterPairs.map { it.first.name }})"
          }
        throw AnvilCompilationExceptionClassReference(
          message = "The @AssistedFactory-annotated type should contain a single abstract, " +
            "non-default method but found multiple: [$foundFunctions]",
          classReference = this
        )
      }
    }
  }

  /**
   * Represents a parsed function in an `@AssistedInject.Factory`-annotated interface.
   */
  private data class AssistedFactoryFunction(
    val function: FunctionReference,
    val parameterKeys: List<AssistedParameterKey>,
    /**
     * Pair of parameter reference to parameter type.
     */
    val parameterPairs: List<Pair<ParameterReference, TypeName>>
  ) {
    companion object {
      fun FunctionReference.toAssistedFactoryFunction(
        factoryClass: ClassReference.Psi
      ): AssistedFactoryFunction {
        return AssistedFactoryFunction(
          function = this,
          parameterKeys = parameters.map { it.toAssistedParameterKey(factoryClass) },
          parameterPairs = parameters.map { it to it.resolveTypeName(factoryClass) }
        )
      }
    }
  }

  // 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 ParameterReference.toAssistedParameterKey(
        factoryClass: ClassReference.Psi
      ): AssistedParameterKey {
        return AssistedParameterKey(
          typeName = resolveTypeName(factoryClass),
          identifier = annotations
            .singleOrNull { it.fqName == assistedFqName }
            ?.let { annotation ->
              annotation.argumentAt("value", index = 0)?.value<String>()
            }
            .orEmpty()
        )
      }
    }
  }
}

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.assistedInjectFqName
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.codegen.injectConstructor
import com.squareup.anvil.compiler.internal.asClassName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.FunctionReference
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.PRIVATE
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.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
      .classAndInnerClassReferences(module)
      .forEach { clazz ->
        clazz.constructors
          .injectConstructor()
          ?.takeIf { it.isAnnotatedWith(assistedInjectFqName) }
          ?.let {
            generateFactoryClass(codeGenDir, clazz, it)
          }
      }
  }

  private fun generateFactoryClass(
    codeGenDir: File,
    clazz: ClassReference.Psi,
    constructor: FunctionReference.Psi
  ): GeneratedFile {
    val packageName = clazz.packageFqName.safePackageString()
    val classIdName = clazz.generateClassName(suffix = "_Factory")
    val className = classIdName.relativeClassName.asString()

    val constructorParameters = constructor.parameters.mapToConstructorParameters()
    val memberInjectParameters = clazz.memberInjectParameters()

    val parameters = constructorParameters + memberInjectParameters
    val parametersAssisted = parameters.filter { it.isAssisted }
    val parametersNotAssisted = parameters.filterNot { it.isAssisted }

    checkAssistedParametersAreDistinct(clazz, parametersAssisted)

    val typeParameters = clazz.typeParameters

    val factoryClass = classIdName.asClassName()
    val factoryClassParameterized = factoryClass.optionallyParameterizedBy(typeParameters)
    val classType = clazz.asClassName().optionallyParameterizedBy(typeParameters)

    val content = FileSpec.buildFile(packageName, className) {
      TypeSpec.classBuilder(factoryClass)
        .apply {
          typeParameters.forEach { addTypeVariable(it.typeVariableName) }

          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.map { it.typeVariableName })
                  }
                  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.map { it.typeVariableName })
                  }
                  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: ClassReference,
    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 AnvilCompilationExceptionClassReference(message = errorMessage, classReference = clazz)
  }
}

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.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.daggerComponentFqName
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
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
      .classAndInnerClassReferences(module)
      .firstOrNull { it.isAnnotatedWith(daggerComponentFqName) }

    if (component != null) {
      throw AnvilCompilationExceptionClassReference(
        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.",
        classReference = component
      )
    }
  }
}

package com.squareup.anvil.compiler.codegen.dagger

import com.squareup.anvil.compiler.assistedFqName
import com.squareup.anvil.compiler.daggerDoubleCheckFqNameString
import com.squareup.anvil.compiler.daggerLazyFqName
import com.squareup.anvil.compiler.injectFqName
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.ParameterReference
import com.squareup.anvil.compiler.internal.reference.PropertyReference
import com.squareup.anvil.compiler.internal.reference.TypeParameterReference
import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE
import com.squareup.anvil.compiler.internal.reference.allSuperTypeClassReferences
import com.squareup.anvil.compiler.internal.reference.argumentAt
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.withJvmSuppressWildcardsIfNeeded
import com.squareup.anvil.compiler.providerFqName
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FunSpec
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 javax.inject.Provider

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

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

internal fun List<ParameterReference>.mapToConstructorParameters(): List<ConstructorParameter> {
  return fold(listOf()) { acc, callableReference ->
    acc + callableReference.toConstructorParameter(callableReference.name.uniqueParameterName(acc))
  }
}

private fun ParameterReference.toConstructorParameter(
  uniqueName: String
): ConstructorParameter {
  val type = type()

  val isWrappedInProvider = type.asClassReferenceOrNull()?.fqName == providerFqName
  val isWrappedInLazy = type.asClassReferenceOrNull()?.fqName == daggerLazyFqName
  val isLazyWrappedInProvider = isWrappedInProvider &&
    type.unwrappedFirstType.asClassReferenceOrNull()?.fqName == daggerLazyFqName

  val typeName = when {
    isLazyWrappedInProvider -> type.unwrappedFirstType.unwrappedFirstType
    isWrappedInProvider || isWrappedInLazy -> type.unwrappedFirstType
    else -> type
  }.asTypeName().withJvmSuppressWildcardsIfNeeded(this, type)

  val assistedAnnotation = annotations.singleOrNull { it.fqName == assistedFqName }

  val assistedIdentifier = assistedAnnotation
    ?.argumentAt("value", 0)
    ?.value()
    ?: ""

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

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
 */
internal fun ClassReference.memberInjectParameters(): List<MemberInjectParameter> {
  return allSuperTypeClassReferences(includeSelf = true)
    .filterNot { it.isInterface() }
    .toList()
    .foldRight(listOf()) { classReference, acc ->
      acc + classReference.declaredMemberInjectParameters(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(
  superParameters: List<Parameter>
): List<MemberInjectParameter> {
  return properties
    .filter { it.isAnnotatedWith(injectFqName) }
    .filter { it.visibility() != PRIVATE }
    .fold(listOf()) { acc, property ->
      val uniqueName = property.name.uniqueParameterName(superParameters, acc)
      acc + property.toMemberInjectParameter(uniqueName = uniqueName)
    }
}

/**
 * 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()
}

private fun PropertyReference.toMemberInjectParameter(
  uniqueName: String
): MemberInjectParameter {
  val originalName = name
  val type = type()

  val isWrappedInProvider = type.asClassReferenceOrNull()?.fqName == providerFqName
  val isWrappedInLazy = type.asClassReferenceOrNull()?.fqName == daggerLazyFqName
  val isLazyWrappedInProvider = isWrappedInProvider &&
    type.unwrappedFirstType.asClassReferenceOrNull()?.fqName == daggerLazyFqName

  val typeName = when {
    isLazyWrappedInProvider -> type.unwrappedFirstType.unwrappedFirstType
    isWrappedInProvider || isWrappedInLazy -> type.unwrappedFirstType
    else -> type
  }.asTypeName().withJvmSuppressWildcardsIfNeeded(this, type)

  val assistedAnnotation = annotations.singleOrNull { it.fqName == assistedFqName }

  val assistedIdentifier = assistedAnnotation
    ?.argumentAt("value", 0)
    ?.value()
    ?: ""

  val memberInjectorClassName = declaringClass
    .generateClassName(separator = "_", suffix = "_MembersInjector")
    .relativeClassName
    .asString()

  val memberInjectorClass = ClassName(
    declaringClass.packageFqName.asString(), memberInjectorClassName
  )

  val isSetterInjected = this.setterAnnotations.any { it.fqName == injectFqName }

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

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

  return 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 = fqName
  )
}

internal fun ClassName.optionallyParameterizedBy(
  typeParameters: List<TypeParameterReference>
): TypeName {
  return if (typeParameters.isEmpty()) {
    this
  } else {
    parameterizedBy(typeParameters.map { it.typeVariableName })
  }
}

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.injectConstructor
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.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.FunctionReference
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
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.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
      .classAndInnerClassReferences(module)
      .forEach { clazz ->
        clazz.constructors
          .injectConstructor()
          ?.takeIf { it.isAnnotatedWith(injectFqName) }
          ?.let {
            generateFactoryClass(codeGenDir, clazz, it)
          }
      }
  }

  private fun generateFactoryClass(
    codeGenDir: File,
    clazz: ClassReference.Psi,
    constructor: FunctionReference.Psi
  ): GeneratedFile {
    val classId = clazz.generateClassName(suffix = "_Factory")

    val packageName = classId.packageFqName.safePackageString()
    val className = classId.relativeClassName.asString()

    val constructorParameters = constructor.parameters.mapToConstructorParameters()
    val memberInjectParameters = clazz.memberInjectParameters()

    val allParameters = constructorParameters + memberInjectParameters

    val typeParameters = clazz.typeParameters

    val factoryClass = classId.asClassName()
    val factoryClassParameterized = factoryClass.optionallyParameterizedBy(typeParameters)
    val classType = clazz.asClassName().optionallyParameterizedBy(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.typeVariableName) }

      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.map { it.typeVariableName })
                  }
                  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.map { it.typeVariableName })
                  }
                  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.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.injectFqName
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.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.Visibility
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.kotlinpoet.AnnotationSpec
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.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
      .classAndInnerClassReferences(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
        // (clazz.memberInjectParameters() will do this).
        clazz.properties
          .filter { it.visibility() != Visibility.PRIVATE }
          .filter { it.isAnnotatedWith(injectFqName) }
          .ifEmpty { return@forEach }

        generateMembersInjectorClass(
          codeGenDir = codeGenDir,
          clazz = clazz,
          parameters = clazz.memberInjectParameters()
        )
      }
  }

  private fun generateMembersInjectorClass(
    codeGenDir: File,
    clazz: ClassReference.Psi,
    parameters: List<MemberInjectParameter>
  ): GeneratedFile {
    val classId = clazz.generateClassName(suffix = "_MembersInjector")
    val packageName = classId.packageFqName.safePackageString()
    val className = classId.relativeClassName.asString()

    val classType = clazz.asClassName()
      .let {
        if (clazz.isGenericClass()) {
          it.parameterizedBy(List(size = clazz.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 = classId.asClassName()

    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.squareup.anvil.compiler.codegen.dagger.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.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.daggerModuleFqName
import com.squareup.anvil.compiler.daggerProvidesFqName
import com.squareup.anvil.compiler.internal.buildFile
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.reference.AnnotatedReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionFunctionReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.FunctionReference
import com.squareup.anvil.compiler.internal.reference.PropertyReference
import com.squareup.anvil.compiler.internal.reference.TypeReference
import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.reference.generateClassName
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.name.FqName
import org.jetbrains.kotlin.psi.KtFile
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
      .classAndInnerClassReferences(module)
      .filter { it.isAnnotatedWith(daggerModuleFqName) }
      .forEach { clazz ->
        (clazz.companionObjects() + clazz)
          .asSequence()
          .flatMap { it.functions }
          .filter { it.isAnnotatedWith(daggerProvidesFqName) }
          .onEach { function ->
            checkFunctionIsNotAbstract(clazz, function)
          }
          .also { functions ->
            // Check for duplicate function names.
            val duplicateFunctions = functions
              .groupBy { it.fqName }
              .filterValues { it.size > 1 }

            if (duplicateFunctions.isNotEmpty()) {
              throw AnvilCompilationExceptionClassReference(
                classReference = 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,
              CallableReference(function = function)
            )
          }

        (clazz.companionObjects() + clazz)
          .asSequence()
          .flatMap { it.properties }
          .filter { property ->
            // Must be '@get:Provides'.
            property.annotations.singleOrNull {
              it.fqName == daggerProvidesFqName
            }?.annotation?.useSiteTarget?.text == "get"
          }
          .forEach { property ->
            generateFactoryClass(
              codeGenDir,
              module,
              clazz,
              CallableReference(property = property)
            )
          }
      }
  }

  private fun generateFactoryClass(
    codeGenDir: File,
    module: ModuleDescriptor,
    clazz: ClassReference.Psi,
    declaration: CallableReference
  ): GeneratedFile {
    val isCompanionObject = declaration.declaringClass.isCompanion()
    val isObject = isCompanionObject || clazz.isObject()

    val isProperty = declaration.isProperty

    val isMangled = !isProperty &&
      declaration.visibility == INTERNAL &&
      !declaration.isAnnotatedWith(publishedApiFqName)

    val packageName = clazz.packageFqName.safePackageString()
    val className = buildString {
      append(clazz.generateClassName().relativeClassName.asString())
      append('_')
      if (isCompanionObject) {
        append("Companion_")
      }
      if (isProperty) {
        append("Get")
      }
      append(declaration.fqName.shortName().asString().capitalize())
      if (isMangled) {
        append("\$${module.mangledNameSuffix()}")
      }
      append("Factory")
    }

    val callableName = declaration.name

    val parameters = declaration.constructorParameters

    val returnType = declaration.type.asTypeName()
      .withJvmSuppressWildcardsIfNeeded(declaration.annotationReference, declaration.type)
    val returnTypeIsNullable = declaration.type.isNullable()

    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: ClassReference.Psi,
    function: FunctionReference.Psi
  ) {
    fun fail(): Nothing = throw AnvilCompilationExceptionFunctionReference(
      message = "@Provides methods cannot be abstract",
      functionReference = function
    )

    // If the function is abstract, then it's an error.
    if (function.isAbstract()) 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.
    if (function.declaringClass.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
    }
  }

  private class CallableReference(
    private val function: FunctionReference.Psi? = null,
    private val property: PropertyReference.Psi? = null
  ) {

    init {
      if (function == null && property == null) {
        throw AnvilCompilationException(
          "Cannot create a CallableReference wrapper without a " +
            "function OR a property"
        )
      }
    }

    val declaringClass = function?.declaringClass ?: property!!.declaringClass

    val visibility
      get() = function?.visibility() ?: property!!.visibility()

    val fqName = function?.fqName ?: property!!.fqName
    val name = function?.name ?: property!!.name

    val isProperty = property != null

    val constructorParameters: List<ConstructorParameter> =
      function?.parameters?.mapToConstructorParameters() ?: emptyList()

    val type: TypeReference = function?.let {
      it.returnTypeOrNull() ?: throw AnvilCompilationExceptionFunctionReference(
        message = "Dagger provider methods must specify the return type explicitly when using " +
          "Anvil. The return type cannot be inferred implicitly.",
        functionReference = it
      )
    } ?: property!!.type()
    val annotationReference: AnnotatedReference = function ?: property!!

    fun isAnnotatedWith(fqName: FqName): Boolean {
      return function?.isAnnotatedWith(fqName) ?: property!!.isAnnotatedWith(fqName)
    }
  }
}

package com.squareup.anvil.compiler.codegen.reference

import org.jetbrains.kotlin.name.FqName

internal interface AnnotatedReferenceIr {
  val annotations: List<AnnotationReferenceIr>

  fun isAnnotatedWith(fqName: FqName): Boolean =
    annotations.any { it.fqName == fqName }
}

internal fun List<AnnotationReferenceIr>.find(
  annotationName: FqName,
  scopeName: FqName? = null
): List<AnnotationReferenceIr> {
  return filter {
    it.fqName == annotationName && (scopeName == null || it.scopeOrNull?.fqName == scopeName)
  }
}

internal fun List<AnnotationReferenceIr>.findAll(
  vararg annotationNames: FqName,
  scopeName: FqName? = null
): List<AnnotationReferenceIr> {
  return filter {
    it.fqName in annotationNames && (scopeName == null || it.scopeOrNull?.fqName == scopeName)
  }
}

package com.squareup.anvil.compiler.codegen.reference

import com.squareup.anvil.compiler.kclassUnwrapped
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrVararg
import kotlin.LazyThreadSafetyMode.NONE

internal class AnnotationArgumentReferenceIr(
  val argumentParameter: IrValueParameter,
  val argumentExpression: IrExpression,
  val annotation: AnnotationReferenceIr
) {
  val name: String = argumentParameter.name.toString()

  val context: IrPluginContext
    get() = annotation.context

  private val value: Any by lazy(NONE) {
    findValue()
  }

  // We currently special-case for Classes, but this is the spot we'll need to update if we need to
  // support primitives later on.
  private fun findValue(): Any {
    (argumentExpression as? IrVararg)?.elements?.let { element ->
      return element.filterIsInstance<IrExpression>()
        .map { it.kclassUnwrapped.owner as IrClass }
        .map { it.symbol.toClassReference(context) }
    }

    (argumentExpression.kclassUnwrapped.owner as? IrClass)?.let {
      return it.symbol.toClassReference(context)
    }

    return argumentExpression
  }

  // Maybe we need to make the return type nullable and allow for a default value.
  @Suppress("UNCHECKED_CAST")
  fun <T : Any> value(): T = value as T

  override fun toString(): String {
    return "${AnnotationArgumentReferenceIr::class.simpleName}(name=$name, value=$value)"
  }

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

    if (name != other.name) return false
    if (value != other.value) return false

    return true
  }

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

internal fun Pair<IrValueParameter, IrExpression>.toAnnotationArgumentReference(
  annotation: AnnotationReferenceIr
): AnnotationArgumentReferenceIr {
  return AnnotationArgumentReferenceIr(
    argumentParameter = this.first,
    argumentExpression = this.second,
    annotation = annotation
  )
}

package com.squareup.anvil.compiler.codegen.reference

import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.internal.reference.AnnotationReference
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.util.getArgumentsWithIr
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.name.FqName
import kotlin.LazyThreadSafetyMode.NONE

internal class AnnotationReferenceIr(
  val annotation: IrConstructorCall,
  val classReference: ClassReferenceIr,
  declaringClass: ClassReferenceIr?
) {
  val fqName: FqName
    get() = classReference.fqName

  val context: IrPluginContext
    get() = classReference.context

  val arguments: List<AnnotationArgumentReferenceIr> by lazy(NONE) {
    annotation.getArgumentsWithIr().map { it.toAnnotationArgumentReference(this) }
  }

  val scope: ClassReferenceIr
    get() = scopeOrNull ?: throw AnvilCompilationException(
      element = annotation,
      message = "Couldn't find scope for $fqName."
    )

  val scopeOrNull: ClassReferenceIr? by lazy(NONE) {
    argumentOrNull("scope")?.value<ClassReferenceIr>()?.let {
      context.referenceClass(it.fqName)?.toClassReference(context)
    }
  }

  val declaringClassOrNull: ClassReferenceIr? = declaringClass
  val declaringClass: ClassReferenceIr = declaringClassOrNull
    ?: throw AnvilCompilationExceptionAnnotationReferenceIr(
      annotationReference = this,
      message = "The declaring class was null, this means the annotation wasn't used on a class."
    )

  val parentScope: ClassReferenceIr by lazy(NONE) {
    argumentOrNull("parentScope")?.value<ClassReferenceIr>()?.let {
      context.referenceClass(it.fqName)?.toClassReference(context)
    } ?: throw AnvilCompilationException(
      element = annotation,
      message = "Couldn't find parent scope for $fqName."
    )
  }

  val excludedClasses: List<ClassReferenceIr> by lazy(NONE) {
    argumentOrNull("exclude")?.value<List<ClassReferenceIr>>().orEmpty()
  }

  val replacedClasses: List<ClassReferenceIr> by lazy(NONE) {
    argumentOrNull("replaces")?.value<List<ClassReferenceIr>>().orEmpty()
  }

  fun argumentOrNull(name: String): AnnotationArgumentReferenceIr? {
    return arguments.singleOrNull { it.name == name }
  }

  override fun toString(): String = "@$fqName"

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

    if (fqName != other.fqName) return false
    if (arguments != other.arguments) return false

    return true
  }

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

internal fun IrConstructorCall.toAnnotationReference(
  context: IrPluginContext,
  declaringClass: ClassReferenceIr?
) =
  AnnotationReferenceIr(
    annotation = this,
    classReference = this.symbol.owner.parentAsClass.symbol.toClassReference(context),
    declaringClass = declaringClass
  )

@Suppress("FunctionName")
internal fun AnvilCompilationExceptionAnnotationReferenceIr(
  annotationReference: AnnotationReferenceIr,
  message: String,
  cause: Throwable? = null
): AnvilCompilationException = AnvilCompilationException(
  element = annotationReference.annotation,
  message = message,
  cause = cause
)

package com.squareup.anvil.compiler.codegen.reference

import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.fqName
import com.squareup.anvil.compiler.internal.reference.Visibility
import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL
import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE
import com.squareup.anvil.compiler.internal.reference.Visibility.PROTECTED
import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC
import com.squareup.anvil.compiler.requireClassId
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.lower.parents
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.packageFqName
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import kotlin.LazyThreadSafetyMode.NONE

internal class ClassReferenceIr(
  val clazz: IrClassSymbol,
  val context: IrPluginContext
) : AnnotatedReferenceIr {
  val fqName: FqName = clazz.fqName
  val packageFqName: FqName? = clazz.owner.packageFqName
  val classId: ClassId = clazz.requireClassId()

  val shortName: String
    get() = fqName.shortName().asString()

  val enclosingClassesWithSelf: List<ClassReferenceIr> by lazy {
    clazz.owner.parents
      .filterIsInstance<IrClass>()
      .map { it.symbol.toClassReference(context) }
      .toList()
      .reversed()
      .plus(this)
  }

  val isInterface: Boolean = clazz.owner.isInterface

  val visibility: Visibility = parseVisibility()

  private fun parseVisibility(): Visibility {
    return when (clazz.owner.visibility) {
      DescriptorVisibilities.PUBLIC -> PUBLIC
      DescriptorVisibilities.PRIVATE -> PRIVATE
      DescriptorVisibilities.INTERNAL -> INTERNAL
      DescriptorVisibilities.PROTECTED -> PROTECTED
      else -> throw AnvilCompilationExceptionClassReferenceIr(
        this,
        "Encountered an unsupported visibility ${clazz.owner.visibility.name} for class $fqName"
      )
    }
  }

  override val annotations: List<AnnotationReferenceIr> by lazy(NONE) {
    clazz.owner.annotations.map { it.toAnnotationReference(context, this) }
  }

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

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

    if (fqName != other.fqName) return false

    return true
  }

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

internal fun IrClassSymbol.toClassReference(context: IrPluginContext) =
  ClassReferenceIr(this, context)

@Suppress("FunctionName")
internal fun AnvilCompilationExceptionClassReferenceIr(
  classReference: ClassReferenceIr,
  message: String,
  cause: Throwable? = null
): AnvilCompilationException = AnvilCompilationException(
  element = classReference.clazz,
  message = message,
  cause = cause
)

package com.squareup.anvil.compiler.codegen.reference

import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.codegen.reference.RealAnvilModuleDescriptor.ClassReferenceCacheKey.Companion.toClassReferenceCacheKey
import com.squareup.anvil.compiler.codegen.reference.RealAnvilModuleDescriptor.ClassReferenceCacheKey.Type.DESCRIPTOR
import com.squareup.anvil.compiler.codegen.reference.RealAnvilModuleDescriptor.ClassReferenceCacheKey.Type.PSI
import com.squareup.anvil.compiler.internal.classIdBestGuess
import com.squareup.anvil.compiler.internal.reference.AnvilModuleDescriptor
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi
import com.squareup.anvil.compiler.internal.requireFqName
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.components.LookupLocation
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.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe

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

  private val ktFileToClassReferenceMap = mutableMapOf<String, List<Psi>>()
  private val allPsiClassReferences: Sequence<Psi>
    get() = ktFileToClassReferenceMap.values.asSequence().flatten()

  private val resolveDescriptorCache = mutableMapOf<FqName, ClassDescriptor?>()
  private val resolveClassIdCache = mutableMapOf<ClassId, FqName?>()
  private val classReferenceCache = mutableMapOf<ClassReferenceCacheKey, ClassReference>()

  internal val allFiles: Sequence<KtFile>
    get() = allPsiClassReferences
      .map { it.clazz.containingKtFile }
      .distinctBy { it.identifier }

  fun addFiles(files: Collection<KtFile>) {
    files.forEach { ktFile ->
      val classReferences = ktFile.classesAndInnerClasses().map { ktClass ->
        Psi(ktClass, ktClass.toClassId(), this)
      }

      classReferences.forEach { classReference ->
        // A `FlushingCodeGenerator` can generate new KtFiles for already generated KtFiles.
        // This can be problematic, because we might have ClassReferences for the old files and
        // KtClassOrObject instances cached. We need to override the entries in the caches for
        // these new files and classes.
        classReferenceCache[classReference.clazz.toClassReferenceCacheKey()] = classReference
        resolveClassIdCache[classReference.classId] = classReference.fqName
      }

      ktFileToClassReferenceMap[ktFile.identifier] = classReferences
    }
  }

  override fun getClassAndInnerClassReferences(ktFile: KtFile): List<Psi> {
    return ktFileToClassReferenceMap.getOrPut(ktFile.identifier) {
      ktFile.classesAndInnerClasses().map { getClassReference(it) }
    }
  }

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

      resolveFqNameOrNull(fqName)?.fqNameSafe
        ?: allPsiClassReferences.firstOrNull { it.fqName == fqName }?.fqName
    }

  override fun resolveFqNameOrNull(
    fqName: FqName,
    lookupLocation: LookupLocation
  ): ClassDescriptor? {
    return resolveDescriptorCache.getOrPut(fqName) {
      // In the case of a typealias, we need to look up the original reference instead.
      resolveClassByFqName(fqName, lookupLocation)
        ?: findTypeAliasAcrossModuleDependencies(fqName.classIdBestGuess())?.classDescriptor
    }
  }

  override fun getClassReference(clazz: KtClassOrObject): Psi {
    return classReferenceCache.getOrPut(clazz.toClassReferenceCacheKey()) {
      Psi(clazz, clazz.toClassId(), this)
    } as Psi
  }

  override fun getClassReference(descriptor: ClassDescriptor): Descriptor {
    return classReferenceCache.getOrPut(descriptor.toClassReferenceCacheKey()) {
      val classId = descriptor.classId ?: throw AnvilCompilationException(
        classDescriptor = descriptor,
        message = "Couldn't find the classId for $fqNameSafe. Are we stuck in a loop while " +
          "resolving super types?"
      )
      Descriptor(descriptor, classId, this)
    } as Descriptor
  }

  override fun getClassReferenceOrNull(fqName: FqName): ClassReference? {
    // Note that we don't cache the result, because all function calls get objects from caches.
    // There's no need to optimize that.
    fun psiClassReference(): Psi? = allPsiClassReferences.firstOrNull { it.fqName == fqName }
    fun descriptorClassReference(): Descriptor? =
      resolveFqNameOrNull(fqName)?.let { getClassReference(it) }

    // Prefer Psi to have consistent results. If the class is part of the compilation unit: it'll
    // be a Psi implementation. If the class comes from a pre-compiled dependency, then it'll be a
    // descriptor implementation.
    //
    // Otherwise, there are inconsistencies. If source code was written manually in this module,
    // then we could use the descriptor or Psi implementation. If the source code was generated,
    // then only Psi works.
    return psiClassReference() ?: descriptorClassReference()
  }

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

  internal class Factory {
    private val cache = mutableMapOf<ModuleDescriptor, RealAnvilModuleDescriptor>()

    fun create(delegate: ModuleDescriptor): RealAnvilModuleDescriptor {
      return cache.getOrPut(delegate) { RealAnvilModuleDescriptor(delegate) }
    }
  }

  private data class ClassReferenceCacheKey(
    private val fqName: FqName,
    private val type: Type
  ) {
    enum class Type {
      PSI,
      DESCRIPTOR,
    }

    companion object {
      fun KtClassOrObject.toClassReferenceCacheKey(): ClassReferenceCacheKey =
        ClassReferenceCacheKey(requireFqName(), PSI)

      fun ClassDescriptor.toClassReferenceCacheKey(): ClassReferenceCacheKey =
        ClassReferenceCacheKey(fqNameSafe, DESCRIPTOR)
    }
  }
}

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

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

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

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