Repository: square/anvil (kotlin)

Issue:
"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()'

Pull request:
Fixes #574

Patch:
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 8df7e7e82..fe1f7911b 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
     }


Test patch:
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 92eb5adae..4c4129a17 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,