Repository: square/anvil (kotlin)

Issue:
`unwrappedTypes` failing for star projections
```kotlin
override val unwrappedTypes: List<Psi> by lazy(NONE) {
      type.typeElement!!.children
        .filterIsInstance<KtTypeArgumentList>()
        .singleOrNull()
        ?.children
        ?.filterIsInstance<KtTypeProjection>()
        ?.map { typeProjection ->
          typeProjection.children
            .filterIsInstance<KtTypeReference>()
            .single()
            .toTypeReference(declaringClass)
        } ?: emptyList()
    }
```
`filterIsInstance.single()` doesn’t seem to work for star projections

Sample:
```kotlin
@Inject lateinit var navigatorFactory: Navigator.Factory<*>
```
Works if I change it to this
```kotlin
@Inject lateinit var navigatorFactory: Navigator.Factory<out Navigator>
```

Pull request:
Fixes #620

Patch:
diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/FqName.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/FqName.kt
index ff5b1130c..21440118d 100644
--- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/FqName.kt
+++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/FqName.kt
@@ -37,13 +37,19 @@ internal val mergeSubcomponentFqName = MergeSubcomponent::class.fqName
 internal val mergeInterfacesFqName = MergeInterfaces::class.fqName
 internal val mergeModulesFqName = MergeModules::class.fqName
 
+internal val anyFqName = Any::class.fqName
+
 @ExperimentalAnvilApi
 public fun FqName.descendant(segments: String): FqName =
   if (isRoot) FqName(segments) else FqName("${asString()}.$segments")
 
 /** Returns the computed [FqName] representation of this [KClass]. */
 @ExperimentalAnvilApi
-public val KClass<*>.fqName: FqName get() = FqName(java.canonicalName)
+public val KClass<*>.fqName: FqName get() = FqName(
+  requireNotNull(qualifiedName) {
+    "An FqName cannot be created for a local class or class of an anonymous object."
+  }
+)
 
 /**
  * This function should only be used for package names. If the FqName is the root (no package at
diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeReference.kt
index 0d2ea8db8..2e447f291 100644
--- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeReference.kt
+++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeReference.kt
@@ -2,6 +2,7 @@ package com.squareup.anvil.compiler.internal.reference
 
 import com.squareup.anvil.annotations.ExperimentalAnvilApi
 import com.squareup.anvil.compiler.api.AnvilCompilationException
+import com.squareup.anvil.compiler.internal.anyFqName
 import com.squareup.anvil.compiler.internal.asClassName
 import com.squareup.anvil.compiler.internal.classDescriptor
 import com.squareup.anvil.compiler.internal.classDescriptorOrNull
@@ -61,7 +62,8 @@ public sealed class TypeReference {
   protected abstract val typeName: TypeName
 
   /**
-   * For `Map<String, Int>` this will return [`String`, `Int`].
+   * For `Map<String, Int>` this will return [`String`, `Int`]. For star projections like
+   * `List<*>` the result will be mapped to [Any].
    */
   public abstract val unwrappedTypes: List<TypeReference>
 
@@ -125,17 +127,23 @@ public sealed class TypeReference {
         null
       }
 
-    override val unwrappedTypes: List<Psi> by lazy(NONE) {
+    override val unwrappedTypes: List<TypeReference> by lazy(NONE) {
       type.typeElement!!.children
         .filterIsInstance<KtTypeArgumentList>()
         .singleOrNull()
         ?.children
         ?.filterIsInstance<KtTypeProjection>()
         ?.map { typeProjection ->
-          typeProjection.children
-            .filterIsInstance<KtTypeReference>()
-            .single()
-            .toTypeReference(declaringClass)
+          if (typeProjection.children.isEmpty() && typeProjection.text == "*") {
+            // Resolve `*` star projections to kotlin.Any similar to the descriptor APIs.
+            val anyDescriptor = module.asAnvilModuleDescriptor().resolveFqNameOrNull(anyFqName)!!
+            anyDescriptor.defaultType.toTypeReference(declaringClass)
+          } else {
+            typeProjection.children
+              .filterIsInstance<KtTypeReference>()
+              .single()
+              .toTypeReference(declaringClass)
+          }
         } ?: emptyList()
     }
 
@@ -302,7 +310,7 @@ public sealed class TypeReference {
 
   public class Descriptor internal constructor(
     public val type: KotlinType,
-    override val declaringClass: ClassReference.Descriptor
+    override val declaringClass: ClassReference
   ) : TypeReference() {
     override val classReference: ClassReference? by lazy(NONE) {
       type.classDescriptorOrNull()?.toClassReference(module)
@@ -413,7 +421,7 @@ public fun KtTypeReference.toTypeReference(declaringClass: ClassReference.Psi):
 }
 
 @ExperimentalAnvilApi
-public fun KotlinType.toTypeReference(declaringClass: ClassReference.Descriptor): Descriptor {
+public fun KotlinType.toTypeReference(declaringClass: ClassReference): Descriptor {
   return Descriptor(this, declaringClass)
 }
 
@@ -431,7 +439,7 @@ public fun AnvilCompilationExceptionTypReference(
   )
   is Descriptor -> {
     AnvilCompilationException(
-      classDescriptor = typeReference.declaringClass.clazz,
+      classDescriptor = typeReference.type.classDescriptor(),
       message = message + " Hint: ${typeReference.type}",
       cause = cause
     )
@@ -547,7 +555,7 @@ private fun ClassReference.Descriptor.superTypeOrNull(
  *  class Parent<T>
  *  class Child : Parent<String>
  *  ```
- *  In this example, [Child] is the [implementingClass] and [this] is [T]
+ *  In this example, `Child` is the [implementingClass] and [this] is `T`.
  *
  * @param implementingClass: The lowest descendant implementing class, e.g. Child from
  */


Test patch:
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TypeReferenceTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TypeReferenceTest.kt
index 08f27559e..d171b557f 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TypeReferenceTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TypeReferenceTest.kt
@@ -167,4 +167,51 @@ class TypeReferenceTest {
       assertThat(exitCode).isEqualTo(OK)
     }
   }
+
+  @Test fun `star projections are supported`() {
+    compile(
+      """
+      package com.squareup.test
+
+      class SomeClass {
+        lateinit var map1: Map<*, *>
+        lateinit var map2: Map<Int, *>
+        lateinit var map3: Map<*, String>
+        lateinit var map4: Map<Int, String>
+      }
+      """,
+      allWarningsAsErrors = false,
+      codeGenerators = listOf(
+        simpleCodeGenerator { psiRef ->
+          when (psiRef.shortName) {
+            "SomeClass" -> {
+              listOf(psiRef, psiRef.toDescriptorReference()).forEach { ref ->
+                assertThat(
+                  ref.properties.single { it.name == "map1" }.type().unwrappedTypes
+                    .map { it.asClassReference().shortName }
+                ).containsExactly("Any", "Any").inOrder()
+                assertThat(
+                  ref.properties.single { it.name == "map2" }.type().unwrappedTypes
+                    .map { it.asClassReference().shortName }
+                ).containsExactly("Int", "Any").inOrder()
+                assertThat(
+                  ref.properties.single { it.name == "map3" }.type().unwrappedTypes
+                    .map { it.asClassReference().shortName }
+                ).containsExactly("Any", "String").inOrder()
+                assertThat(
+                  ref.properties.single { it.name == "map4" }.type().unwrappedTypes
+                    .map { it.asClassReference().shortName }
+                ).containsExactly("Int", "String").inOrder()
+              }
+            }
+            else -> throw NotImplementedError(psiRef.shortName)
+          }
+
+          null
+        }
+      )
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+    }
+  }
 }