Count recursive local declarations as captures

Local declarations were already counted as captures for composable lambdas before, but the traversal ordering missed the recursive captures, converting some of them into singletons.

Test: Compiler test
Fixes: 318745941
Change-Id: I9097d1be71fb67b73e5027f723fd187c4272f6b4
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/KtxTransformationTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt
similarity index 95%
rename from compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/KtxTransformationTest.kt
rename to compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt
index a1cfb0c..739cbf2 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/KtxTransformationTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt
@@ -16,10 +16,11 @@
 
 package androidx.compose.compiler.plugins.kotlin
 
+import kotlin.test.assertFalse
 import org.junit.Assume.assumeFalse
 import org.junit.Test
 
-class KtxTransformationTest(useFir: Boolean) : AbstractCodegenTest(useFir) {
+class ComposeBytecodeCodegenTest(useFir: Boolean) : AbstractCodegenTest(useFir) {
 //    b/179279455
 //    @Test
 //    fun testObserveLowering() {
@@ -718,4 +719,27 @@
         """
         )
     }
+
+    @Test
+    fun testRecursiveLocalFunction() = validateBytecode(
+        """
+            import androidx.compose.runtime.*
+
+            @Composable fun Surface(content: @Composable () -> Unit) {}
+
+            @Composable
+            fun MyComposable(){
+                @Composable
+                fun LocalComposable(){
+                    Surface { LocalComposable() }
+                }
+            }
+        """,
+        validate = {
+            assertFalse(
+                it.contains("ComposableSingletons"),
+                message = "ComposableSingletons class should not be generated"
+            )
+        }
+    )
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt
index 2dd5570..a82b240 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt
@@ -117,9 +117,8 @@
         return loader
     }
 
-    protected fun testCompile(source: String, dumpClasses: Boolean = false) {
-        val loader = createClassLoader(listOf(SourceFile("Test.kt", source)))
-        if (dumpClasses) dumpClasses(loader)
+    protected fun testCompile(@Language("kotlin") source: String, dumpClasses: Boolean = false) {
+        classLoader(source, "Test.kt", dumpClasses)
     }
 
     protected val COMPOSE_VIEW_STUBS_IMPORTS = """
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index 99cacc5..f7efc83 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -428,23 +428,23 @@
             !declaration.isInline
 
         val context = FunctionContext(declaration, composable, canRemember)
-        declarationContextStack.push(context)
-        val result = super.visitFunction(declaration)
-        declarationContextStack.pop()
         if (declaration.isLocal) {
             declarationContextStack.recordLocalDeclaration(context)
         }
+        declarationContextStack.push(context)
+        val result = super.visitFunction(declaration)
+        declarationContextStack.pop()
         return result
     }
 
     override fun visitClass(declaration: IrClass): IrStatement {
         val context = ClassContext(declaration)
-        declarationContextStack.push(context)
-        val result = super.visitClass(declaration)
-        declarationContextStack.pop()
         if (declaration.isLocal) {
             declarationContextStack.recordLocalDeclaration(context)
         }
+        declarationContextStack.push(context)
+        val result = super.visitClass(declaration)
+        declarationContextStack.pop()
         return result
     }