Fix propagating invalidations to subcompositions

Writes to state objects in a parent composition were
not being correctly propagated to sub-compositions
which could cause, for example, lambda captures to be
out-of-date.

Test: ./gradles :compose:r:r:tDUT
Change-Id: I89d7863fa61e286761b53cadd2ffd16820b44317
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index e7b3a19..6db216b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.runtime
 
+import androidx.compose.runtime.collection.IdentityArraySet
 import androidx.compose.runtime.snapshots.MutableSnapshot
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.snapshots.SnapshotApplyResult
@@ -416,9 +417,12 @@
                             }
 
                             // Perform recomposition for any invalidated composers
+                            val modifiedValues = IdentityArraySet<Any>()
                             try {
                                 toRecompose.fastForEach { composer ->
-                                    performRecompose(composer)?.let { toApply += it }
+                                    performRecompose(composer, modifiedValues)?.let {
+                                        toApply += it
+                                    }
                                 }
                                 if (toApply.isNotEmpty()) changeCount++
                             } finally {
@@ -494,7 +498,7 @@
         content: @Composable () -> Unit
     ) {
         val composerWasComposing = composition.isComposing
-        composing(composition) {
+        composing(composition, null) {
             composition.composeContent(content)
         }
         // TODO(b/143755743)
@@ -518,10 +522,14 @@
         }
     }
 
-    private fun performRecompose(composition: ControlledComposition): ControlledComposition? {
+    private fun performRecompose(
+        composition: ControlledComposition,
+        modifiedValues: IdentityArraySet<Any>
+    ): ControlledComposition? {
         if (composition.isComposing || composition.isDisposed) return null
         return if (
-            composing(composition) {
+            composing(composition, modifiedValues) {
+                modifiedValues.forEach { composition.recordWriteOf(it) }
                 composition.recompose()
             }
         ) composition else null
@@ -531,13 +539,23 @@
         return { value -> composition.recordReadOf(value) }
     }
 
-    private fun writeObserverOf(composition: ControlledComposition): (Any) -> Unit {
-        return { value -> composition.recordWriteOf(value) }
+    private fun writeObserverOf(
+        composition: ControlledComposition,
+        modifiedValues: IdentityArraySet<Any>?
+    ): (Any) -> Unit {
+        return { value ->
+            composition.recordWriteOf(value)
+            modifiedValues?.add(value)
+        }
     }
 
-    private inline fun <T> composing(composition: ControlledComposition, block: () -> T): T {
+    private inline fun <T> composing(
+        composition: ControlledComposition,
+        modifiedValues: IdentityArraySet<Any>?,
+        block: () -> T
+    ): T {
         val snapshot = Snapshot.takeMutableSnapshot(
-            readObserverOf(composition), writeObserverOf(composition)
+            readObserverOf(composition), writeObserverOf(composition, modifiedValues)
         )
         try {
             return snapshot.enter(block)
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
index 69e8be0..9f0ecb3 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -2608,6 +2608,40 @@
         validate()
     }
 
+    @Test
+    fun testModificationsPropagateToSubcomposition() = compositionTest {
+        var value by mutableStateOf(0)
+        val content: MutableState<@Composable () -> Unit> = mutableStateOf({ })
+        @Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
+        var subCompositionOccurred = false
+
+        @Composable
+        fun ComposeContent() {
+            content.value()
+        }
+
+        fun updateContent(parentValue: Int) {
+            content.value = {
+                subCompositionOccurred = true
+                assertEquals(parentValue, value)
+            }
+        }
+
+        compose {
+            updateContent(value)
+            TestSubcomposition {
+                ComposeContent()
+            }
+        }
+
+        subCompositionOccurred = false
+
+        value = 10
+        expectChanges()
+
+        assertTrue(subCompositionOccurred)
+    }
+
     /**
      * This test checks that an updated ComposableLambda capture used in a subcomposition
      * correctly invalidates that subcomposition and schedules recomposition of that subcomposition.