mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 06:11:35 +00:00
Merge commit 'ddf105b646
' into sync-from-ra
This commit is contained in:
parent
0816d49d83
commit
e41ab350d6
378 changed files with 14720 additions and 3111 deletions
145
crates/salsa/tests/incremental/constants.rs
Normal file
145
crates/salsa/tests/incremental/constants.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
use crate::implementation::{TestContext, TestContextImpl};
|
||||
use salsa::debug::DebugQueryTable;
|
||||
use salsa::Durability;
|
||||
|
||||
#[salsa::query_group(Constants)]
|
||||
pub(crate) trait ConstantsDatabase: TestContext {
|
||||
#[salsa::input]
|
||||
fn input(&self, key: char) -> usize;
|
||||
|
||||
fn add(&self, key1: char, key2: char) -> usize;
|
||||
|
||||
fn add3(&self, key1: char, key2: char, key3: char) -> usize;
|
||||
}
|
||||
|
||||
fn add(db: &dyn ConstantsDatabase, key1: char, key2: char) -> usize {
|
||||
db.log().add(format!("add({}, {})", key1, key2));
|
||||
db.input(key1) + db.input(key2)
|
||||
}
|
||||
|
||||
fn add3(db: &dyn ConstantsDatabase, key1: char, key2: char, key3: char) -> usize {
|
||||
db.log().add(format!("add3({}, {}, {})", key1, key2, key3));
|
||||
db.add(key1, key2) + db.input(key3)
|
||||
}
|
||||
|
||||
// Test we can assign a constant and things will be correctly
|
||||
// recomputed afterwards.
|
||||
#[test]
|
||||
fn invalidate_constant() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
db.set_input_with_durability('a', 44, Durability::HIGH);
|
||||
db.set_input_with_durability('b', 22, Durability::HIGH);
|
||||
assert_eq!(db.add('a', 'b'), 66);
|
||||
|
||||
db.set_input_with_durability('a', 66, Durability::HIGH);
|
||||
assert_eq!(db.add('a', 'b'), 88);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalidate_constant_1() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
|
||||
// Not constant:
|
||||
db.set_input('a', 44);
|
||||
assert_eq!(db.add('a', 'a'), 88);
|
||||
|
||||
// Becomes constant:
|
||||
db.set_input_with_durability('a', 44, Durability::HIGH);
|
||||
assert_eq!(db.add('a', 'a'), 88);
|
||||
|
||||
// Invalidates:
|
||||
db.set_input_with_durability('a', 33, Durability::HIGH);
|
||||
assert_eq!(db.add('a', 'a'), 66);
|
||||
}
|
||||
|
||||
// Test cases where we assign same value to 'a' after declaring it a
|
||||
// constant.
|
||||
#[test]
|
||||
fn set_after_constant_same_value() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
db.set_input_with_durability('a', 44, Durability::HIGH);
|
||||
db.set_input_with_durability('a', 44, Durability::HIGH);
|
||||
db.set_input('a', 44);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_constant() {
|
||||
let mut db = TestContextImpl::default();
|
||||
|
||||
db.set_input('a', 22);
|
||||
db.set_input('b', 44);
|
||||
assert_eq!(db.add('a', 'b'), 66);
|
||||
assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn durability() {
|
||||
let mut db = TestContextImpl::default();
|
||||
|
||||
db.set_input_with_durability('a', 22, Durability::HIGH);
|
||||
db.set_input_with_durability('b', 44, Durability::HIGH);
|
||||
assert_eq!(db.add('a', 'b'), 66);
|
||||
assert_eq!(Durability::HIGH, AddQuery.in_db(&db).durability(('a', 'b')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_constant() {
|
||||
let mut db = TestContextImpl::default();
|
||||
|
||||
db.set_input_with_durability('a', 22, Durability::HIGH);
|
||||
db.set_input('b', 44);
|
||||
assert_eq!(db.add('a', 'b'), 66);
|
||||
assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn becomes_constant_with_change() {
|
||||
let mut db = TestContextImpl::default();
|
||||
|
||||
db.set_input('a', 22);
|
||||
db.set_input('b', 44);
|
||||
assert_eq!(db.add('a', 'b'), 66);
|
||||
assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b')));
|
||||
|
||||
db.set_input_with_durability('a', 23, Durability::HIGH);
|
||||
assert_eq!(db.add('a', 'b'), 67);
|
||||
assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b')));
|
||||
|
||||
db.set_input_with_durability('b', 45, Durability::HIGH);
|
||||
assert_eq!(db.add('a', 'b'), 68);
|
||||
assert_eq!(Durability::HIGH, AddQuery.in_db(&db).durability(('a', 'b')));
|
||||
|
||||
db.set_input_with_durability('b', 45, Durability::MEDIUM);
|
||||
assert_eq!(db.add('a', 'b'), 68);
|
||||
assert_eq!(Durability::MEDIUM, AddQuery.in_db(&db).durability(('a', 'b')));
|
||||
}
|
||||
|
||||
// Test a subtle case in which an input changes from constant to
|
||||
// non-constant, but its value doesn't change. If we're not careful,
|
||||
// this can cause us to incorrectly consider derived values as still
|
||||
// being constant.
|
||||
#[test]
|
||||
fn constant_to_non_constant() {
|
||||
let mut db = TestContextImpl::default();
|
||||
|
||||
db.set_input_with_durability('a', 11, Durability::HIGH);
|
||||
db.set_input_with_durability('b', 22, Durability::HIGH);
|
||||
db.set_input_with_durability('c', 33, Durability::HIGH);
|
||||
|
||||
// Here, `add3` invokes `add`, which yields 33. Both calls are
|
||||
// constant.
|
||||
assert_eq!(db.add3('a', 'b', 'c'), 66);
|
||||
|
||||
db.set_input('a', 11);
|
||||
|
||||
// Here, `add3` invokes `add`, which *still* yields 33, but which
|
||||
// is no longer constant. Since value didn't change, we might
|
||||
// preserve `add3` unchanged, not noticing that it is no longer
|
||||
// constant.
|
||||
assert_eq!(db.add3('a', 'b', 'c'), 66);
|
||||
|
||||
// In that case, we would not get the correct result here, when
|
||||
// 'a' changes *again*.
|
||||
db.set_input('a', 22);
|
||||
assert_eq!(db.add3('a', 'b', 'c'), 77);
|
||||
}
|
14
crates/salsa/tests/incremental/counter.rs
Normal file
14
crates/salsa/tests/incremental/counter.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use std::cell::Cell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Counter {
|
||||
value: Cell<usize>,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
pub(crate) fn increment(&self) -> usize {
|
||||
let v = self.value.get();
|
||||
self.value.set(v + 1);
|
||||
v
|
||||
}
|
||||
}
|
59
crates/salsa/tests/incremental/implementation.rs
Normal file
59
crates/salsa/tests/incremental/implementation.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use crate::constants;
|
||||
use crate::counter::Counter;
|
||||
use crate::log::Log;
|
||||
use crate::memoized_dep_inputs;
|
||||
use crate::memoized_inputs;
|
||||
use crate::memoized_volatile;
|
||||
|
||||
pub(crate) trait TestContext: salsa::Database {
|
||||
fn clock(&self) -> &Counter;
|
||||
fn log(&self) -> &Log;
|
||||
}
|
||||
|
||||
#[salsa::database(
|
||||
constants::Constants,
|
||||
memoized_dep_inputs::MemoizedDepInputs,
|
||||
memoized_inputs::MemoizedInputs,
|
||||
memoized_volatile::MemoizedVolatile
|
||||
)]
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TestContextImpl {
|
||||
storage: salsa::Storage<TestContextImpl>,
|
||||
clock: Counter,
|
||||
log: Log,
|
||||
}
|
||||
|
||||
impl TestContextImpl {
|
||||
#[track_caller]
|
||||
pub(crate) fn assert_log(&self, expected_log: &[&str]) {
|
||||
let expected_text = &format!("{:#?}", expected_log);
|
||||
let actual_text = &format!("{:#?}", self.log().take());
|
||||
|
||||
if expected_text == actual_text {
|
||||
return;
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
for diff in dissimilar::diff(expected_text, actual_text) {
|
||||
match diff {
|
||||
dissimilar::Chunk::Delete(l) => println!("-{}", l),
|
||||
dissimilar::Chunk::Equal(l) => println!(" {}", l),
|
||||
dissimilar::Chunk::Insert(r) => println!("+{}", r),
|
||||
}
|
||||
}
|
||||
|
||||
panic!("incorrect log results");
|
||||
}
|
||||
}
|
||||
|
||||
impl TestContext for TestContextImpl {
|
||||
fn clock(&self) -> &Counter {
|
||||
&self.clock
|
||||
}
|
||||
|
||||
fn log(&self) -> &Log {
|
||||
&self.log
|
||||
}
|
||||
}
|
||||
|
||||
impl salsa::Database for TestContextImpl {}
|
16
crates/salsa/tests/incremental/log.rs
Normal file
16
crates/salsa/tests/incremental/log.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Log {
|
||||
data: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Log {
|
||||
pub(crate) fn add(&self, text: impl Into<String>) {
|
||||
self.data.borrow_mut().push(text.into());
|
||||
}
|
||||
|
||||
pub(crate) fn take(&self) -> Vec<String> {
|
||||
self.data.take()
|
||||
}
|
||||
}
|
9
crates/salsa/tests/incremental/main.rs
Normal file
9
crates/salsa/tests/incremental/main.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
mod constants;
|
||||
mod counter;
|
||||
mod implementation;
|
||||
mod log;
|
||||
mod memoized_dep_inputs;
|
||||
mod memoized_inputs;
|
||||
mod memoized_volatile;
|
||||
|
||||
fn main() {}
|
60
crates/salsa/tests/incremental/memoized_dep_inputs.rs
Normal file
60
crates/salsa/tests/incremental/memoized_dep_inputs.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use crate::implementation::{TestContext, TestContextImpl};
|
||||
|
||||
#[salsa::query_group(MemoizedDepInputs)]
|
||||
pub(crate) trait MemoizedDepInputsContext: TestContext {
|
||||
fn dep_memoized2(&self) -> usize;
|
||||
fn dep_memoized1(&self) -> usize;
|
||||
#[salsa::dependencies]
|
||||
fn dep_derived1(&self) -> usize;
|
||||
#[salsa::input]
|
||||
fn dep_input1(&self) -> usize;
|
||||
#[salsa::input]
|
||||
fn dep_input2(&self) -> usize;
|
||||
}
|
||||
|
||||
fn dep_memoized2(db: &dyn MemoizedDepInputsContext) -> usize {
|
||||
db.log().add("Memoized2 invoked");
|
||||
db.dep_memoized1()
|
||||
}
|
||||
|
||||
fn dep_memoized1(db: &dyn MemoizedDepInputsContext) -> usize {
|
||||
db.log().add("Memoized1 invoked");
|
||||
db.dep_derived1() * 2
|
||||
}
|
||||
|
||||
fn dep_derived1(db: &dyn MemoizedDepInputsContext) -> usize {
|
||||
db.log().add("Derived1 invoked");
|
||||
db.dep_input1() / 2
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revalidate() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
|
||||
db.set_dep_input1(0);
|
||||
|
||||
// Initial run starts from Memoized2:
|
||||
let v = db.dep_memoized2();
|
||||
assert_eq!(v, 0);
|
||||
db.assert_log(&["Memoized2 invoked", "Memoized1 invoked", "Derived1 invoked"]);
|
||||
|
||||
// After that, we first try to validate Memoized1 but wind up
|
||||
// running Memoized2. Note that we don't try to validate
|
||||
// Derived1, so it is invoked by Memoized1.
|
||||
db.set_dep_input1(44);
|
||||
let v = db.dep_memoized2();
|
||||
assert_eq!(v, 44);
|
||||
db.assert_log(&["Memoized1 invoked", "Derived1 invoked", "Memoized2 invoked"]);
|
||||
|
||||
// Here validation of Memoized1 succeeds so Memoized2 never runs.
|
||||
db.set_dep_input1(45);
|
||||
let v = db.dep_memoized2();
|
||||
assert_eq!(v, 44);
|
||||
db.assert_log(&["Memoized1 invoked", "Derived1 invoked"]);
|
||||
|
||||
// Here, a change to input2 doesn't affect us, so nothing runs.
|
||||
db.set_dep_input2(45);
|
||||
let v = db.dep_memoized2();
|
||||
assert_eq!(v, 44);
|
||||
db.assert_log(&[]);
|
||||
}
|
76
crates/salsa/tests/incremental/memoized_inputs.rs
Normal file
76
crates/salsa/tests/incremental/memoized_inputs.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use crate::implementation::{TestContext, TestContextImpl};
|
||||
|
||||
#[salsa::query_group(MemoizedInputs)]
|
||||
pub(crate) trait MemoizedInputsContext: TestContext {
|
||||
fn max(&self) -> usize;
|
||||
#[salsa::input]
|
||||
fn input1(&self) -> usize;
|
||||
#[salsa::input]
|
||||
fn input2(&self) -> usize;
|
||||
}
|
||||
|
||||
fn max(db: &dyn MemoizedInputsContext) -> usize {
|
||||
db.log().add("Max invoked");
|
||||
std::cmp::max(db.input1(), db.input2())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revalidate() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
|
||||
db.set_input1(0);
|
||||
db.set_input2(0);
|
||||
|
||||
let v = db.max();
|
||||
assert_eq!(v, 0);
|
||||
db.assert_log(&["Max invoked"]);
|
||||
|
||||
let v = db.max();
|
||||
assert_eq!(v, 0);
|
||||
db.assert_log(&[]);
|
||||
|
||||
db.set_input1(44);
|
||||
db.assert_log(&[]);
|
||||
|
||||
let v = db.max();
|
||||
assert_eq!(v, 44);
|
||||
db.assert_log(&["Max invoked"]);
|
||||
|
||||
let v = db.max();
|
||||
assert_eq!(v, 44);
|
||||
db.assert_log(&[]);
|
||||
|
||||
db.set_input1(44);
|
||||
db.assert_log(&[]);
|
||||
db.set_input2(66);
|
||||
db.assert_log(&[]);
|
||||
db.set_input1(64);
|
||||
db.assert_log(&[]);
|
||||
|
||||
let v = db.max();
|
||||
assert_eq!(v, 66);
|
||||
db.assert_log(&["Max invoked"]);
|
||||
|
||||
let v = db.max();
|
||||
assert_eq!(v, 66);
|
||||
db.assert_log(&[]);
|
||||
}
|
||||
|
||||
/// Test that invoking `set` on an input with the same value still
|
||||
/// triggers a new revision.
|
||||
#[test]
|
||||
fn set_after_no_change() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
|
||||
db.set_input2(0);
|
||||
|
||||
db.set_input1(44);
|
||||
let v = db.max();
|
||||
assert_eq!(v, 44);
|
||||
db.assert_log(&["Max invoked"]);
|
||||
|
||||
db.set_input1(44);
|
||||
let v = db.max();
|
||||
assert_eq!(v, 44);
|
||||
db.assert_log(&["Max invoked"]);
|
||||
}
|
77
crates/salsa/tests/incremental/memoized_volatile.rs
Normal file
77
crates/salsa/tests/incremental/memoized_volatile.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use crate::implementation::{TestContext, TestContextImpl};
|
||||
use salsa::{Database, Durability};
|
||||
|
||||
#[salsa::query_group(MemoizedVolatile)]
|
||||
pub(crate) trait MemoizedVolatileContext: TestContext {
|
||||
// Queries for testing a "volatile" value wrapped by
|
||||
// memoization.
|
||||
fn memoized2(&self) -> usize;
|
||||
fn memoized1(&self) -> usize;
|
||||
fn volatile(&self) -> usize;
|
||||
}
|
||||
|
||||
fn memoized2(db: &dyn MemoizedVolatileContext) -> usize {
|
||||
db.log().add("Memoized2 invoked");
|
||||
db.memoized1()
|
||||
}
|
||||
|
||||
fn memoized1(db: &dyn MemoizedVolatileContext) -> usize {
|
||||
db.log().add("Memoized1 invoked");
|
||||
let v = db.volatile();
|
||||
v / 2
|
||||
}
|
||||
|
||||
fn volatile(db: &dyn MemoizedVolatileContext) -> usize {
|
||||
db.log().add("Volatile invoked");
|
||||
db.salsa_runtime().report_untracked_read();
|
||||
db.clock().increment()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volatile_x2() {
|
||||
let query = TestContextImpl::default();
|
||||
|
||||
// Invoking volatile twice doesn't execute twice, because volatile
|
||||
// queries are memoized by default.
|
||||
query.volatile();
|
||||
query.volatile();
|
||||
query.assert_log(&["Volatile invoked"]);
|
||||
}
|
||||
|
||||
/// Test that:
|
||||
///
|
||||
/// - On the first run of R0, we recompute everything.
|
||||
/// - On the second run of R1, we recompute nothing.
|
||||
/// - On the first run of R1, we recompute Memoized1 but not Memoized2 (since Memoized1 result
|
||||
/// did not change).
|
||||
/// - On the second run of R1, we recompute nothing.
|
||||
/// - On the first run of R2, we recompute everything (since Memoized1 result *did* change).
|
||||
#[test]
|
||||
fn revalidate() {
|
||||
let mut query = TestContextImpl::default();
|
||||
|
||||
query.memoized2();
|
||||
query.assert_log(&["Memoized2 invoked", "Memoized1 invoked", "Volatile invoked"]);
|
||||
|
||||
query.memoized2();
|
||||
query.assert_log(&[]);
|
||||
|
||||
// Second generation: volatile will change (to 1) but memoized1
|
||||
// will not (still 0, as 1/2 = 0)
|
||||
query.salsa_runtime_mut().synthetic_write(Durability::LOW);
|
||||
query.memoized2();
|
||||
query.assert_log(&["Volatile invoked", "Memoized1 invoked"]);
|
||||
query.memoized2();
|
||||
query.assert_log(&[]);
|
||||
|
||||
// Third generation: volatile will change (to 2) and memoized1
|
||||
// will too (to 1). Therefore, after validating that Memoized1
|
||||
// changed, we now invoke Memoized2.
|
||||
query.salsa_runtime_mut().synthetic_write(Durability::LOW);
|
||||
|
||||
query.memoized2();
|
||||
query.assert_log(&["Volatile invoked", "Memoized1 invoked", "Memoized2 invoked"]);
|
||||
|
||||
query.memoized2();
|
||||
query.assert_log(&[]);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue