Repository: square/anvil (kotlin)

Issue:
Wildcard assisted parameters regression in 2.3.7
The following snippet in 2.3.7 now fails 

```kotlin
class AmiActionsMediaGalleryBinder @AssistedInject constructor(
  @Assisted private val attachCallback: (MediaTabContract.View) -> Unit
) : SKListCustomViewBinder {

  // ...

  @AssistedFactory
  interface Factory {
    fun create(attachCallback: (MediaTabContract.View) -> Unit): AmiActionsMediaGalleryBinder
  }
}
```

With a message like so

```
e: com.squareup.anvil.compiler.api.AnvilCompilationException: Back-end (JVM) Internal error: The parameters in the factory method must match the @Assisted parameters in slack.app.ui.advancedmessageinput.amiActions.AmiActionsMediaGalleryBinder.
File being compiled: (35,3) in /mnt/buildkite-agent/builds/mbr-buildkite-agent-android-iad-5ik6-1/slack/android-pull-request/apps/app-legacy/src/main/java/slack/app/ui/advancedmessageinput/amiActions/AmiActionsMediaGalleryBinder.kt
 
	at com.squareup.anvil.compiler.codegen.dagger.AssistedFactoryGenerator.generateFactoryClass(AssistedFactoryGenerator.kt:166)
	at com.squareup.anvil.compiler.codegen.dagger.AssistedFactoryGenerator.generateCodePrivate(AssistedFactoryGenerator.kt:88)
	at com.squareup.anvil.compiler.codegen.PrivateCodeGenerator.generateCode(PrivateCodeGenerator.kt:21)
	at com.squareup.anvil.compiler.codegen.CodeGenerationExtension.analysisCompleted$generateCode(CodeGenerationExtension.kt:92)
	at com.squareup.anvil.compiler.codegen.CodeGenerationExtension.analysisCompleted(CodeGenerationExtension.kt:113)
```

Pull request description:
fixes #400

Patch:
diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/KotlinPoetUtils.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/KotlinPoetUtils.kt
index 10bcddb9d..62876b2bd 100644
--- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/KotlinPoetUtils.kt
+++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/KotlinPoetUtils.kt
@@ -11,6 +11,7 @@ import com.squareup.kotlinpoet.FileSpec
 import com.squareup.kotlinpoet.LambdaTypeName
 import com.squareup.kotlinpoet.MemberName
 import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.ParameterizedTypeName
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.STAR
 import com.squareup.kotlinpoet.TypeName
@@ -232,6 +233,23 @@ public fun KtTypeReference.requireTypeName(
   return (typeElement ?: fail()).requireTypeName()
 }
 
+/**
+ * Converts a lambda `TypeName` to a standard `kotlin.Function*` type.
+ *
+ * given the lambda type: `(kotlin.String) -> kotlin.Unit`
+ * returns the function type: `kotlin.Function1<String, Unit>`
+ */
+@ExperimentalAnvilApi
+public fun LambdaTypeName.asFunctionType(): ParameterizedTypeName {
+
+  val allTypes = listOfNotNull(receiver)
+    .plus(parameters.map { it.type })
+    .plus(returnType)
+
+  return ClassName("kotlin", "Function${allTypes.size - 1}")
+    .parameterizedBy(allTypes)
+}
+
 @ExperimentalAnvilApi
 public fun KotlinType.asTypeName(): TypeName {
   return asTypeNameOrNull { true }!!
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/AssistedFactoryGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/AssistedFactoryGenerator.kt
index 79cd82d36..6676389ee 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/AssistedFactoryGenerator.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/AssistedFactoryGenerator.kt
@@ -18,6 +18,7 @@ import com.squareup.anvil.compiler.internal.ClassReference.Descriptor
 import com.squareup.anvil.compiler.internal.ClassReference.Psi
 import com.squareup.anvil.compiler.internal.allSuperTypeClassReferences
 import com.squareup.anvil.compiler.internal.asClassName
+import com.squareup.anvil.compiler.internal.asFunctionType
 import com.squareup.anvil.compiler.internal.asTypeName
 import com.squareup.anvil.compiler.internal.buildFile
 import com.squareup.anvil.compiler.internal.classDescriptorForType
@@ -40,6 +41,7 @@ import com.squareup.kotlinpoet.FileSpec
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier.OVERRIDE
 import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.LambdaTypeName
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.PropertySpec
 import com.squareup.kotlinpoet.TypeName
@@ -451,6 +453,14 @@ internal class AssistedFactoryGenerator : PrivateCodeGenerator() {
               ?: typeReference
           }
           ?.requireTypeName(module)
+          ?.let { typeName ->
+            // This is a special case when mixing parsing between descriptors and PSI.  Descriptors
+            // will always represent lambda types as kotlin.Function* types, so lambda arguments
+            // must be converted or their signatures won't match.
+            // see https://github.com/square/anvil/issues/400
+            (typeName as? LambdaTypeName)?.asFunctionType()
+              ?: typeName
+          }
           ?: throw AnvilCompilationException(
             "Couldn't get type for parameter.",
             element = parameter


Test patch:
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/AssistedFactoryGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/AssistedFactoryGeneratorTest.kt
index 8baf30cf4..0d646aa77 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/AssistedFactoryGeneratorTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/AssistedFactoryGeneratorTest.kt
@@ -281,6 +281,174 @@ public final class AssistedServiceFactory_Impl implements AssistedServiceFactory
     }
   }
 
