Repository: square/anvil
Pull request #54 (2020-08-03 17:04:49)

Change the `replaces` attribute from a single class to an array. 
This gives the API more flexibility and avoids redundant classes. E.g. one Dagger module with several binding and provider methods may wish to replace multiple other Dagger modules.


Patch:
diff --git a/README.md b/README.md
index 372d1981..d514d65e 100644
--- a/README.md
+++ b/README.md
@@ -104,7 +104,7 @@ different bindings for instrumentation tests, e.g.
 @Module
 @ContributesTo(
     scope = AppScope::class,
-    replaces = DevelopmentApplicationModule::class
+    replaces = [DevelopmentApplicationModule::class]
 )
 object DevelopmentApplicationTestModule {
   @Provides
diff --git a/annotations/src/main/java/com/squareup/anvil/annotations/ContributesBinding.kt b/annotations/src/main/java/com/squareup/anvil/annotations/ContributesBinding.kt
index 7db60f6e..eb436494 100644
--- a/annotations/src/main/java/com/squareup/anvil/annotations/ContributesBinding.kt
+++ b/annotations/src/main/java/com/squareup/anvil/annotations/ContributesBinding.kt
@@ -45,7 +45,7 @@ import kotlin.reflect.KClass
  * ```
  * @ContributesBinding(
  *     scope = AppScope::class,
- *     replaces = RealAuthenticator::class
+ *     replaces = [RealAuthenticator::class]
  * )
  * class FakeAuthenticator @Inject constructor() : Authenticator
  * ```
@@ -55,5 +55,5 @@ import kotlin.reflect.KClass
 annotation class ContributesBinding(
   val scope: KClass<*>,
   val boundType: KClass<*> = Unit::class,
-  val replaces: KClass<*> = Unit::class
+  val replaces: Array<KClass<*>> = []
 )
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/InterfaceMerger.kt b/compiler/src/main/java/com/squareup/anvil/compiler/InterfaceMerger.kt
index 5fe7e296..0580e158 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/InterfaceMerger.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/InterfaceMerger.kt
@@ -66,22 +66,22 @@ internal class InterfaceMerger(
         }
 
     val replacedClasses = classes
-        .mapNotNull { (classDescriptor, contributeAnnotation) ->
-          val classDescriptorForReplacement = contributeAnnotation.replaces(classDescriptor.module)
-              ?: return@mapNotNull null
-
-          // Verify the other class is an interface. It doesn't make sense for a contributed interface
-          // to replace a class that is not an interface.
-          if (!DescriptorUtils.isInterface(classDescriptorForReplacement)) {
-            throw AnvilCompilationException(
-                classDescriptor,
-                "${classDescriptor.fqNameSafe} wants to replace " +
-                    "${classDescriptorForReplacement.fqNameSafe}, but the class being replaced " +
-                    "is not an interface."
-            )
-          }
-
-          classDescriptorForReplacement.fqNameSafe
+        .flatMap { (classDescriptor, contributeAnnotation) ->
+          contributeAnnotation.replaces(classDescriptor.module)
+              .asSequence()
+              .onEach { classDescriptorForReplacement ->
+                // Verify the other class is an interface. It doesn't make sense for a contributed
+                // interface to replace a class that is not an interface.
+                if (!DescriptorUtils.isInterface(classDescriptorForReplacement)) {
+                  throw AnvilCompilationException(
+                      classDescriptor,
+                      "${classDescriptor.fqNameSafe} wants to replace " +
+                          "${classDescriptorForReplacement.fqNameSafe}, but the class being " +
+                          "replaced is not an interface."
+                  )
+                }
+              }
+              .map { it.fqNameSafe }
         }
         .toSet()
 
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt b/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt
index cd405a49..fa595cc9 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/ModuleMerger.kt
@@ -106,23 +106,25 @@ internal class ModuleMerger(
         }
 
     val replacedModules = modules
-        .mapNotNull { (classDescriptor, contributeAnnotation) ->
-          val classDescriptorForReplacement =
-            contributeAnnotation.replaces(module) ?: return@mapNotNull null
+        .flatMap { (classDescriptor, contributeAnnotation) ->
+          contributeAnnotation.replaces(module)
+              .asSequence()
+              .map { classDescriptorForReplacement ->
+                // Verify has @Module annotation. It doesn't make sense for a Dagger module to
+                // replace a non-Dagger module.
+                if (classDescriptorForReplacement.annotationOrNull(daggerModuleFqName) == null &&
+                    classDescriptorForReplacement.annotationOrNull(contributesBindingFqName) == null
+                ) {
+                  throw AnvilCompilationException(
+                      classDescriptor,
+                      "${classDescriptor.fqNameSafe} wants to replace " +
+                          "${classDescriptorForReplacement.fqNameSafe}, but the class being " +
+                          "replaced is not a Dagger module."
+                  )
+                }
 
-          // Verify has @Module annotation. It doesn't make sense for a Dagger module to replace a
-          // non-Dagger module.
-          if (classDescriptorForReplacement.annotationOrNull(daggerModuleFqName) == null &&
-              classDescriptorForReplacement.annotationOrNull(contributesBindingFqName) == null) {
-            throw AnvilCompilationException(
-                classDescriptor,
-                "${classDescriptor.fqNameSafe} wants to replace " +
-                    "${classDescriptorForReplacement.fqNameSafe}, but the class being replaced " +
-                    "is not a Dagger module."
-            )
-          }
-
-          classDescriptorForReplacement.defaultType.asmType(codegen.typeMapper)
+                classDescriptorForReplacement.defaultType.asmType(codegen.typeMapper)
+              }
         }
 
     val replacedModulesByContributedBindings = classScanner
@@ -132,12 +134,14 @@ internal class ModuleMerger(
             annotation = contributesBindingFqName
         )
         .asSequence()
-        .mapNotNull {
-          val annotation = it.annotation(contributesBindingFqName)
+        .flatMap { contributedClass ->
+          val annotation = contributedClass.annotation(contributesBindingFqName)
           if (scopeFqName == annotation.scope(module).fqNameSafe) {
-            annotation.replaces(module)?.defaultType?.asmType(codegen.typeMapper)
+            annotation.replaces(module)
+                .asSequence()
+                .map { it.defaultType.asmType(codegen.typeMapper) }
           } else {
-            null
+            emptySequence()
           }
         }
 
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
index d1363110..44ae92a5 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.platform.has
 import org.jetbrains.kotlin.platform.jvm.JvmPlatform
 import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.resolve.constants.ArrayValue
 import org.jetbrains.kotlin.resolve.constants.ConstantValue
 import org.jetbrains.kotlin.resolve.constants.KClassValue
 import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
@@ -109,11 +110,15 @@ internal fun AnnotationDescriptor.scope(module: ModuleDescriptor): ClassDescript
       .classDescriptorForType()
 }
 
-internal fun AnnotationDescriptor.replaces(module: ModuleDescriptor): ClassDescriptor? {
-  return (getAnnotationValue("replaces") as? KClassValue)
-      ?.getType(module)
-      ?.argumentType()
-      ?.classDescriptorForType()
+internal fun AnnotationDescriptor.replaces(module: ModuleDescriptor): List<ClassDescriptor> {
+  return (getAnnotationValue("replaces") as? ArrayValue)
+      ?.value
+      ?.map {
+        it.getType(module)
+            .argumentType()
+            .classDescriptorForType()
+      }
+      ?: emptyList()
 }
 
 internal fun AnnotationDescriptor.boundType(
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt
index c8a9c1fd..04fda8bf 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/BindingModuleGenerator.kt
@@ -153,9 +153,11 @@ internal class BindingModuleGenerator(
       ).asSequence()
 
       val replacedBindings = (contributedBindingsThisModule + contributedBindingsDependencies)
-          .mapNotNull {
+          .flatMap {
             it.annotationOrNull(contributesBindingFqName, scope = scope)
                 ?.replaces(module)
+                ?.asSequence()
+                ?: emptySequence()
           }
 
       // Contributed Dagger modules can replace other Dagger modules but also contributed bindings.
@@ -168,9 +170,11 @@ internal class BindingModuleGenerator(
               annotation = contributesToFqName
           ))
           .filter { it.annotationOrNull(daggerModuleFqName) != null }
-          .mapNotNull {
+          .flatMap {
             it.annotationOrNull(contributesToFqName, scope)
                 ?.replaces(module)
+                ?.asSequence()
+                ?: emptySequence()
           }
 
       val bindingMethods = (contributedBindingsThisModule + contributedBindingsDependencies)


Test patch:
diff --git a/annotations/src/main/java/com/squareup/anvil/annotations/ContributesTo.kt b/annotations/src/main/java/com/squareup/anvil/annotations/ContributesTo.kt
index b752b99f..315c22cb 100644
--- a/annotations/src/main/java/com/squareup/anvil/annotations/ContributesTo.kt
+++ b/annotations/src/main/java/com/squareup/anvil/annotations/ContributesTo.kt
@@ -28,7 +28,7 @@ import kotlin.reflect.KClass
  * @Module
  * @ContributesTo(
  *     AppScope::class,
- *     replaces = DevelopmentApplicationModule::class
+ *     replaces = [DevelopmentApplicationModule::class]
  * )
  * object DevelopmentApplicationTestModule {
  *   @Provides
@@ -40,5 +40,5 @@ import kotlin.reflect.KClass
 @Retention(RUNTIME)
 annotation class ContributesTo(
   val scope: KClass<*>,
-  val replaces: KClass<*> = Unit::class
+  val replaces: Array<KClass<*>> = []
 )
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/InterfaceMergerTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/InterfaceMergerTest.kt
index 5551713b..a1402565 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/InterfaceMergerTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/InterfaceMergerTest.kt
@@ -159,7 +159,7 @@ class InterfaceMergerTest(
         
         @ContributesTo(
             Any::class,
-            replaces = ContributingInterface::class
+            replaces = [ContributingInterface::class]
         )
         interface SecondContributingInterface        
 
@@ -185,7 +185,7 @@ class InterfaceMergerTest(
         
         @ContributesTo(
             Any::class,
-            replaces = ContributingInterface::class
+            replaces = [ContributingInterface::class]
         )
         interface SecondContributingInterface        
 
@@ -213,7 +213,7 @@ class InterfaceMergerTest(
         
         @ContributesTo(
             Any::class,
-            replaces = ContributingInterface::class
+            replaces = [ContributingInterface::class]
         )
         interface SecondContributingInterface
 
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/MergeModulesTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/MergeModulesTest.kt
index 406b952e..e66373f5 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/MergeModulesTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/MergeModulesTest.kt
@@ -172,7 +172,7 @@ class MergeModulesTest {
 
         @ContributesTo(
             Any::class,
-            replaces = DaggerModule2::class
+            replaces = [DaggerModule2::class]
         )
         @dagger.Module
         abstract class DaggerModule3
@@ -202,7 +202,7 @@ class MergeModulesTest {
 
         @ContributesTo(
             Any::class,
-            replaces = ContributingInterface::class
+            replaces = [ContributingInterface::class]
         )
         @dagger.Module
         abstract class DaggerModule2
@@ -235,7 +235,7 @@ class MergeModulesTest {
 
         @ContributesBinding(
             Any::class,
-            replaces = DaggerModule2::class
+            replaces = [DaggerModule2::class]
         )
         interface ContributingInterface : ParentInterface
 
@@ -260,7 +260,7 @@ class MergeModulesTest {
 
         @ContributesTo(
             Any::class,
-            replaces = DaggerModule3::class
+            replaces = [DaggerModule3::class]
         )
         @dagger.Module
         abstract class DaggerModule2
@@ -288,7 +288,7 @@ class MergeModulesTest {
 
         @ContributesTo(
             Any::class,
-            replaces = DaggerModule2::class
+            replaces = [DaggerModule2::class]
         )
         @dagger.Module
         abstract class DaggerModule3
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/ModuleMergerTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/ModuleMergerTest.kt
index 0300c1ac..c9ae8b11 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/ModuleMergerTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/ModuleMergerTest.kt
@@ -228,7 +228,7 @@ class ModuleMergerTest(
 
         @ContributesTo(
             Any::class,
-            replaces = DaggerModule1::class
+            replaces = [DaggerModule1::class]
         )
         @dagger.Module
         abstract class DaggerModule2 
@@ -258,7 +258,7 @@ class ModuleMergerTest(
 
         @ContributesTo(
             Any::class,
-            replaces = ContributingInterface::class
+            replaces = [ContributingInterface::class]
         )
         @dagger.Module
         abstract class DaggerModule1
@@ -291,7 +291,7 @@ class ModuleMergerTest(
 
         @ContributesBinding(
             Any::class,
-            replaces = DaggerModule1::class
+            replaces = [DaggerModule1::class]
         )
         interface ContributingInterface : ParentInterface
 
@@ -316,7 +316,7 @@ class ModuleMergerTest(
 
         @ContributesTo(
             Any::class,
-            replaces = DaggerModule1::class
+            replaces = [DaggerModule1::class]
         )
         @dagger.Module
         abstract class DaggerModule2 
@@ -344,7 +344,7 @@ class ModuleMergerTest(
 
         @ContributesTo(
             Any::class,
-            replaces = DaggerModule1::class
+            replaces = [DaggerModule1::class]
         )
         @dagger.Module
         abstract class DaggerModule2 
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/BindingModuleGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/BindingModuleGeneratorTest.kt
index e474c320..bb16f812 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/BindingModuleGeneratorTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/BindingModuleGeneratorTest.kt
@@ -312,7 +312,7 @@ class BindingModuleGeneratorTest(
         
         @ContributesBinding(
             Any::class,
-            replaces = ContributingInterface::class
+            replaces = [ContributingInterface::class]
         )
         interface SecondContributingInterface : ParentInterface
         
diff --git a/sample/app/src/androidTest/java/com/squareup/anvil/sample/FakeFatherProviderModule.kt b/sample/app/src/androidTest/java/com/squareup/anvil/sample/FakeFatherProviderModule.kt
index a1c915ad..ab388e15 100644
--- a/sample/app/src/androidTest/java/com/squareup/anvil/sample/FakeFatherProviderModule.kt
+++ b/sample/app/src/androidTest/java/com/squareup/anvil/sample/FakeFatherProviderModule.kt
@@ -10,7 +10,7 @@ import dagger.Module
 @Module
 @ContributesTo(
     scope = AppScope::class,
-    replaces = FatherProviderModule::class
+    replaces = [FatherProviderModule::class]
 )
 abstract class FakeFatherProviderModule {
   @Binds abstract fun bindFatherProvider(provider: FakeFatherProvider): FatherProvider