mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-11-01 12:24:29 +00:00
Adapt for new cycle handling changing in Salsa
This commit is contained in:
parent
db72e2ff41
commit
57c019a3c5
15 changed files with 133 additions and 339 deletions
|
|
@ -10,10 +10,13 @@ use queries::{
|
|||
Queries, SetterKind, TrackedQuery, Transparent,
|
||||
};
|
||||
use quote::{ToTokens, format_ident, quote};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::visit_mut::VisitMut;
|
||||
use syn::{
|
||||
Attribute, FnArg, ItemTrait, Path, TraitItem, TraitItemFn, parse_quote, parse_quote_spanned,
|
||||
Attribute, FnArg, ItemTrait, Path, Token, TraitItem, TraitItemFn, parse_quote,
|
||||
parse_quote_spanned,
|
||||
};
|
||||
|
||||
mod queries;
|
||||
|
|
@ -106,6 +109,66 @@ enum QueryKind {
|
|||
Interned,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct Cycle {
|
||||
cycle_fn: Option<(syn::Ident, Path)>,
|
||||
cycle_initial: Option<(syn::Ident, Path)>,
|
||||
cycle_result: Option<(syn::Ident, Path)>,
|
||||
}
|
||||
|
||||
impl Parse for Cycle {
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
let options = Punctuated::<Option, Token![,]>::parse_terminated(input)?;
|
||||
let mut cycle_fn = None;
|
||||
let mut cycle_initial = None;
|
||||
let mut cycle_result = None;
|
||||
for option in options {
|
||||
let name = option.name.to_string();
|
||||
match &*name {
|
||||
"cycle_fn" => {
|
||||
if cycle_fn.is_some() {
|
||||
return Err(syn::Error::new_spanned(&option.name, "duplicate option"));
|
||||
}
|
||||
cycle_fn = Some((option.name, option.value));
|
||||
}
|
||||
"cycle_initial" => {
|
||||
if cycle_initial.is_some() {
|
||||
return Err(syn::Error::new_spanned(&option.name, "duplicate option"));
|
||||
}
|
||||
cycle_initial = Some((option.name, option.value));
|
||||
}
|
||||
"cycle_result" => {
|
||||
if cycle_result.is_some() {
|
||||
return Err(syn::Error::new_spanned(&option.name, "duplicate option"));
|
||||
}
|
||||
cycle_result = Some((option.name, option.value));
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&option.name,
|
||||
"unknown cycle option. Accepted values: `cycle_result`, `cycle_fn`, `cycle_initial`",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(Self { cycle_fn, cycle_initial, cycle_result });
|
||||
|
||||
struct Option {
|
||||
name: syn::Ident,
|
||||
value: Path,
|
||||
}
|
||||
|
||||
impl Parse for Option {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
let value = input.parse()?;
|
||||
Ok(Self { name, value })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn query_group_impl(
|
||||
_args: proc_macro::TokenStream,
|
||||
input: proc_macro::TokenStream,
|
||||
|
|
@ -155,8 +218,8 @@ pub(crate) fn query_group_impl(
|
|||
for SalsaAttr { name, tts, span } in salsa_attrs {
|
||||
match name.as_str() {
|
||||
"cycle" => {
|
||||
let path = syn::parse::<Parenthesized<Path>>(tts)?;
|
||||
cycle = Some(path.0.clone())
|
||||
let c = syn::parse::<Parenthesized<Cycle>>(tts)?;
|
||||
cycle = Some(c.0);
|
||||
}
|
||||
"input" => {
|
||||
if !pat_and_tys.is_empty() {
|
||||
|
|
@ -415,7 +478,7 @@ impl<T> syn::parse::Parse for Parenthesized<T>
|
|||
where
|
||||
T: syn::parse::Parse,
|
||||
{
|
||||
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
let content;
|
||||
syn::parenthesized!(content in input);
|
||||
content.parse::<T>().map(Parenthesized)
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@
|
|||
use quote::{ToTokens, format_ident, quote, quote_spanned};
|
||||
use syn::{FnArg, Ident, PatType, Path, Receiver, ReturnType, Type, parse_quote, spanned::Spanned};
|
||||
|
||||
use crate::Cycle;
|
||||
|
||||
pub(crate) struct TrackedQuery {
|
||||
pub(crate) trait_name: Ident,
|
||||
pub(crate) signature: syn::Signature,
|
||||
pub(crate) pat_and_tys: Vec<PatType>,
|
||||
pub(crate) invoke: Option<Path>,
|
||||
pub(crate) default: Option<syn::Block>,
|
||||
pub(crate) cycle: Option<Path>,
|
||||
pub(crate) cycle: Option<Cycle>,
|
||||
pub(crate) lru: Option<u32>,
|
||||
pub(crate) generated_struct: Option<GeneratedInputStruct>,
|
||||
}
|
||||
|
|
@ -34,12 +36,20 @@ impl ToTokens for TrackedQuery {
|
|||
let fn_ident = &sig.ident;
|
||||
let shim: Ident = format_ident!("{}_shim", fn_ident);
|
||||
|
||||
let annotation = match (self.cycle.clone(), self.lru) {
|
||||
(Some(cycle), Some(lru)) => quote!(#[salsa::tracked(lru = #lru, recovery_fn = #cycle)]),
|
||||
(Some(cycle), None) => quote!(#[salsa::tracked(recovery_fn = #cycle)]),
|
||||
(None, Some(lru)) => quote!(#[salsa::tracked(lru = #lru)]),
|
||||
(None, None) => quote!(#[salsa::tracked]),
|
||||
};
|
||||
let options = self
|
||||
.cycle
|
||||
.as_ref()
|
||||
.map(|Cycle { cycle_fn, cycle_initial, cycle_result }| {
|
||||
let cycle_fn = cycle_fn.as_ref().map(|(ident, path)| quote!(#ident=#path));
|
||||
let cycle_initial =
|
||||
cycle_initial.as_ref().map(|(ident, path)| quote!(#ident=#path));
|
||||
let cycle_result = cycle_result.as_ref().map(|(ident, path)| quote!(#ident=#path));
|
||||
let options = cycle_fn.into_iter().chain(cycle_initial).chain(cycle_result);
|
||||
quote!(#(#options),*)
|
||||
})
|
||||
.into_iter()
|
||||
.chain(self.lru.map(|lru| quote!(lru = #lru)));
|
||||
let annotation = quote!(#[salsa::tracked( #(#options),* )]);
|
||||
|
||||
let pat_and_tys = &self.pat_and_tys;
|
||||
let params = self
|
||||
|
|
|
|||
|
|
@ -1,265 +0,0 @@
|
|||
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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue