| use std::panic::UnwindSafe; | 
 |  | 
 | use expect_test::expect; | 
 | use query_group_macro::query_group; | 
 | use salsa::Setter; | 
 |  | 
 | /// The queries A, B, and C in `Database` can be configured | 
 | /// to invoke one another in arbitrary ways using this | 
 | /// enum. | 
 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] | 
 | enum CycleQuery { | 
 |     None, | 
 |     A, | 
 |     B, | 
 |     C, | 
 |     AthenC, | 
 | } | 
 |  | 
 | #[salsa::input] | 
 | struct ABC { | 
 |     a: CycleQuery, | 
 |     b: CycleQuery, | 
 |     c: CycleQuery, | 
 | } | 
 |  | 
 | impl CycleQuery { | 
 |     fn invoke(self, db: &dyn CycleDatabase, abc: ABC) -> Result<(), Error> { | 
 |         match self { | 
 |             CycleQuery::A => db.cycle_a(abc), | 
 |             CycleQuery::B => db.cycle_b(abc), | 
 |             CycleQuery::C => db.cycle_c(abc), | 
 |             CycleQuery::AthenC => { | 
 |                 let _ = db.cycle_a(abc); | 
 |                 db.cycle_c(abc) | 
 |             } | 
 |             CycleQuery::None => Ok(()), | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | #[salsa::input] | 
 | struct MyInput {} | 
 |  | 
 | #[salsa::tracked] | 
 | fn memoized_a(db: &dyn CycleDatabase, input: MyInput) { | 
 |     memoized_b(db, input) | 
 | } | 
 |  | 
 | #[salsa::tracked] | 
 | fn memoized_b(db: &dyn CycleDatabase, input: MyInput) { | 
 |     memoized_a(db, input) | 
 | } | 
 |  | 
 | #[salsa::tracked] | 
 | fn volatile_a(db: &dyn CycleDatabase, input: MyInput) { | 
 |     db.report_untracked_read(); | 
 |     volatile_b(db, input) | 
 | } | 
 |  | 
 | #[salsa::tracked] | 
 | fn volatile_b(db: &dyn CycleDatabase, input: MyInput) { | 
 |     db.report_untracked_read(); | 
 |     volatile_a(db, input) | 
 | } | 
 |  | 
 | #[track_caller] | 
 | fn extract_cycle(f: impl FnOnce() + UnwindSafe) -> salsa::Cycle { | 
 |     let v = std::panic::catch_unwind(f); | 
 |     if let Err(d) = &v { | 
 |         if let Some(cycle) = d.downcast_ref::<salsa::Cycle>() { | 
 |             return cycle.clone(); | 
 |         } | 
 |     } | 
 |     panic!("unexpected value: {:?}", v) | 
 | } | 
 |  | 
 | #[derive(PartialEq, Eq, Hash, Clone, Debug)] | 
 | struct Error { | 
 |     cycle: Vec<String>, | 
 | } | 
 |  | 
 | #[query_group] | 
 | trait CycleDatabase: salsa::Database { | 
 |     #[salsa::cycle(recover_a)] | 
 |     fn cycle_a(&self, abc: ABC) -> Result<(), Error>; | 
 |  | 
 |     #[salsa::cycle(recover_b)] | 
 |     fn cycle_b(&self, abc: ABC) -> Result<(), Error>; | 
 |  | 
 |     fn cycle_c(&self, abc: ABC) -> Result<(), Error>; | 
 | } | 
 |  | 
 | fn cycle_a(db: &dyn CycleDatabase, abc: ABC) -> Result<(), Error> { | 
 |     abc.a(db).invoke(db, abc) | 
 | } | 
 |  | 
 | fn recover_a( | 
 |     _db: &dyn CycleDatabase, | 
 |     cycle: &salsa::Cycle, | 
 |     _: CycleDatabaseData, | 
 |     _abc: ABC, | 
 | ) -> Result<(), Error> { | 
 |     Err(Error { cycle: cycle.participant_keys().map(|k| format!("{k:?}")).collect() }) | 
 | } | 
 |  | 
 | fn cycle_b(db: &dyn CycleDatabase, abc: ABC) -> Result<(), Error> { | 
 |     abc.b(db).invoke(db, abc) | 
 | } | 
 |  | 
 | fn recover_b( | 
 |     _db: &dyn CycleDatabase, | 
 |     cycle: &salsa::Cycle, | 
 |     _: CycleDatabaseData, | 
 |     _abc: ABC, | 
 | ) -> Result<(), Error> { | 
 |     Err(Error { cycle: cycle.participant_keys().map(|k| format!("{k:?}")).collect() }) | 
 | } | 
 |  | 
 | fn cycle_c(db: &dyn CycleDatabase, abc: ABC) -> Result<(), Error> { | 
 |     abc.c(db).invoke(db, abc) | 
 | } | 
 |  | 
 | #[test] | 
 | fn cycle_memoized() { | 
 |     let db = salsa::DatabaseImpl::new(); | 
 |  | 
 |     let input = MyInput::new(&db); | 
 |     let cycle = extract_cycle(|| memoized_a(&db, input)); | 
 |     let expected = expect![[r#" | 
 |         [ | 
 |             DatabaseKeyIndex( | 
 |                 IngredientIndex( | 
 |                     1, | 
 |                 ), | 
 |                 Id(0), | 
 |             ), | 
 |             DatabaseKeyIndex( | 
 |                 IngredientIndex( | 
 |                     2, | 
 |                 ), | 
 |                 Id(0), | 
 |             ), | 
 |         ] | 
 |     "#]]; | 
 |     expected.assert_debug_eq(&cycle.all_participants(&db)); | 
 | } | 
 |  | 
 | #[test] | 
 | fn inner_cycle() { | 
 |     //     A --> B <-- C | 
 |     //     ^     | | 
 |     //     +-----+ | 
 |     let db = salsa::DatabaseImpl::new(); | 
 |  | 
 |     let abc = ABC::new(&db, CycleQuery::B, CycleQuery::A, CycleQuery::B); | 
 |     let err = db.cycle_c(abc); | 
 |     assert!(err.is_err()); | 
 |     let expected = expect![[r#" | 
 |             [ | 
 |                 "cycle_a_shim(Id(1400))", | 
 |                 "cycle_b_shim(Id(1000))", | 
 |             ] | 
 |         "#]]; | 
 |     expected.assert_debug_eq(&err.unwrap_err().cycle); | 
 | } | 
 |  | 
 | #[test] | 
 | fn cycle_revalidate() { | 
 |     //     A --> B | 
 |     //     ^     | | 
 |     //     +-----+ | 
 |     let mut db = salsa::DatabaseImpl::new(); | 
 |     let abc = ABC::new(&db, CycleQuery::B, CycleQuery::A, CycleQuery::None); | 
 |     assert!(db.cycle_a(abc).is_err()); | 
 |     abc.set_b(&mut db).to(CycleQuery::A); // same value as default | 
 |     assert!(db.cycle_a(abc).is_err()); | 
 | } | 
 |  | 
 | #[test] | 
 | fn cycle_recovery_unchanged_twice() { | 
 |     //     A --> B | 
 |     //     ^     | | 
 |     //     +-----+ | 
 |     let mut db = salsa::DatabaseImpl::new(); | 
 |     let abc = ABC::new(&db, CycleQuery::B, CycleQuery::A, CycleQuery::None); | 
 |     assert!(db.cycle_a(abc).is_err()); | 
 |  | 
 |     abc.set_c(&mut db).to(CycleQuery::A); // force new revision | 
 |     assert!(db.cycle_a(abc).is_err()); | 
 | } | 
 |  | 
 | #[test] | 
 | fn cycle_appears() { | 
 |     let mut db = salsa::DatabaseImpl::new(); | 
 |     //     A --> B | 
 |     let abc = ABC::new(&db, CycleQuery::B, CycleQuery::None, CycleQuery::None); | 
 |     assert!(db.cycle_a(abc).is_ok()); | 
 |  | 
 |     //     A --> B | 
 |     //     ^     | | 
 |     //     +-----+ | 
 |     abc.set_b(&mut db).to(CycleQuery::A); | 
 |     assert!(db.cycle_a(abc).is_err()); | 
 | } | 
 |  | 
 | #[test] | 
 | fn cycle_disappears() { | 
 |     let mut db = salsa::DatabaseImpl::new(); | 
 |  | 
 |     //     A --> B | 
 |     //     ^     | | 
 |     //     +-----+ | 
 |     let abc = ABC::new(&db, CycleQuery::B, CycleQuery::A, CycleQuery::None); | 
 |     assert!(db.cycle_a(abc).is_err()); | 
 |  | 
 |     //     A --> B | 
 |     abc.set_b(&mut db).to(CycleQuery::None); | 
 |     assert!(db.cycle_a(abc).is_ok()); | 
 | } | 
 |  | 
 | #[test] | 
 | fn cycle_multiple() { | 
 |     // No matter whether we start from A or B, we get the same set of participants: | 
 |     let db = salsa::DatabaseImpl::new(); | 
 |  | 
 |     // Configuration: | 
 |     // | 
 |     //     A --> B <-- C | 
 |     //     ^     |     ^ | 
 |     //     +-----+     | | 
 |     //           |     | | 
 |     //           +-----+ | 
 |     // | 
 |     // Here, conceptually, B encounters a cycle with A and then | 
 |     // recovers. | 
 |     let abc = ABC::new(&db, CycleQuery::B, CycleQuery::AthenC, CycleQuery::A); | 
 |  | 
 |     let c = db.cycle_c(abc); | 
 |     let b = db.cycle_b(abc); | 
 |     let a = db.cycle_a(abc); | 
 |     let expected = expect![[r#" | 
 |         ( | 
 |             [ | 
 |                 "cycle_a_shim(Id(1000))", | 
 |                 "cycle_b_shim(Id(1400))", | 
 |             ], | 
 |             [ | 
 |                 "cycle_a_shim(Id(1000))", | 
 |                 "cycle_b_shim(Id(1400))", | 
 |             ], | 
 |             [ | 
 |                 "cycle_a_shim(Id(1000))", | 
 |                 "cycle_b_shim(Id(1400))", | 
 |             ], | 
 |         ) | 
 |     "#]]; | 
 |     expected.assert_debug_eq(&(c.unwrap_err().cycle, b.unwrap_err().cycle, a.unwrap_err().cycle)); | 
 | } | 
 |  | 
 | #[test] | 
 | fn cycle_mixed_1() { | 
 |     let db = salsa::DatabaseImpl::new(); | 
 |     //     A --> B <-- C | 
 |     //           |     ^ | 
 |     //           +-----+ | 
 |     let abc = ABC::new(&db, CycleQuery::B, CycleQuery::C, CycleQuery::B); | 
 |  | 
 |     let expected = expect![[r#" | 
 |         [ | 
 |             "cycle_b_shim(Id(1000))", | 
 |             "cycle_c_shim(Id(c00))", | 
 |         ] | 
 |     "#]]; | 
 |     expected.assert_debug_eq(&db.cycle_c(abc).unwrap_err().cycle); | 
 | } |