Chayim Refael Friedman c58ddafe90 Make HirFileId, EditionedFileId and macro files Salsa struct
And make more queries non-interned.

Also flip the default for queries, now the default is to not intern and to intern a query you need to say `invoke_interned`.
2025-04-19 22:10:52 +03:00

266 lines
6.5 KiB
Rust

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, _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, _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(0))",
"cycle_b_shim(Id(0))",
]
"#]];
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(0))",
"cycle_b_shim(Id(0))",
],
[
"cycle_a_shim(Id(0))",
"cycle_b_shim(Id(0))",
],
[
"cycle_a_shim(Id(0))",
"cycle_b_shim(Id(0))",
],
)
"#]];
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(0))",
"cycle_c_shim(Id(0))",
]
"#]];
expected.assert_debug_eq(&db.cycle_c(abc).unwrap_err().cycle);
}