Fix use of stale record in DerivedState hash calculation

Updates DerivedState hash calculation to make sure it uses the current state record for given snapshot when calculating hash of dependent derived state.

Using stale record was forcing additional recalculations and invalidations with subcompositions.

Test: DerivedSnapshotStateTests
Fixes: 244621257
Change-Id: I6d3720aa8042dd4b14f3ff14b7fa7f6c97040d8a
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
index 76d772b..59a68e6 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
@@ -100,17 +100,17 @@
                             return@forEach
                         }
 
-                        if (stateObject is DerivedSnapshotState<*>) {
+                        // Find the first record without triggering an observer read.
+                        val record = if (stateObject is DerivedSnapshotState<*>) {
                             // eagerly access the parent derived states without recording the
                             // read
                             // that way we can be sure derived states in deps were recalculated,
                             // and are updated to the last values
-                            stateObject.refresh(stateObject.firstStateRecord, snapshot)
+                            stateObject.current(snapshot)
+                        } else {
+                            current(stateObject.firstStateRecord, snapshot)
                         }
 
-                        // Find the first record without triggering an observer read.
-                        val record = current(stateObject.firstStateRecord, snapshot)
-
                         hash = 31 * hash + identityHashCode(record)
                         hash = 31 * hash + record.snapshotId
                     }
@@ -120,10 +120,15 @@
         }
     }
 
-    fun refresh(record: StateRecord, snapshot: Snapshot) {
+    /**
+     * Get current record in snapshot. Forces recalculation if record is invalid to refresh
+     * state value.
+     *
+     * @return latest state record for the derived state.
+     */
+    fun current(snapshot: Snapshot): StateRecord =
         @Suppress("UNCHECKED_CAST")
-        currentRecord(record as ResultRecord<T>, snapshot, false, calculation)
-    }
+        currentRecord(current(first, snapshot), snapshot, false, calculation)
 
     private fun currentRecord(
         readable: ResultRecord<T>,
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/DerivedSnapshotStateTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/DerivedSnapshotStateTests.kt
index d7ec4ee..3457ab2 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/DerivedSnapshotStateTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/DerivedSnapshotStateTests.kt
@@ -244,6 +244,44 @@
         assertEquals(1, runs)
     }
 
+    @Test
+    fun stateUpdatedInSnapshotIsNotRecalculated() {
+        var runs = 0
+        val dependency = mutableStateOf(0)
+        val a = derivedStateOf {
+            runs++
+            dependency.value
+        }
+        val b = derivedStateOf {
+            a.value
+        }
+
+        Snapshot.takeMutableSnapshot().apply {
+            enter {
+                b.value
+            }
+            apply()
+        }
+
+        dependency.value++
+
+        Snapshot.takeMutableSnapshot().apply {
+            enter {
+                b.value
+            }
+            apply()
+        }
+
+        Snapshot.takeMutableSnapshot().apply {
+            enter {
+                b.value
+            }
+            apply()
+        }
+
+        assertEquals(2, runs)
+    }
+
     private var count = 0
 
     @BeforeTest