Repository: square/anvil (kotlin)

Issue:
AnnotationArgumentReference.Psi does not parse string templates correctly
The string template parsing [Here](https://github.com/square/anvil/blob/855e55baeb8afdd81b22277f8477e86490ec5e2d/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnnotationArgumentReference.kt#L267) does not currently grab the entire string. When given a blob like

```kotlin
const val SOME_STR: String = "World!"

@AnnotatedWith(value = "Hello, $SOME_STR")
object SomeObj {}
```

`annotationArgRef.value<Any>()` returns the string `"Hello, "`. Seeing as the compiler enforces that all template entries are constantly resolvable the entire value should be resolvable, which would bring this in line with the other available expression types.

Pull request:
Fixes #631

Patch:
diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnnotationArgumentReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnnotationArgumentReference.kt
index a0b86a524..9b69a6439 100644
--- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnnotationArgumentReference.kt
+++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnnotationArgumentReference.kt
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.com.intellij.psi.PsiElement
 import org.jetbrains.kotlin.lexer.KtTokens
 import org.jetbrains.kotlin.name.FqName
 import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry
 import org.jetbrains.kotlin.psi.KtClass
 import org.jetbrains.kotlin.psi.KtClassLiteralExpression
 import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression
@@ -26,12 +27,13 @@ import org.jetbrains.kotlin.psi.KtNamedFunction
 import org.jetbrains.kotlin.psi.KtObjectDeclaration
 import org.jetbrains.kotlin.psi.KtPrefixExpression
 import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry
 import org.jetbrains.kotlin.psi.KtStringTemplateEntry
 import org.jetbrains.kotlin.psi.KtStringTemplateExpression
 import org.jetbrains.kotlin.psi.KtValueArgument
 import org.jetbrains.kotlin.psi.KtValueArgumentName
 import org.jetbrains.kotlin.psi.psiUtil.containingClass
-import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
+import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
 import org.jetbrains.kotlin.resolve.constants.ArrayValue
 import org.jetbrains.kotlin.resolve.constants.BooleanValue
 import org.jetbrains.kotlin.resolve.constants.ByteValue
@@ -263,11 +265,19 @@ public sealed class AnnotationArgumentReference {
           is KtCollectionLiteralExpression ->
             psiElement.children.map { parsePsiElement(it) }.convertToArrayIfNeeded()
           is KtConstantExpression, is KtPrefixExpression -> parsePrimitiveType(psiElement.text)
-          is KtStringTemplateExpression ->
-            psiElement
-              .getChildOfType<KtStringTemplateEntry>()
-              ?.text
-              ?: fail()
+          is KtStringTemplateExpression -> {
+            val parts = psiElement.getChildrenOfType<KtStringTemplateEntry>()
+
+            parts.map { templateEntry ->
+              if (templateEntry is KtSimpleNameStringTemplateEntry ||
+                templateEntry is KtBlockStringTemplateEntry
+              ) {
+                parsePsiElement(templateEntry.expression as PsiElement)
+              } else {
+                templateEntry.text
+              }
+            }.joinToString("").ifEmpty { fail() }
+          }
           is KtNameReferenceExpression -> {
             // Those are likely enum values or another primitive constant.
             resolveConstant(psiElement)?.let { return it }


Test patch:
diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/AnnotationReferenceTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/AnnotationReferenceTest.kt
index 8125d6203..ecc881f2e 100644
--- a/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/AnnotationReferenceTest.kt
+++ b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/AnnotationReferenceTest.kt
@@ -337,4 +337,128 @@ class AnnotationReferenceTest {
       assertThat(exitCode).isEqualTo(OK)
     }
   }
+
+  @Test fun `a string template annotation argument can be parsed`() {
+    compile(
+      """
+      package com.squareup.test
+
+      import kotlin.reflect.KClass
+ 
+      annotation class BindingKey(val name: String)
+
+      const val WORLD: String = "World!"
+
+      @BindingKey("Hello, ${'$'}WORLD")
+      interface SomeClass1
+      """,
+      allWarningsAsErrors = false,
+      codeGenerators = listOf(
+        simpleCodeGenerator { psiRef ->
+          if (psiRef.shortName in listOf("BindingKey"))
+            return@simpleCodeGenerator null
+
+          listOf(psiRef, psiRef.toDescriptorReference()).forEach { ref ->
+            val arguments = ref.annotations.single().arguments
+            assertThat(arguments[0].value<String>()).isEqualTo("Hello, World!")
+
+            assertThat(ref.annotations.single().toAnnotationSpec().toString()).isEqualTo(
+              "@com.squareup.test.BindingKey(name = \"Hello, World!\")"
+            )
+          }
+
+          null
+        }
+      )
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+    }
+  }
+
+  @Test fun `a string template annotation argument with no literals can be parsed`() {
+    compile(
+      """
+      package com.squareup.test
+
+      import kotlin.reflect.KClass
+ 
+      annotation class BindingKey(val name: String)
+
+      const val WORLD: String = "World!"
+
+      @BindingKey("${'$'}WORLD")
+      interface SomeClass1
+      """,
+      allWarningsAsErrors = false,
+      codeGenerators = listOf(
+        simpleCodeGenerator { psiRef ->
+          if (psiRef.shortName in listOf("BindingKey"))
+            return@simpleCodeGenerator null
+
+          listOf(psiRef, psiRef.toDescriptorReference()).forEach { ref ->
+            val arguments = ref.annotations.single().arguments
+            assertThat(arguments[0].value<String>()).isEqualTo("World!")
+
+            assertThat(ref.annotations.single().toAnnotationSpec().toString()).isEqualTo(
+              "@com.squareup.test.BindingKey(name = \"World!\")"
+            )
+          }
+
+          null
+        }
+      )
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+    }
+  }
+
+  @Test fun `a string template annotation argument with dot-qualified constants can be parsed`() {
+    compile(
+      """
+      package com.squareup.test
+
+      import com.squareup.test2.Abc
+      import com.squareup.test2.SomeObject
+      import kotlin.reflect.KClass
+ 
+      annotation class BindingKey(val name: String)
+
+      @BindingKey("${'$'}{SomeObject.HELLO_NESTED} ${'$'}{Abc.WORLD_NESTED}")
+      interface SomeClass1
+      """,
+      """
+      package com.squareup.test2
+      
+      object SomeObject {
+        const val HELLO_NESTED = "Hello nested,"
+      }
+      
+      class Abc {
+        companion object {
+          const val WORLD_NESTED = "World nested!"
+        }
+      }
+      """,
+      allWarningsAsErrors = false,
+      codeGenerators = listOf(
+        simpleCodeGenerator { psiRef ->
+          if (psiRef.shortName in listOf("BindingKey", "SomeObject", "Abc", "Companion"))
+            return@simpleCodeGenerator null
+
+          listOf(psiRef, psiRef.toDescriptorReference()).forEach { ref ->
+            val arguments = ref.annotations.single().arguments
+            assertThat(arguments[0].value<String>()).isEqualTo("Hello nested, World nested!")
+
+            assertThat(ref.annotations.single().toAnnotationSpec().toString()).isEqualTo(
+              "@com.squareup.test.BindingKey(name = \"Hello nested, World nested!\")"
+            )
+          }
+
+          null
+        }
+      )
+    ) {
+      assertThat(exitCode).isEqualTo(OK)
+    }
+  }
 }