Repository: square/anvil (kotlin)

Issue:
FR: Generate MapKeyCreator classes in factory gen
When using `@MapKey(unwrapValue = false)`, dagger generates creator classes for annotations that are consumed in components to create annotation instances. It would be useful to generate these in the signature Dagger expects as well.

```kotlin
// Given this
@MapKey(unwrapValue = false)
annotation class MultiScopeActivityKey(
  val value: KClass<out Activity>,
  val scope: KClass<*>,
)

// Generate this
object MultiScopeActivityKeyCreator {
  @JvmStatic
  fun createMultiScopeActivityKey(
    value: Class<out Activity>,
    scope: Class<*>
  ): MultiScopeActivityKey {
    return MultiScopeActivityKey(value.kotlin, scope.kotlin)
  }
}
```

The nice thing with Kotlin 1.7 is we can instantiate annotations directly, which is simpler than Dagger's standard mechanism of using AutoAnnotation

Pull request description:
Resolves #651

Patch:
diff --git a/compiler-utils/build.gradle b/compiler-utils/build.gradle
index 427c5e4d2..8cac66d64 100644
--- a/compiler-utils/build.gradle
+++ b/compiler-utils/build.gradle
@@ -24,6 +24,10 @@ dependencies {
   testFixturesImplementation deps.junit
   testFixturesImplementation deps.truth
 
+  // Necessary because this is what dagger uses when it runs to support instantiating annotations at runtime
+  testFixturesImplementation deps.auto.value.annotations
+  testFixturesImplementation deps.auto.value.processor
+
   // This workaround is needed to resolve classes in the IDE properly.
   if (isIdeSync) {
     compileOnly project(':compiler')
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 ec76c83c4..91d9bf407 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
@@ -70,6 +70,7 @@ public sealed class ClassReference : Comparable<ClassReference>, AnnotatedRefere
   public abstract fun isObject(): Boolean
   public abstract fun isCompanion(): Boolean
   public abstract fun isGenericClass(): Boolean
+  public abstract fun isAnnotationClass(): Boolean
   public abstract fun visibility(): Visibility
 
   /**
@@ -200,6 +201,8 @@ public sealed class ClassReference : Comparable<ClassReference>, AnnotatedRefere
 
     override fun isGenericClass(): Boolean = clazz.typeParameterList != null
 
+    override fun isAnnotationClass(): Boolean = clazz is KtClass && clazz.isAnnotation()
+
     override fun visibility(): Visibility {
       return when (val visibility = clazz.visibilityModifierTypeOrDefault()) {
         PUBLIC_KEYWORD -> PUBLIC
@@ -299,6 +302,8 @@ public sealed class ClassReference : Comparable<ClassReference>, AnnotatedRefere
 
     override fun isCompanion(): Boolean = DescriptorUtils.isCompanionObject(clazz)
 
+    override fun isAnnotationClass(): Boolean = DescriptorUtils.isAnnotationClass(clazz)
+
     override fun isGenericClass(): Boolean = clazz.declaredTypeParameters.isNotEmpty()
 
     override fun visibility(): Visibility {
diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MapKeyCreatorGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MapKeyCreatorGenerator.kt
new file mode 100644
index 000000000..3d27b8436
--- /dev/null
+++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/MapKeyCreatorGenerator.kt
@@ -0,0 +1,250 @@
+package com.squareup.anvil.compiler.codegen.dagger
+
+import com.google.auto.service.AutoService
+import com.squareup.anvil.compiler.api.AnvilContext
+import com.squareup.anvil.compiler.api.CodeGenerator
+import com.squareup.anvil.compiler.api.GeneratedFile
+import com.squareup.anvil.compiler.api.createGeneratedFile
+import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
+import com.squareup.anvil.compiler.internal.buildFile
+import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
+import com.squareup.anvil.compiler.internal.reference.ClassReference
+import com.squareup.anvil.compiler.internal.reference.PropertyReference
+import com.squareup.anvil.compiler.internal.reference.argumentAt
+import com.squareup.anvil.compiler.internal.reference.asClassName
+import com.squareup.anvil.compiler.internal.reference.asTypeName
+import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
+import com.squareup.anvil.compiler.internal.safePackageString
+import com.squareup.anvil.compiler.mapKeyFqName
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.BOOLEAN
+import com.squareup.kotlinpoet.BOOLEAN_ARRAY
+import com.squareup.kotlinpoet.BYTE
+import com.squareup.kotlinpoet.BYTE_ARRAY
+import com.squareup.kotlinpoet.CHAR
+import com.squareup.kotlinpoet.CHAR_ARRAY
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.DOUBLE
+import com.squareup.kotlinpoet.DOUBLE_ARRAY
+import com.squareup.kotlinpoet.FLOAT
+import com.squareup.kotlinpoet.FLOAT_ARRAY
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.INT
+import com.squareup.kotlinpoet.INT_ARRAY
+import com.squareup.kotlinpoet.LONG
+import com.squareup.kotlinpoet.LONG_ARRAY
+import com.squareup.kotlinpoet.ParameterizedTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.SHORT
+import com.squareup.kotlinpoet.SHORT_ARRAY
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.joinToCode
+import dagger.MapKey
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.psi.KtFile
+import java.io.File
+import kotlin.reflect.KClass
+
+/**
+ * Generates [MapKey] creator classes + static functions for cases where [MapKey.unwrapValue] is set
+ * to false.
+ *
+ * Implemented from eyeballing https://github.com/google/dagger/blob/b5990a0641a7860b760aa9055b90a99d06186af6/javatests/dagger/internal/codegen/MapKeyProcessorTest.java
+ */
+@AutoService(CodeGenerator::class)
+internal class MapKeyCreatorGenerator : PrivateCodeGenerator() {
+
+  override fun isApplicable(context: AnvilContext) = context.generateFactories
+
+  override fun generateCodePrivate(
+    codeGenDir: File,
+    module: ModuleDescriptor,
+    projectFiles: Collection<KtFile>
+  ) {
+    projectFiles
+      .classAndInnerClassReferences(module)
+      .filter { classRef ->
+        val mapKey = classRef.annotations.find { it.fqName == mapKeyFqName }
+        if (mapKey != null) {
+          mapKey.argumentAt("unwrapValue", 0)?.value<Boolean>() == false
+        } else {
+          false
+        }
+      }
+      .forEach { clazz ->
+        generateCreatorClass(codeGenDir, clazz)
+      }
+  }
+
+  private fun generateCreatorClass(
+    codeGenDir: File,
+    clazz: ClassReference
+  ): GeneratedFile {
+    // // Given this
+    // @MapKey(unwrapValue = false)
+    // annotation class ActivityKey(
+    //   val value: KClass<out Activity>,
+    //   val scope: KClass<*>,
+    // )
+    //
+    // // Generate this
+    // object ActivityKeyCreator {
+    //   @JvmStatic
+    //   fun createActivityKey(
+    //     value: Class<out Activity>,
+    //     scope: Class<*>
+    //   ): ActivityKey {
+    //     return ActivityKey(value.kotlin, scope.kotlin)
+    //   }
+    // }
+
+    val packageName = clazz.packageFqName.safePackageString()
+
+    if (!clazz.isAnnotationClass()) {
+      throw AnvilCompilationExceptionClassReference(
+        message = "@MapKey is only applicable to annotation classes.",
+        classReference = clazz
+      )
+    }
+
+    val className = clazz.asClassName()
+
+    val creatorsToGenerate = mutableSetOf<ClassReference>()
+
+    fun visitAnnotations(clazz: ClassReference) {
+      if (clazz.isAnnotationClass()) {
+        val added = creatorsToGenerate.add(clazz)
+        if (added) {
+          for (property in clazz.properties) {
+            val type = property.type().asClassReferenceOrNull()
+            if (type?.isAnnotationClass() == true) {
+              visitAnnotations(type)
+            }
+          }
+        }
+      }
+    }
+
+    // Populate all used annotations
+    visitAnnotations(clazz)
+
+    val creatorFunctions = creatorsToGenerate
+      .associateBy { annotationClass ->
+        annotationClass.asTypeName().rawTypeOrNull()
+          ?: throw AnvilCompilationExceptionClassReference(
+            message = "@MapKey is only applicable to non-generic annotation classes.",
+            classReference = annotationClass
+          )
+      }
+      .toSortedMap()
+      .map { (className, clazz) -> generateCreatorFunction(className, clazz) }
+
+    val simpleName = className.simpleNames.joinToString("_")
+    val generatedClassName = "${simpleName}Creator"
+    val content = FileSpec.buildFile(packageName, generatedClassName) {
+      addType(
+        TypeSpec.objectBuilder(generatedClassName)
+          .addFunctions(creatorFunctions)
+          .build()
+      )
+    }
+
+    return createGeneratedFile(codeGenDir, packageName, generatedClassName, content)
+  }
+
+  /**
+   * Generates a single static creator function for a given annotation [annotationClass].
+   */
+  private fun generateCreatorFunction(
+    className: ClassName,
+    annotationClass: ClassReference,
+  ): FunSpec {
+    val properties = annotationClass.properties
+      .map { AnnotationProperty(it) }
+      .associateBy { it.name }
+    return FunSpec.builder("create${className.simpleName}")
+      .addAnnotation(JvmStatic::class)
+      .apply {
+        properties.forEach { (name, property) ->
+          addParameter(name, property.javaType)
+        }
+      }
+      .addStatement(
+        "return %T(%L)",
+        className,
+        properties.entries.map { it.value.callExpression }.joinToCode()
+      )
+      .build()
+  }
+}
+
+private class AnnotationProperty(
+  val name: String,
+  val javaType: TypeName,
+  val callExpression: CodeBlock
+) {
+  companion object {
+    operator fun invoke(
+      property: PropertyReference
+    ): AnnotationProperty {
+      val name = property.name
+      val typeName = property.type().asTypeName()
+      val javaType = typeName.resolveJavaType()
+      val codeBlock = when {
+        javaType.rawTypeOrNull() == CLASS_CLASS_NAME -> CodeBlock.of("%L.kotlin", name)
+        typeName is ParameterizedTypeName &&
+          typeName.rawType == ARRAY &&
+          typeName.typeArguments[0].rawTypeOrNull() == KCLASS_CLASS_NAME -> {
+          // Dense but this avoids an intermediate list allocation compared to .map { ... }.toTypedArray()
+          CodeBlock.of("%1T(%2L.size)·{·%2L[it].kotlin·}", ARRAY, name)
+        }
+
+        else -> CodeBlock.of("%L", name)
+      }
+      return AnnotationProperty(
+        name,
+        javaType,
+        codeBlock
+      )
+    }
+  }
+}
+
+private val CLASS_CLASS_NAME = Class::class.asClassName()
+private val KCLASS_CLASS_NAME = KClass::class.asClassName()
+
+private fun TypeName.rawTypeOrNull(): ClassName? {
+  return when (this) {
+    is ClassName -> this
+    is ParameterizedTypeName -> rawType
+    else -> null
+  }
+}
+
+private fun TypeName.resolveJavaType(): TypeName {
+  var type = this
+  if (this is ParameterizedTypeName) {
+    if (rawType == KCLASS_CLASS_NAME) {
+      // This needs to be converted to a java.lang.Class type.
+      type = CLASS_CLASS_NAME.parameterizedBy(typeArguments.map { it.resolveJavaType() })
+    } else if (rawType == ARRAY) {
+      // These need to use their primitive forms where possible
+      type = when (val componentType = typeArguments.single()) {
+        BOOLEAN -> BOOLEAN_ARRAY
+        BYTE -> BYTE_ARRAY
+        CHAR -> CHAR_ARRAY
+        SHORT -> SHORT_ARRAY
+        INT -> INT_ARRAY
+        LONG -> LONG_ARRAY
+        FLOAT -> FLOAT_ARRAY
+        DOUBLE -> DOUBLE_ARRAY
+        else -> ARRAY.parameterizedBy(componentType.resolveJavaType())
+      }
+    }
+  }
+  return type
+}
diff --git a/dependencies.gradle b/dependencies.gradle
index 31a3e4b36..ad5bc5389 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -2,6 +2,7 @@ ext {
   ci = (System.getenv('CI') ?: 'false').toBoolean()
 
   autoServiceVersion = '1.0.1'
+  autoValueVersion = '1.10'
   daggerVersion = '2.39.1'
   espressoVersion = '3.2.0'
 
@@ -64,6 +65,10 @@ ext {
           service: [
               annotations: "com.google.auto.service:auto-service-annotations:$autoServiceVersion",
               processor: "com.google.auto.service:auto-service:$autoServiceVersion"
+          ],
+          value: [
+              annotations: "com.google.auto.value:auto-value-annotations:$autoValueVersion",
+              processor: "com.google.auto.value:auto-value:$autoValueVersion"
           ]
       ],
 


Test patch:
diff --git a/compiler-utils/src/testFixtures/java/com/squareup/anvil/compiler/internal/testing/AnvilCompilation.kt b/compiler-utils/src/testFixtures/java/com/squareup/anvil/compiler/internal/testing/AnvilCompilation.kt
index acb7fa7fd..063032026 100644
--- a/compiler-utils/src/testFixtures/java/com/squareup/anvil/compiler/internal/testing/AnvilCompilation.kt
+++ b/compiler-utils/src/testFixtures/java/com/squareup/anvil/compiler/internal/testing/AnvilCompilation.kt
@@ -1,5 +1,6 @@
 package com.squareup.anvil.compiler.internal.testing
 
+import com.google.auto.value.processor.AutoAnnotationProcessor
 import com.squareup.anvil.annotations.ExperimentalAnvilApi
 import com.squareup.anvil.compiler.AnvilCommandLineProcessor
 import com.squareup.anvil.compiler.AnvilComponentRegistrar
@@ -51,7 +52,7 @@ public class AnvilCompilation internal constructor(
         AnvilComponentRegistrar().also { it.addCodeGenerators(codeGenerators) }
       )
       if (enableDaggerAnnotationProcessor) {
-        annotationProcessors = listOf(ComponentProcessor())
+        annotationProcessors = listOf(ComponentProcessor(), AutoAnnotationProcessor())
       }
 
       val anvilCommandLineProcessor = AnvilCommandLineProcessor()
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/MapKeyCreatorGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/MapKeyCreatorGeneratorTest.kt
new file mode 100644
index 000000000..831710dd1
--- /dev/null
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/MapKeyCreatorGeneratorTest.kt
@@ -0,0 +1,235 @@
+package com.squareup.anvil.compiler.dagger
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.anvil.compiler.USE_IR
+import com.squareup.anvil.compiler.WARNINGS_AS_ERRORS
+import com.squareup.anvil.compiler.assumeIrBackend
+import com.squareup.anvil.compiler.internal.testing.compileAnvil
+import com.squareup.anvil.compiler.internal.testing.isStatic
+import com.squareup.anvil.compiler.isFullTestRun
+import com.tschuchort.compiletesting.KotlinCompilation.Result
+import org.intellij.lang.annotations.Language
+import org.jetbrains.kotlin.descriptors.runtime.components.tryLoadClass
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class MapKeyCreatorGeneratorTest(
+  private val useDagger: Boolean
+) {
+
+  companion object {
+    @Parameters(name = "Use Dagger: {0}")
+    @JvmStatic fun useDagger(): Collection<Any> {
+      return listOf(isFullTestRun(), false).distinct()
+    }
+  }
+
+  @Test fun `a creator class is generated`() {
+    assumeIrBackend()
+    compile(
+      """
+      package com.squareup.test
+      
+      import dagger.MapKey
+      import kotlin.reflect.KClass
+      import com.squareup.anvil.compiler.dagger.TestEnum
+      import com.squareup.anvil.compiler.dagger.TestAnnotation
+
+      @MapKey(unwrapValue = false)
+      annotation class ExampleMapKey(
+        val stringValue: String,
+        val stringArrayValue: Array<String>,
+        val annotationValue: TestAnnotation,
+        val annotationArrayValue: Array<TestAnnotation>,
+        val enumValue: TestEnum,
+        val enumArrayValue: Array<TestEnum>,
+        val kClassValue: KClass<out String>,
+        val kClassStarValue: KClass<*>,
+        val kClassArrayValue: Array<KClass<*>>,
+        val booleanValue: Boolean,
+        val byteValue: Byte,
+        val charValue: Char,
+        val shortValue: Short,
+        val intValue: Int,
+        val longValue: Long,
+        val floatValue: Float,
+        val doubleValue: Double,
+        val booleanArrayValue: BooleanArray,
+        val byteArrayValue: ByteArray,
+        val charArrayValue: CharArray,
+        val shortArrayValue: ShortArray,
+        val intArrayValue: IntArray,
+        val longArrayValue: LongArray,
+        val floatArrayValue: FloatArray,
+        val doubleArrayValue: DoubleArray,
+      )
+      """
+    ) {
+      val mapKeyClass = classLoader.loadClass("com.squareup.test.ExampleMapKey")
+      val creatorClass = classLoader.loadClass("com.squareup.test.ExampleMapKeyCreator")
+
+      val staticMethods = creatorClass.declaredMethods.filter { it.isStatic }
+      assertThat(staticMethods).hasSize(2)
+
+      // Assert that any used annotations also have creators
+      val testAnnotationInstance = staticMethods.single { it.name == "createTestAnnotation" }
+        .invoke(null, "test")
+
+      assertThat(testAnnotationInstance).isEqualTo(TestAnnotation("test"))
+
+      // Assert the primary creator is working
+      val mapKeyInstance = staticMethods.single { it.name == "createExampleMapKey" }
+        .invoke(
+          null,
+          /* stringValue*/ "stringValue",
+          /* stringArrayValue */ arrayOf("stringArrayValue"),
+          /* annotationValue */ TestAnnotation("annotationValue"),
+          /* annotationArrayValue */ arrayOf(TestAnnotation("annotationValue")),
+          /* enumValue */ TestEnum.A,
+          /* enumArrayValue */ arrayOf(TestEnum.A),
+          /* kClassValue */ String::class.java,
+          /* kClassStarValue */ String::class.java,
+          /* kClassArrayValue */ arrayOf(String::class.java),
+          /* booleanValue */ true,
+          /* byteValue */ 1.toByte(),
+          /* charValue */ 3.toChar(),
+          /* shortValue */ 2.toShort(),
+          /* intValue */ 4,
+          /* longValue */ 5L,
+          /* floatValue */ 6.0f,
+          /* doubleValue */ 7.0,
+          /* booleanArrayValue */ booleanArrayOf(true),
+          /* byteArrayValue */ byteArrayOf(1.toByte()),
+          /* charArrayValue */ charArrayOf(3.toChar()),
+          /* shortArrayValue */ shortArrayOf(2.toShort()),
+          /* intArrayValue */ intArrayOf(4),
+          /* longArrayValue */ longArrayOf(5L),
+          /* floatArrayValue */ floatArrayOf(6.0f),
+          /* doubleArrayValue */ doubleArrayOf(7.0),
+        )
+
+      assertThat(mapKeyInstance::class.java).isAssignableTo(mapKeyClass)
+    }
+  }
+
+  @Test fun `a recursive annotation still works`() {
+    assumeIrBackend()
+    compile(
+      """
+      package com.squareup.test
+      
+      import dagger.MapKey
+
+      annotation class RecursiveAnnotation(
+        val value: Array<RecursiveAnnotation> = []
+      )
+
+      @MapKey(unwrapValue = false)
+      annotation class ExampleMapKey(
+        val value: RecursiveAnnotation,
+      )
+      """
+    ) {
+      val recursiveClass = classLoader.loadClass("com.squareup.test.RecursiveAnnotation")
+      val mapKeyClass = classLoader.loadClass("com.squareup.test.ExampleMapKey")
+      val creatorClass = classLoader.loadClass("com.squareup.test.ExampleMapKeyCreator")
+
+      val staticMethods = creatorClass.declaredMethods.filter { it.isStatic }
+      assertThat(staticMethods).hasSize(2)
+
+      // Assert that any used annotations also have creators
+      val recursiveAnnotationInstance = staticMethods
+        .single { it.name == "createRecursiveAnnotation" }
+        .invoke(null, java.lang.reflect.Array.newInstance(recursiveClass, 0))
+
+      assertThat(recursiveAnnotationInstance).isInstanceOf(recursiveClass)
+
+      // Assert the primary creator is working
+      val mapKeyInstance = staticMethods.single { it.name == "createExampleMapKey" }
+        .invoke(
+          null,
+          /* stringValue*/ recursiveAnnotationInstance,
+        )
+
+      assertThat(mapKeyInstance::class.java).isAssignableTo(mapKeyClass)
+    }
+  }
+
+  @Test fun `do nothing if unwrapValue is not set to false`() {
+    assumeIrBackend()
+    compile(
+      """
+      package com.squareup.test
+      
+      import dagger.MapKey
+
+      @MapKey(unwrapValue = true)
+      annotation class ExampleMapKey(
+        val value: String,
+      )
+
+      @MapKey // Use default value
+      annotation class ExampleMapKey2(
+        val value: String,
+      )
+      """
+    ) {
+      classLoader.loadClass("com.squareup.test.ExampleMapKey")
+      val creatorClass = classLoader.tryLoadClass("com.squareup.test.ExampleMapKeyCreator")
+      assertThat(creatorClass).isNull()
+      classLoader.loadClass("com.squareup.test.ExampleMapKey2")
+      val creatorClass2 = classLoader.tryLoadClass("com.squareup.test.ExampleMapKeyCreator2")
+      assertThat(creatorClass2).isNull()
+    }
+  }
+
+  @Ignore("https://youtrack.jetbrains.com/issue/KT-54931")
+  @Test
+  fun `a nested creator class is generated`() {
+    assumeIrBackend()
+    compile(
+      """
+      package com.squareup.test
+      
+      import dagger.MapKey
+
+      class Container {
+        @MapKey(unwrapValue = false)
+        annotation class ExampleMapKey(
+          val stringValue: String,
+        )
+      }
+      """
+    ) {
+      val mapKeyClass = classLoader.loadClass("com.squareup.test.Container\$ExampleMapKey")
+      val creatorClass = classLoader.loadClass("com.squareup.test.Container_ExampleMapKeyCreator")
+
+      val staticMethods = creatorClass.declaredMethods.filter { it.isStatic }
+      assertThat(staticMethods).hasSize(1)
+
+      val mapKeyInstance = staticMethods.single { it.name == "createExampleMapKey" }
+        .invoke(
+          null,
+          /* stringValue*/ "stringValue",
+        )
+
+      assertThat(mapKeyInstance::class.java).isAssignableTo(mapKeyClass)
+    }
+  }
+
+  private fun compile(
+    @Language("kotlin") vararg sources: String,
+    block: Result.() -> Unit = { }
+  ): Result = compileAnvil(
+    sources = sources,
+    enableDaggerAnnotationProcessor = useDagger,
+    generateDaggerFactories = !useDagger,
+    useIR = USE_IR,
+    allWarningsAsErrors = WARNINGS_AS_ERRORS,
+    block = block
+  )
+}
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/TestClasses.kt b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/TestClasses.kt
index 225dda0d3..cd1663e90 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/TestClasses.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/TestClasses.kt
@@ -13,3 +13,7 @@ abstract class OuterClass constructor(
 ) {
   class InnerClass
 }
+
+enum class TestEnum { A, B, C }
+
+annotation class TestAnnotation(val value: String)