Count unused (anonymous) parameters as real parameters

Underscore parameter is considered unused (or anonymous), but is still a real parameter.

Fixes: 293367137
Test: FunctionalInterfaceTransformTests
Change-Id: I93f14f4e92646955b23312d751f1bb2c5adf5640
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
index f100c5d..e1af9da 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
@@ -4117,4 +4117,38 @@
             }
         """
     )
+
+    @Test
+    fun test_ComposableLambdaWithUnusedParameter() = verifyComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            val layoutLambda = @Composable { _: Int ->
+                Layout()
+            }
+        """,
+        extra = """
+            import androidx.compose.runtime.*
+
+            @Composable inline fun Layout() {}
+        """,
+        expectedTransformed = """
+            val layoutLambda: Function3<Int, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
+            internal object ComposableSingletons%TestKt {
+              val lambda-1: Function3<Int, Composer, Int, Unit> = composableLambdaInstance(<>, false) { <unused var>: Int, %composer: Composer?, %changed: Int ->
+                if (%changed and 0b01010001 !== 0b00010000 || !%composer.skipping) {
+                  if (isTraceInProgress()) {
+                    traceEventStart(<>, %changed, -1, <>)
+                  }
+                  Layout(%composer, 0)
+                  if (isTraceInProgress()) {
+                    traceEventEnd()
+                  }
+                } else {
+                  %composer.skipToGroupEnd()
+                }
+              }
+            }
+        """
+    )
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt
index 606cc70..98786eb 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt
@@ -387,4 +387,88 @@
             }
         """
     )
+
+    @Test
+    fun testComposableFunInterfaceWAnonymousParam() = verifyComposeIrTransform(
+        """
+            import androidx.compose.runtime.*
+
+            fun interface Consumer {
+                @Composable operator fun invoke(t: Int)
+            }
+
+            @Composable fun Test(int: Int) {
+                Example { _ ->
+                }
+            }
+
+            @Composable fun Example(consumer: Consumer) {
+            }
+        """,
+        """
+            interface Consumer {
+              @Composable
+              abstract fun invoke(t: Int, %composer: Composer?, %changed: Int)
+            }
+            @Composable
+            fun Test(int: Int, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Exampl...>:Test.kt")
+              if (%changed and 0b0001 !== 0 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Example(class <no name provided> : Consumer {
+                  @Composable
+                  override fun invoke(<unused var>: Int, %composer: Composer?, %changed: Int) {
+                    %composer = %composer.startRestartGroup(<>)
+                    sourceInformation(%composer, "C(invoke):Test.kt")
+                    if (%changed and 0b0001 !== 0 || !%composer.skipping) {
+                      if (isTraceInProgress()) {
+                        traceEventStart(<>, %changed, -1, <>)
+                      }
+                      Unit
+                      if (isTraceInProgress()) {
+                        traceEventEnd()
+                      }
+                    } else {
+                      %composer.skipToGroupEnd()
+                    }
+                    val tmp0_rcvr = <this>
+                    %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                      tmp0_rcvr.invoke(<unused var>, %composer, updateChangedFlags(%changed or 0b0001))
+                    }
+                  }
+                }
+                <no name provided>(), %composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(int, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+            @Composable
+            fun Example(consumer: Consumer, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
+              if (%changed and 0b0001 !== 0 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Example(consumer, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
 }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index e334859..07c13e3 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -1689,12 +1689,20 @@
         )
 
         if (defaultParam == null) {
-            require(parameterCount == defaultIndex) // param count is 1-based, index is 0-based
+            // param count is 1-based, index is 0-based
+            require(parameterCount == defaultIndex) {
+                "Expected $defaultIndex params for ${function.fqNameWhenAvailable}, " +
+                    "found $parameterCount"
+            }
         } else {
+            val expectedParamCount = defaultIndex +
+                defaultParamCount(contextParameterCount + numRealValueParameters)
             require(
-                parameterCount == defaultIndex +
-                    defaultParamCount(contextParameterCount + numRealValueParameters)
-            )
+                parameterCount == expectedParamCount
+            ) {
+                "Expected $expectedParamCount params for ${function.fqNameWhenAvailable}, " +
+                    "found $parameterCount"
+            }
         }
 
         val lambda = irLambdaExpression(
@@ -3875,7 +3883,6 @@
                         paramName.startsWith(KtxNameConventions.CHANGED_PARAMETER.identifier) ->
                             changedParams += param
                         paramName.startsWith("\$context_receiver_") ||
-                        paramName.startsWith("\$anonymous\$parameter") ||
                         paramName.startsWith("\$name\$for\$destructuring") ||
                         paramName.startsWith("\$noName_") ||
                         paramName == "\$this" -> Unit
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
index 04c7f2c..841ab83a 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
@@ -1572,7 +1572,8 @@
         get() = when {
             // FIR generates both <iterator> and tmp0_for_iterator...
             origin == IrDeclarationOrigin.FOR_LOOP_ITERATOR -> "<iterator>"
-            !useFir && origin == IrDeclarationOrigin.UNDERSCORE_PARAMETER -> "<unused var>"
+            // $anonymous$parameter$x vs $unused$var$x
+            origin == IrDeclarationOrigin.UNDERSCORE_PARAMETER -> "<unused var>"
             !useFir && name.asString().endsWith("_elvis_lhs") -> "<elvis>"
             !useFir && name.asString() == "\$this\$null" -> "<this>"
             else -> name.asString()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index 93604d6..e905ca7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -1953,11 +1953,12 @@
                     // This simulates a child that recomposes, for example due to a transition.
                     content(offset.value)
                 }
-                val assumeLayoutBeforeDraw = @Composable { _: Int ->
+                val assumeLayoutBeforeDraw = @Composable { value: Int ->
                     // This assumes a layout was done before the draw pass.
                     Layout(
                         content = {},
                         modifier = Modifier.drawBehind {
+                            assertEquals(offset.value, value)
                             assertTrue(laidOut)
                             latch.countDown()
                         }