Repository: salsa-rs/salsa (Rust)

Issue:
on_demand_inputs test failed with derived query
The on-demand inputs feature does not invalidate derived queries. The unit test in *on_demand_inputs.rs* will be failed with derived queries.

```rust
#[salsa::query_group(DerivedQueryGroupStorage)]
trait DerivedQueryGroup: QueryGroup {
    fn double(&self, x: u32) -> u32;
}

fn double(db: &dyn DerivedQueryGroup, x: u32) -> u32 {
    db.a(x) * 2
}

#[salsa::database(QueryGroupStorage, DerivedQueryGroupStorage)]
#[derive(Default)]
struct Database {
// ...
}

#[test]
fn on_demand_input_works() {
    let mut db = Database::default();

    db.external_state.insert(1, 10);
    assert_eq!(db.b(1), 10);
    assert_eq!(db.a(1), 10);
    assert_eq!(db.double(1), 20);

    // We changed external state, but haven't signaled about this yet,
    // so we expect to see the old answer
    db.external_state.insert(1, 92);
    assert_eq!(db.b(1), 10);
    assert_eq!(db.a(1), 10);
    assert_eq!(db.double(1), 20);

    AQuery.in_db_mut(&mut db).invalidate(&1);

    assert_eq!(db.b(1), 92);
    assert_eq!(db.a(1), 92);
    assert_eq!(db.double(1), 184);  // <----failed
}
```
See also: #246  


Discussion:
In addition, it may panic:
panicked at 'assertion failed: old_memo.revisions.changed_at <= result.changed_at', salsa-0.17.0-pre.1\src\derived\slot.rs:264:21




