Repository: square/anvil (kotlin)

Issue:
Support for @Inject with generic types
Currently processing:
```
class InjectClass<T> @Inject constructor(val provider: Provider<T>)
```
is valid in Dagger but throws an error in Anvil

Pull request:
Fixes #111 
Feedbacks very appreciated, I don't have much experience with Kotlin psi 😅

Patch:
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoet.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoet.kt
index 493061f0b..71d84374a 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoet.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoet.kt
@@ -15,6 +15,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.STAR
 import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeVariableName
 import com.squareup.kotlinpoet.asClassName
 import com.squareup.kotlinpoet.jvm.jvmSuppressWildcards
 import dagger.Lazy
@@ -97,7 +98,13 @@ internal fun KtTypeReference.requireTypeName(
   fun KtTypeElement.requireTypeName(): TypeName {
     return when (this) {
       is KtUserType -> {
-        val className = requireFqName(module).asClassName(module)
+        val className = fqNameOrNull(module)?.asClassName(module)
+            ?: if (isTypeParameter()) {
+              return TypeVariableName(text)
+            } else {
+              throw AnvilCompilationException("Couldn't resolve fqName.", element = this)
+            }
+
         val typeArgumentList = typeArgumentList
         if (typeArgumentList != null) {
           className.parameterizedBy(
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/PsiUtils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/PsiUtils.kt
index dd590faaf..36fd0da77 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/PsiUtils.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/PsiUtils.kt
@@ -356,3 +356,9 @@ fun KtCallableDeclaration.requireTypeReference(): KtTypeReference =
   typeReference ?: throw AnvilCompilationException(
       "Couldn't obtain type reference.", element = this
   )
+
+fun KtUserType.isTypeParameter(): Boolean {
+  return parents.filterIsInstance<KtClassOrObject>().first().typeParameters.any {
+    it.textMatches(this)
+  }
+}
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/InjectConstructorFactoryGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/InjectConstructorFactoryGenerator.kt
index e284a59a3..7d2c510bd 100644
--- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/InjectConstructorFactoryGenerator.kt
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/InjectConstructorFactoryGenerator.kt
@@ -6,6 +6,7 @@ import com.squareup.anvil.compiler.codegen.addGeneratedByComment
 import com.squareup.anvil.compiler.codegen.asArgumentList
 import com.squareup.anvil.compiler.codegen.asClassName
 import com.squareup.anvil.compiler.codegen.classesAndInnerClasses
+import com.squareup.anvil.compiler.codegen.fqNameOrNull
 import com.squareup.anvil.compiler.codegen.hasAnnotation
 import com.squareup.anvil.compiler.codegen.mapToParameter
 import com.squareup.anvil.compiler.codegen.writeToString
@@ -19,6 +20,7 @@ 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.TypeVariableName
 import com.squareup.kotlinpoet.asClassName
 import com.squareup.kotlinpoet.jvm.jvmStatic
 import dagger.internal.Factory
@@ -57,11 +59,24 @@ internal class InjectConstructorFactoryGenerator : PrivateCodeGenerator() {
   ): GeneratedFile {
     val packageName = clazz.containingKtFile.packageFqName.asString()
     val className = "${clazz.generateClassName()}_Factory"
-    val classType = clazz.asClassName()
 
     val parameters = constructor.getValueParameters().mapToParameter(module)
 
+    val typeParameters = clazz.typeParameterList
+      ?.parameters
+      ?.mapNotNull {
+        val fqName = it.fqNameOrNull(module)
+        if (fqName == null) TypeVariableName(it.nameAsSafeName.asString()) else null
+      }
+      ?: emptyList()
+
     val factoryClass = ClassName(packageName, className)
+    val factoryClassParameterized =
+      if (typeParameters.isEmpty()) factoryClass else factoryClass.parameterizedBy(typeParameters)
+
+    val classType = clazz.asClassName().let {
+      if (typeParameters.isEmpty()) it else it.parameterizedBy(typeParameters)
+    }
 
     val content = FileSpec.builder(packageName, className)
         .apply {
@@ -71,6 +86,7 @@ internal class InjectConstructorFactoryGenerator : PrivateCodeGenerator() {
           } else {
             TypeSpec.classBuilder(factoryClass)
           }
+          typeParameters.forEach { classBuilder.addTypeVariable(it) }
 
           classBuilder
               .addSuperinterface(Factory::class.asClassName().parameterizedBy(classType))
@@ -117,6 +133,9 @@ internal class InjectConstructorFactoryGenerator : PrivateCodeGenerator() {
                         FunSpec.builder("create")
                             .jvmStatic()
                             .apply {
+                              if (typeParameters.isNotEmpty()) {
+                                addTypeVariables(typeParameters)
+                              }
                               if (canGenerateAnObject) {
                                 addStatement("return this")
                               } else {
@@ -131,17 +150,20 @@ internal class InjectConstructorFactoryGenerator : PrivateCodeGenerator() {
 
                                 addStatement(
                                     "return %T($argumentList)",
-                                    factoryClass
+                                    factoryClassParameterized
                                 )
                               }
                             }
-                            .returns(factoryClass)
+                            .returns(factoryClassParameterized)
                             .build()
                     )
                     .addFunction(
                         FunSpec.builder("newInstance")
                             .jvmStatic()
                             .apply {
+                              if (typeParameters.isNotEmpty()) {
+                                addTypeVariables(typeParameters)
+                              }
                               parameters.forEach { parameter ->
                                 addParameter(
                                     name = parameter.name,


Test patch:
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/InjectConstructorFactoryGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/InjectConstructorFactoryGeneratorTest.kt
index 87c9d6738..3e96424bb 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/InjectConstructorFactoryGeneratorTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/InjectConstructorFactoryGeneratorTest.kt
@@ -1149,6 +1149,56 @@ public final class InjectClass_Inner_Factory implements Factory<InjectClass.Inne
     }
   }
 
+  @Test fun `a factory class is generated for a class with a type parameter`() {
+    /*
+package com.squareup.test;
+
+import dagger.internal.Factory;
+import javax.annotation.Generated;
+import javax.inject.Provider;
+
+@Generated(
+    value = "dagger.internal.codegen.ComponentProcessor",
+    comments = "https://dagger.dev"
+)
+@SuppressWarnings({
+    "unchecked",
+    "rawtypes"
+})
+public final class InjectClass_Factory<T> implements Factory<InjectClass<T>> {
+  private final Provider<T> creatorProvider;
+  public InjectClass_Factory(Provider<T> creatorProvider) {
+    this.creatorProvider = creatorProvider;
+  }
+  @Override
+  public BaseViewModelFactory<T> get() {
+    return newInstance(creatorProvider);
+  }
+  public static <T> InjectClass_Factory<T> create(Provider<T> creatorProvider) {
+    return new InjectClass_Factory<T>(creatorProvider);
+  }
+  public static <T> InjectClass<T> newInstance(Provider<T> creator) {
+    return new InjectClass<T>(creator);
+  }
+}
+     */
+
+    compile(
+      """
+        package com.squareup.test
+        
+        import javax.inject.Inject
+        import javax.inject.Provider;
+        
+        class InjectClass<T> @Inject constructor(private val prov: Provider<T>)
+        """
+    ) {
+      val constructor = classLoader.loadClass("com.squareup.test.InjectClass")
+        .factoryClass().declaredConstructors.single()
+      assertThat(constructor.parameterTypes.toList()).containsExactly(Provider::class.java)
+    }
+  }
+
   private fun compile(
     source: String,
     block: Result.() -> Unit = { }