+  @Test fun `the factory function may require a lambda type`() {
+    compile(
+      """
+      package com.squareup.test
+      
+      import dagger.assisted.Assisted
+      import dagger.assisted.AssistedFactory
+      import dagger.assisted.AssistedInject
+      
+      data class AssistedService @AssistedInject constructor(
+        val int: Int,
+        @Assisted val stringFactory: (Int) -> String
+      )
+      
+      @AssistedFactory
+      interface AssistedServiceFactory {
+        fun create(stringFactory: (Int) -> String): AssistedService
+      }
+      """
+    ) {
+      val factoryImplClass = assistedServiceFactory.implClass()
+      val generatedFactoryInstance = assistedService.factoryClass().createInstance(Provider { 5 })
+      val factoryImplInstance = factoryImplClass.createInstance(generatedFactoryInstance)
+
+      val staticMethods = factoryImplClass.declaredMethods.filter { it.isStatic }
+      assertThat(staticMethods).hasSize(1)
+
+      val factoryProvider = staticMethods.single { it.name == "create" }
+        .invoke(null, generatedFactoryInstance) as Provider<*>
+      assertThat(factoryProvider.get()::class.java).isEqualTo(factoryImplClass)
+
+      val lambdaArg = { num: Int -> num.toString() }
+
+      val assistedServiceInstance = factoryImplClass.declaredMethods
+        .filterNot { it.isStatic }
+        .last { it.name == "create" }
+        .invoke(factoryImplInstance, lambdaArg)
+
+      assertThat(assistedServiceInstance).isEqualTo(assistedService.createInstance(5, lambdaArg))
+    }
+  }
+
+  @Test fun `the factory function may require a Function type`() {
+    compile(
+      """
+      package com.squareup.test
+      
+      import dagger.assisted.Assisted
+      import dagger.assisted.AssistedFactory
+      import dagger.assisted.AssistedInject
+      
+      data class AssistedService @AssistedInject constructor(
+        val int: Int,
+        @Assisted val stringFactory: Function1<Int, String>
+      )
+      
+      @AssistedFactory
+      interface AssistedServiceFactory {
+        fun create(stringFactory: Function1<Int, String>): AssistedService
+      }
+      """
+    ) {
+      val factoryImplClass = assistedServiceFactory.implClass()
+      val generatedFactoryInstance = assistedService.factoryClass().createInstance(Provider { 5 })
+      val factoryImplInstance = factoryImplClass.createInstance(generatedFactoryInstance)
+
+      val staticMethods = factoryImplClass.declaredMethods.filter { it.isStatic }
+      assertThat(staticMethods).hasSize(1)
+
+      val factoryProvider = staticMethods.single { it.name == "create" }
+        .invoke(null, generatedFactoryInstance) as Provider<*>
+      assertThat(factoryProvider.get()::class.java).isEqualTo(factoryImplClass)
+
+      val lambdaArg = { num: Int -> num.toString() }
+
+      val assistedServiceInstance = factoryImplClass.declaredMethods
+        .filterNot { it.isStatic }
+        .last { it.name == "create" }
+        .invoke(factoryImplInstance, lambdaArg)
+
+      assertThat(assistedServiceInstance).isEqualTo(assistedService.createInstance(5, lambdaArg))
+    }
+  }
+
+  @Test fun `the factory function may substitute a Function type with a lambda type`() {
+    compile(
+      """
+      package com.squareup.test
+      
+      import dagger.assisted.Assisted
+      import dagger.assisted.AssistedFactory
+      import dagger.assisted.AssistedInject
+      
+      data class AssistedService @AssistedInject constructor(
+        val int: Int,
+        @Assisted val stringFactory: Function1<Int, String>
+      )
+      
+      @AssistedFactory
+      interface AssistedServiceFactory {
+        fun create(stringFactory: (Int) -> String): AssistedService
+      }
+      """
+    ) {
+      val factoryImplClass = assistedServiceFactory.implClass()
+      val generatedFactoryInstance = assistedService.factoryClass().createInstance(Provider { 5 })
+      val factoryImplInstance = factoryImplClass.createInstance(generatedFactoryInstance)
+
+      val staticMethods = factoryImplClass.declaredMethods.filter { it.isStatic }
+      assertThat(staticMethods).hasSize(1)
+
+      val factoryProvider = staticMethods.single { it.name == "create" }
+        .invoke(null, generatedFactoryInstance) as Provider<*>
+      assertThat(factoryProvider.get()::class.java).isEqualTo(factoryImplClass)
+
+      val lambdaArg = { num: Int -> num.toString() }
+
+      val assistedServiceInstance = factoryImplClass.declaredMethods
+        .filterNot { it.isStatic }
+        .last { it.name == "create" }
+        .invoke(factoryImplInstance, lambdaArg)
+
+      assertThat(assistedServiceInstance).isEqualTo(assistedService.createInstance(5, lambdaArg))
+    }
+  }
+
+  @Test fun `the factory function may substitute a lambda type with a Function type`() {
+    compile(
+      """
+      package com.squareup.test
+      
+      import dagger.assisted.Assisted
+      import dagger.assisted.AssistedFactory
+      import dagger.assisted.AssistedInject
+      
+      data class AssistedService @AssistedInject constructor(
+        val int: Int,
+        @Assisted val stringFactory: (Int) -> String
+      )
+      
+      @AssistedFactory
+      interface AssistedServiceFactory {
+        fun create(stringFactory: Function1<Int, String>): AssistedService
+      }
+      """
+    ) {
+      val factoryImplClass = assistedServiceFactory.implClass()
+      val generatedFactoryInstance = assistedService.factoryClass().createInstance(Provider { 5 })
+      val factoryImplInstance = factoryImplClass.createInstance(generatedFactoryInstance)
+
+      val staticMethods = factoryImplClass.declaredMethods.filter { it.isStatic }
+      assertThat(staticMethods).hasSize(1)
+
+      val factoryProvider = staticMethods.single { it.name == "create" }
+        .invoke(null, generatedFactoryInstance) as Provider<*>
+      assertThat(factoryProvider.get()::class.java).isEqualTo(factoryImplClass)
+
+      val lambdaArg = { num: Int -> num.toString() }
+
+      val assistedServiceInstance = factoryImplClass.declaredMethods
+        .filterNot { it.isStatic }
+        .last { it.name == "create" }
+        .invoke(factoryImplInstance, lambdaArg)
+
+      assertThat(assistedServiceInstance).isEqualTo(assistedService.createInstance(5, lambdaArg))
+    }
+  }
+
   @Test fun `the factory function may be provided by a generic super type from another module`() {
     compile(
       """