Patch:
diff --git a/src/derived.rs b/src/derived.rs
index 044262084..3d144fad2 100644
--- a/src/derived.rs
+++ b/src/derived.rs
@@ -229,11 +229,11 @@ where
         Q::Key: Borrow<S>,
     {
         db.salsa_runtime_mut()
-            .with_incremented_revision(&mut |_new_revision| {
+            .with_incremented_revision(&mut |new_revision| {
                 let map_read = self.slot_map.read();
 
                 if let Some(slot) = map_read.get(key) {
-                    if let Some(durability) = slot.invalidate() {
+                    if let Some(durability) = slot.invalidate(new_revision) {
                         return Some(durability);
                     }
                 }
diff --git a/src/derived/slot.rs b/src/derived/slot.rs
index 8d39c32f9..95443c996 100644
--- a/src/derived/slot.rs
+++ b/src/derived/slot.rs
@@ -461,12 +461,16 @@ where
         }
     }
 
-    pub(super) fn invalidate(&self) -> Option<Durability> {
-        if let QueryState::Memoized(memo) = &mut *self.state.write() {
-            memo.revisions.inputs = MemoInputs::Untracked;
-            Some(memo.revisions.durability)
-        } else {
-            None
+    pub(super) fn invalidate(&self, new_revision: Revision) -> Option<Durability> {
+        log::debug!("Slot::invalidate(new_revision = {:?})", new_revision);
+        match &mut *self.state.write() {
+            QueryState::Memoized(memo) => {
+                memo.revisions.inputs = MemoInputs::Untracked;
+                memo.revisions.changed_at = new_revision;
+                Some(memo.revisions.durability)
+            }
+            QueryState::NotComputed => None,
+            QueryState::InProgress { .. } => unreachable!(),
         }
     }
 
@@ -495,7 +499,7 @@ where
             // entry, that must mean that it was found to be out
             // of date and removed.
             QueryState::NotComputed => {
-                debug!("maybe_changed_since({:?}: no value", self);
+                debug!("maybe_changed_since({:?}): no value", self);
                 return true;
             }
 
@@ -505,7 +509,7 @@ where
             QueryState::InProgress { id, waiting } => {
                 let other_id = *id;
                 debug!(
-                    "maybe_changed_since({:?}: blocking on thread `{:?}`",
+                    "maybe_changed_since({:?}): blocking on thread `{:?}`",
                     self, other_id,
                 );
                 match self.register_with_in_progress_thread(db, runtime, other_id, waiting) {
@@ -527,7 +531,7 @@ where
 
         if memo.revisions.verified_at == revision_now {
             debug!(
-                "maybe_changed_since({:?}: {:?} since up-to-date memo that changed at {:?}",
+                "maybe_changed_since({:?}): {:?} since up-to-date memo that changed at {:?}",
                 self,
                 memo.revisions.changed_at > revision,
                 memo.revisions.changed_at,
@@ -857,7 +861,10 @@ impl MemoRevisions {
         assert!(self.verified_at != revision_now);
         let verified_at = self.verified_at;
 
-        debug!("validate_memoized_value: verified_at={:#?}", self.inputs,);
+        debug!(
+            "validate_memoized_value: verified_at={:?}, revision_now={:?}, inputs={:#?}",
+            verified_at, revision_now, self.inputs
+        );
 
         if self.check_durability(db.salsa_runtime()) {
             return self.mark_value_as_verified(revision_now);
@@ -866,7 +873,7 @@ impl MemoRevisions {
         match &self.inputs {
             // We can't validate values that had untracked inputs; just have to
             // re-execute.
-            MemoInputs::Untracked { .. } => {
+            MemoInputs::Untracked => {
                 return false;
             }
 
diff --git a/src/runtime.rs b/src/runtime.rs
index 2c5a4374f..5743271bd 100644
--- a/src/runtime.rs
+++ b/src/runtime.rs
@@ -280,7 +280,8 @@ impl Runtime {
     ///
     /// This is mostly useful to control the durability level for [on-demand inputs](https://salsa-rs.github.io/salsa/common_patterns/on_demand_inputs.html).
     pub fn report_synthetic_read(&self, durability: Durability) {
-        self.local_state.report_synthetic_read(durability);
+        self.local_state
+            .report_synthetic_read(durability, self.current_revision());
     }
 
     /// Obviously, this should be user configurable at some point.
@@ -526,8 +527,9 @@ impl ActiveQuery {
         self.changed_at = changed_at;
     }
 
-    fn add_synthetic_read(&mut self, durability: Durability) {
+    fn add_synthetic_read(&mut self, durability: Durability, current_revision: Revision) {
         self.durability = self.durability.min(durability);
+        self.changed_at = current_revision;
     }
 }
 
diff --git a/src/runtime/local_state.rs b/src/runtime/local_state.rs
index 6c6c1fdba..04fd6f8e1 100644
--- a/src/runtime/local_state.rs
+++ b/src/runtime/local_state.rs
@@ -81,9 +81,9 @@ impl LocalState {
         }
     }
 
-    pub(super) fn report_synthetic_read(&self, durability: Durability) {
+    pub(super) fn report_synthetic_read(&self, durability: Durability, current_revision: Revision) {
         if let Some(top_query) = self.query_stack.borrow_mut().last_mut() {
-            top_query.add_synthetic_read(durability);
+            top_query.add_synthetic_read(durability, current_revision);
         }
     }
 }


Test patch:
diff --git a/tests/on_demand_inputs.rs b/tests/on_demand_inputs.rs
index 67c7ab131..0d4b19873 100644
--- a/tests/on_demand_inputs.rs
+++ b/tests/on_demand_inputs.rs
@@ -73,6 +73,12 @@ fn on_demand_input_works() {
     AQuery.in_db_mut(&mut db).invalidate(&1);
     assert_eq!(db.b(1), 92);
     assert_eq!(db.a(1), 92);
+
+    // Downstream queries should also be rerun if we call `a` first.
+    db.external_state.insert(1, 50);
+    AQuery.in_db_mut(&mut db).invalidate(&1);
+    assert_eq!(db.a(1), 50);
+    assert_eq!(db.b(1), 50);
 }
 
 #[test]