Merge "Fix composition locals that are provided in a nested scope" into androidx-main
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 674fab2..f8ace7f 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -2350,6 +2350,9 @@
                 // Invoke the scope's composition function
                 firstInRange.scope.compose(this)
 
+                // We could have moved out of a provider so the provider cache is invalid.
+                providerCache = null
+
                 // Restore the parent of the reader to the previous parent
                 reader.restoreParent(parent)
             } else {
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
index 64c54a5..468fd71 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
@@ -18,11 +18,13 @@
 
 import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentHashMapOf
 import androidx.compose.runtime.mock.EmptyApplier
+import androidx.compose.runtime.mock.MockViewValidator
 import androidx.compose.runtime.mock.TestMonotonicFrameClock
 import androidx.compose.runtime.mock.Text
 import androidx.compose.runtime.mock.compositionTest
 import androidx.compose.runtime.mock.expectChanges
 import androidx.compose.runtime.mock.expectNoChanges
+import androidx.compose.runtime.mock.revalidate
 import androidx.compose.runtime.mock.validate
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
@@ -46,6 +48,7 @@
 // Create a static CompositionLocal with an int value
 val LocalSomeStaticInt = staticCompositionLocalOf { 40 }
 
+@Stable
 class CompositionLocalTests {
 
     @Composable
@@ -643,6 +646,51 @@
         advance()
         assertEquals(setOf(0, 0, 0), actualValues)
     }
+
+    @Test // Regression test for b/233064044
+    fun testRecomposeCacheInvalidation() = compositionTest {
+        var state = mutableStateOf(0)
+
+        compose {
+            CacheInvalidate(state)
+        }
+
+        validate {
+            this.CacheInvalidate(state)
+        }
+
+        state.value++
+        advance()
+
+        revalidate()
+    }
+}
+
+val cacheLocal = staticCompositionLocalOf { "Unset" }
+@Composable
+fun CacheInvalidate(state: State<Int>) {
+    Text("${state.value}")
+    Text(cacheLocal.current)
+    CacheInvalidateSet {
+        Text("${state.value}")
+        Text(cacheLocal.current)
+    }
+    Text(cacheLocal.current)
+}
+
+@Composable
+fun CacheInvalidateSet(content: @Composable () -> Unit) {
+    CompositionLocalProvider(cacheLocal provides "Set") {
+        content()
+    }
+}
+
+fun MockViewValidator.CacheInvalidate(state: State<Int>) {
+    Text("${state.value}")
+    Text("Unset")
+    Text("${state.value}")
+    Text("Set")
+    Text("Unset")
 }
 
 data class SomeData(val value: String = "default")