Repository: square/anvil (kotlin)

Issue:
Consider adding back a `ClassReference.toTypename()` API
For dealing with generically typed `ClassReference`s, it would be useful to be able to convert to `TypeName` and include any available type arguments

Pull request:
Fixes #566

Patch:
diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ClassReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ClassReference.kt
index d0f63ca65..c23f5b031 100644
--- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ClassReference.kt
+++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ClassReference.kt
@@ -11,6 +11,8 @@ 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
@@ -360,6 +362,15 @@ public fun ClassReference.generateClassName(
 @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.


Test patch:
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/ClassReferenceTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/ClassReferenceTest.kt
index 51afe99e4..5c8269d95 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/ClassReferenceTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/ClassReferenceTest.kt
@@ -3,6 +3,8 @@ package com.squareup.anvil.compiler.internal.reference
 import com.google.common.truth.Truth.assertThat
 import com.squareup.anvil.compiler.compile
 import com.squareup.anvil.compiler.internal.testing.simpleCodeGenerator
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.TypeSpec
 import com.tschuchort.compiletesting.KotlinCompilation.ExitCode.OK
 import org.jetbrains.kotlin.name.FqName
 import org.jetbrains.kotlin.psi.KtClassOrObject
@@ -311,6 +313,98 @@ class ClassReferenceTest {
       assertThat(exitCode).isEqualTo(OK)
     }
   }
+
+  @Test fun `a generic class can be converted to a typename`() {
+    compile(
+      """
+      package com.squareup.test
+
+      abstract class SomeClass1 : Lazy<String> {
+        abstract fun string(): String
+      }
+
+      class SomeClass2<T : List<String>>(
+        private val t: T
+      )
+
+      class SomeClass3<T : String, S : Map<List<String>, Int>>(
+        private val t: T
+      )
+      """,
+      allWarningsAsErrors = false,
+      codeGenerators = listOf(
+        simpleCodeGenerator { psiRef ->
+          val descriptorRef = psiRef.toDescriptorReference()
+
+          when (psiRef.shortName) {
+            "SomeClass1" -> {
+              val expected = ClassName("com.squareup.test", "SomeClass1")
+              assertThat(psiRef.asClassName()).isEqualTo(expected)
+              assertThat(descriptorRef.asClassName()).isEqualTo(expected)
+
+              assertThat(psiRef.asTypeName()).isEqualTo(expected)
+              assertThat(descriptorRef.asTypeName()).isEqualTo(expected)
+            }
+            "SomeClass2" -> {
+              val expected = ClassName("com.squareup.test", "SomeClass2")
+              assertThat(psiRef.asClassName()).isEqualTo(expected)
+              assertThat(descriptorRef.asClassName()).isEqualTo(expected)
+
+              assertThat(psiRef.asTypeName().toString())
+                .isEqualTo("com.squareup.test.SomeClass2<T>")
+              assertThat(descriptorRef.asTypeName().toString())
+                .isEqualTo("com.squareup.test.SomeClass2<T>")
+
+              val psiTypeSpec = TypeSpec.classBuilder("Class")
+                .addTypeVariables(psiRef.typeParameters.map { it.typeVariableName })
+                .build()
+
+              val descriptorTypeSpec = TypeSpec.classBuilder("Class")
+                .addTypeVariables(psiRef.typeParameters.map { it.typeVariableName })
+                .build()
+
+              assertThat(psiTypeSpec.toString())
+                .contains("public class Class<T : kotlin.collections.List<kotlin.String>>")
+              assertThat(descriptorTypeSpec.toString())
+                .contains("public class Class<T : kotlin.collections.List<kotlin.String>>")
+            }
+            "SomeClass3" -> {
+              val expected = ClassName("com.squareup.test", "SomeClass3")
+              assertThat(psiRef.asClassName()).isEqualTo(expected)
+              assertThat(descriptorRef.asClassName()).isEqualTo(expected)
+
+              assertThat(psiRef.asTypeName().toString())
+                .isEqualTo("com.squareup.test.SomeClass3<T, S>")
+              assertThat(descriptorRef.asTypeName().toString())
+                .isEqualTo("com.squareup.test.SomeClass3<T, S>")
+
+              val psiTypeSpec = TypeSpec.classBuilder("Class")
+                .addTypeVariables(psiRef.typeParameters.map { it.typeVariableName })
+                .build()
+
+              val descriptorTypeSpec = TypeSpec.classBuilder("Class")
+                .addTypeVariables(psiRef.typeParameters.map { it.typeVariableName })
+                .build()
+
+              assertThat(psiTypeSpec.toString()).contains(
+                "public class Class<T : kotlin.String, " +
+                  "S : kotlin.collections.Map<kotlin.collections.List<kotlin.String>, kotlin.Int>>"
+              )
+              assertThat(descriptorTypeSpec.toString()).contains(
+                "public class Class<T : kotlin.String, " +
+                  "S : kotlin.collections.Map<kotlin.collections.List<kotlin.String>, kotlin.Int>>"
+              )
+            }
+            else -> throw NotImplementedError(psiRef.shortName)
+          }
+
+          null
+        }
+      )
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+    }
+  }
 }
 
 fun ClassReference.toDescriptorReference(): ClassReference.Descriptor {