Make ksp method element isVarargs mirror the behavior in kapt.

Do not label ksp method element as vararg if the element is a suspend
function, or the vararg isn't the last element. Each parameter element
can be evaluated individually on whether they are varargs.

Bug:285051174

Test: tested with XExecutableElementTest

Change-Id: I6d914fd54d178b52e332ce9f4decdf20d05086c0
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
index d8258a7..23230b8 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
@@ -75,7 +75,6 @@
                 addModifiers(KModifier.SUSPEND)
             }
             // TODO(b/251316420): Add type variable names
-            val isVarArgs = executableElement.isVarArgs()
             val parameterTypes = resolvedType.parameterTypes.let {
                 // Drop the synthetic Continuation param of suspend functions, always at the last
                 // position.
@@ -86,7 +85,7 @@
                 val typeName: XTypeName
                 val modifiers: Array<KModifier>
                 // TODO(b/253268357): In Kotlin the vararg is not always the last param
-                if (isVarArgs && index == parameterTypes.size - 1) {
+                if (executableElement.parameters.get(index).isVarArgs()) {
                     typeName = (paramType as XArrayType).componentType.asTypeName()
                     modifiers = arrayOf(KModifier.VARARG)
                 } else {
@@ -108,4 +107,4 @@
             )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableParameterElement.kt
index 8454bd9..08343c8 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableParameterElement.kt
@@ -37,6 +37,11 @@
     fun isKotlinPropertyParam(): Boolean
 
     /**
+     * Returns `true` if this parameter is a vararg.
+     */
+    fun isVarArgs(): Boolean
+
+    /**
      * The enclosing [XExecutableElement] this parameter belongs to.
      */
     override val enclosingElement: XExecutableElement
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodParameter.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodParameter.kt
index 88fe3b7..8c78886 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodParameter.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodParameter.kt
@@ -40,6 +40,10 @@
         enclosingElement.isExtensionFunction() &&
         enclosingElement.parameters.first() == this
 
+    override fun isVarArgs() =
+        kotlinMetadata?.isVarArgs() ?: (enclosingElement.isVarArgs() &&
+            enclosingElement.parameters.last() == this)
+
     override fun isKotlinPropertyParam() =
         enclosingElement is JavacMethodElement &&
         enclosingElement.isKotlinPropertyMethod()
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
index 9e8131c..14a4a40 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
@@ -377,6 +377,7 @@
         get() = kmValueParameter.flags
     val name: String
         get() = kmValueParameter.name
+    fun isVarArgs() = kmValueParameter.varargElementType != null
     fun isNullable() = type.isNullable()
     fun hasDefault() = Flag.ValueParameter.DECLARES_DEFAULT_VALUE(flags)
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
index f47d467..17a4ee4 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
@@ -65,7 +65,11 @@
     }
 
     override fun isVarArgs(): Boolean {
-        return declaration.parameters.any { it.isVararg }
+        // TODO(b/254135327): Revisit with the introduction of a target language.
+        if (this is KspMethodElement && this.isSuspendFunction()) {
+            return false
+        }
+        return declaration.parameters.lastOrNull()?.isVararg ?: false
     }
 
     companion object {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
index a840aa4..3c52674 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
@@ -40,6 +40,8 @@
 
     override fun isKotlinPropertyParam() = false
 
+    override fun isVarArgs() = parameter.isVararg
+
     override val name: String
         get() = parameter.name?.asString() ?: "_no_param_name"
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
index de2c250..6a62daf 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
@@ -52,6 +52,8 @@
 
     override fun isKotlinPropertyParam() = false
 
+    override fun isVarArgs() = false
+
     override val name: String by lazy {
         // KAPT uses `$completion` but it doesn't check for conflicts, we do. Be aware that before
         // Kotlin 1.8.0 the param was named 'continuation'.
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
index 2067b96..ead1d25 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
@@ -245,6 +245,8 @@
 
             override fun isKotlinPropertyParam() = true
 
+            override fun isVarArgs() = false
+
             override val name: String by lazy {
                 val originalName = enclosingElement.accessor.parameter.name?.asString()
                 originalName.sanitizeAsJavaParameterName(0)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticReceiverParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticReceiverParameterElement.kt
index 0ebf74c..d72cc4e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticReceiverParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticReceiverParameterElement.kt
@@ -46,6 +46,8 @@
 
     override fun isKotlinPropertyParam() = false
 
+    override fun isVarArgs() = false
+
     override val name: String by lazy {
         // KAPT uses `$this$<functionName>`
         "$" + "this" + "$" + enclosingElement.name
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index 91ff9e3..994be2f 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -126,7 +126,9 @@
             """
             interface Subject {
                 fun method(vararg inputs: String)
-                suspend fun suspendMethod(vararg inputs: String);
+                suspend fun suspendMethod(vararg inputs: String)
+                fun method2(vararg inputs: String, arg: Int)
+                fun String.extFun(vararg inputs: String)
             }
             """.trimIndent()
         )
@@ -134,11 +136,34 @@
             sources = listOf(subject)
         ) {
             val element = it.processingEnv.requireTypeElement("Subject")
-            assertThat(element.getMethodByJvmName("method").isVarArgs()).isTrue()
-            if (it.isKsp) {
-                assertThat(element.getMethodByJvmName("suspendMethod").isVarArgs()).isTrue()
-            } else {
-                assertThat(element.getMethodByJvmName("suspendMethod").isVarArgs()).isFalse()
+
+            element.getMethodByJvmName("method").let { method ->
+                assertThat(method.isVarArgs()).isTrue()
+                assertThat(method.parameters).hasSize(1)
+                assertThat(method.parameters.single().isVarArgs()).isTrue()
+            }
+
+            element.getMethodByJvmName("suspendMethod").let { suspendMethod ->
+                assertThat(suspendMethod.isVarArgs()).isFalse()
+                assertThat(suspendMethod.parameters).hasSize(2)
+                assertThat(
+                    suspendMethod.parameters.first { it.name == "inputs" }.isVarArgs()
+                ).isTrue()
+            }
+
+            element.getMethodByJvmName("extFun").let { extFun ->
+                assertThat(extFun.isVarArgs()).isTrue()
+                assertThat(extFun.parameters).hasSize(2)
+                // kapt messed with parameter names, sometimes the synthetic parameter can use the
+                // second parameter's name.
+                assertThat(extFun.parameters.get(1).isVarArgs()).isTrue()
+            }
+
+            element.getMethodByJvmName("method2").let { method2 ->
+                assertThat(method2.isVarArgs()).isFalse()
+                assertThat(method2.parameters).hasSize(2)
+                assertThat(method2.parameters.first { it.name == "inputs" }.isVarArgs())
+                    .isTrue()
             }
         }
     }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index 6eeeb49..a737a73 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -20,6 +20,7 @@
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeSpec
+import androidx.room.compiler.processing.XExecutableParameterElement
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XMethodType
 import androidx.room.compiler.processing.XSuspendMethodType
@@ -59,7 +60,7 @@
 
     abstract fun extractReturnType(): XType
 
-    abstract fun extractParams(): List<XVariableElement>
+    abstract fun extractParams(): List<XExecutableParameterElement>
 
     fun extractQueryParams(query: ParsedQuery): List<QueryParameter> {
         return extractParams().map { variableElement ->
@@ -305,4 +306,4 @@
             )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt
index 71f283c..8743265 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt
@@ -69,12 +69,10 @@
                 TransactionMethod.CallType.CONCRETE
         }
 
-        val isVarArgs = executableElement.isVarArgs()
         val parameters = delegate.extractParams()
-        val processedParamNames = parameters.mapIndexed { index, param ->
+        val processedParamNames = parameters.map { param ->
             // Apply spread operator when delegating to a vararg parameter in Kotlin.
-            if (context.codeLanguage == CodeLanguage.KOTLIN &&
-                isVarArgs && index == parameters.size - 1) {
+            if (context.codeLanguage == CodeLanguage.KOTLIN && param.isVarArgs()) {
                 "*${param.name}"
             } else {
                 param.name