mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-28 21:05:13 +00:00
753 lines
28 KiB
Rust
753 lines
28 KiB
Rust
//! Implementation for `[salsa::query_group]` decorator.
|
|
|
|
use crate::parenthesized::Parenthesized;
|
|
use heck::ToUpperCamelCase;
|
|
use proc_macro::TokenStream;
|
|
use proc_macro2::Span;
|
|
use quote::ToTokens;
|
|
use syn::{
|
|
parse_macro_input, parse_quote, spanned::Spanned, Attribute, Error, FnArg, Ident, ItemTrait,
|
|
ReturnType, TraitItem, Type,
|
|
};
|
|
|
|
/// Implementation for `[salsa::query_group]` decorator.
|
|
pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream {
|
|
let group_struct = parse_macro_input!(args as Ident);
|
|
let input: ItemTrait = parse_macro_input!(input as ItemTrait);
|
|
// println!("args: {:#?}", args);
|
|
// println!("input: {:#?}", input);
|
|
|
|
let input_span = input.span();
|
|
let (trait_attrs, salsa_attrs) = filter_attrs(input.attrs);
|
|
if !salsa_attrs.is_empty() {
|
|
return Error::new(input_span, format!("unsupported attributes: {salsa_attrs:?}"))
|
|
.to_compile_error()
|
|
.into();
|
|
}
|
|
|
|
let trait_vis = input.vis;
|
|
let trait_name = input.ident;
|
|
let _generics = input.generics.clone();
|
|
let dyn_db = quote! { dyn #trait_name };
|
|
|
|
// Decompose the trait into the corresponding queries.
|
|
let mut queries = vec![];
|
|
for item in input.items {
|
|
if let TraitItem::Fn(method) = item {
|
|
let query_name = method.sig.ident.to_string();
|
|
|
|
let mut storage = QueryStorage::Memoized;
|
|
let mut cycle = None;
|
|
let mut invoke = None;
|
|
|
|
let mut query_type =
|
|
format_ident!("{}Query", query_name.to_string().to_upper_camel_case());
|
|
let mut num_storages = 0;
|
|
|
|
// Extract attributes.
|
|
let (attrs, salsa_attrs) = filter_attrs(method.attrs);
|
|
for SalsaAttr { name, tts, span } in salsa_attrs {
|
|
match name.as_str() {
|
|
"memoized" => {
|
|
storage = QueryStorage::Memoized;
|
|
num_storages += 1;
|
|
}
|
|
"dependencies" => {
|
|
storage = QueryStorage::LruDependencies;
|
|
num_storages += 1;
|
|
}
|
|
"lru" => {
|
|
storage = QueryStorage::LruMemoized;
|
|
num_storages += 1;
|
|
}
|
|
"input" => {
|
|
storage = QueryStorage::Input;
|
|
num_storages += 1;
|
|
}
|
|
"interned" => {
|
|
storage = QueryStorage::Interned;
|
|
num_storages += 1;
|
|
}
|
|
"cycle" => {
|
|
cycle = Some(parse_macro_input!(tts as Parenthesized<syn::Path>).0);
|
|
}
|
|
"invoke" => {
|
|
invoke = Some(parse_macro_input!(tts as Parenthesized<syn::Path>).0);
|
|
}
|
|
"query_type" => {
|
|
query_type = parse_macro_input!(tts as Parenthesized<Ident>).0;
|
|
}
|
|
"transparent" => {
|
|
storage = QueryStorage::Transparent;
|
|
num_storages += 1;
|
|
}
|
|
_ => {
|
|
return Error::new(span, format!("unknown salsa attribute `{name}`"))
|
|
.to_compile_error()
|
|
.into();
|
|
}
|
|
}
|
|
}
|
|
|
|
let sig_span = method.sig.span();
|
|
// Check attribute combinations.
|
|
if num_storages > 1 {
|
|
return Error::new(sig_span, "multiple storage attributes specified")
|
|
.to_compile_error()
|
|
.into();
|
|
}
|
|
match &invoke {
|
|
Some(invoke) if storage == QueryStorage::Input => {
|
|
return Error::new(
|
|
invoke.span(),
|
|
"#[salsa::invoke] cannot be set on #[salsa::input] queries",
|
|
)
|
|
.to_compile_error()
|
|
.into();
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
// Extract keys.
|
|
let mut iter = method.sig.inputs.iter();
|
|
let self_receiver = match iter.next() {
|
|
Some(FnArg::Receiver(sr)) if sr.mutability.is_none() => sr,
|
|
_ => {
|
|
return Error::new(
|
|
sig_span,
|
|
format!("first argument of query `{query_name}` must be `&self`"),
|
|
)
|
|
.to_compile_error()
|
|
.into();
|
|
}
|
|
};
|
|
let mut keys: Vec<(Ident, Type)> = vec![];
|
|
for (idx, arg) in iter.enumerate() {
|
|
match arg {
|
|
FnArg::Typed(syn::PatType { pat, ty, .. }) => keys.push((
|
|
match pat.as_ref() {
|
|
syn::Pat::Ident(ident_pat) => ident_pat.ident.clone(),
|
|
_ => format_ident!("key{}", idx),
|
|
},
|
|
Type::clone(ty),
|
|
)),
|
|
arg => {
|
|
return Error::new(
|
|
arg.span(),
|
|
format!("unsupported argument `{arg:?}` of `{query_name}`",),
|
|
)
|
|
.to_compile_error()
|
|
.into();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extract value.
|
|
let value = match method.sig.output {
|
|
ReturnType::Type(_, ref ty) => ty.as_ref().clone(),
|
|
ref ret => {
|
|
return Error::new(
|
|
ret.span(),
|
|
format!("unsupported return type `{ret:?}` of `{query_name}`"),
|
|
)
|
|
.to_compile_error()
|
|
.into();
|
|
}
|
|
};
|
|
|
|
// For `#[salsa::interned]` keys, we create a "lookup key" automatically.
|
|
//
|
|
// For a query like:
|
|
//
|
|
// fn foo(&self, x: Key1, y: Key2) -> u32
|
|
//
|
|
// we would create
|
|
//
|
|
// fn lookup_foo(&self, x: u32) -> (Key1, Key2)
|
|
let lookup_query = if let QueryStorage::Interned = storage {
|
|
let lookup_query_type =
|
|
format_ident!("{}LookupQuery", query_name.to_string().to_upper_camel_case());
|
|
let lookup_fn_name = format_ident!("lookup_{}", query_name);
|
|
let keys = keys.iter().map(|(_, ty)| ty);
|
|
let lookup_value: Type = parse_quote!((#(#keys),*));
|
|
let lookup_keys = vec![(parse_quote! { key }, value.clone())];
|
|
Some(Query {
|
|
query_type: lookup_query_type,
|
|
query_name: format!("{lookup_fn_name}"),
|
|
fn_name: lookup_fn_name,
|
|
receiver: self_receiver.clone(),
|
|
attrs: vec![], // FIXME -- some automatically generated docs on this method?
|
|
storage: QueryStorage::InternedLookup { intern_query_type: query_type.clone() },
|
|
keys: lookup_keys,
|
|
value: lookup_value,
|
|
invoke: None,
|
|
cycle: cycle.clone(),
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
queries.push(Query {
|
|
query_type,
|
|
query_name,
|
|
fn_name: method.sig.ident,
|
|
receiver: self_receiver.clone(),
|
|
attrs,
|
|
storage,
|
|
keys,
|
|
value,
|
|
invoke,
|
|
cycle,
|
|
});
|
|
|
|
queries.extend(lookup_query);
|
|
}
|
|
}
|
|
|
|
let group_storage = format_ident!("{}GroupStorage__", trait_name, span = Span::call_site());
|
|
|
|
let mut query_fn_declarations = proc_macro2::TokenStream::new();
|
|
let mut query_fn_definitions = proc_macro2::TokenStream::new();
|
|
let mut storage_fields = proc_macro2::TokenStream::new();
|
|
let mut queries_with_storage = vec![];
|
|
for query in &queries {
|
|
#[allow(clippy::map_identity)]
|
|
// clippy is incorrect here, this is not the identity function due to match ergonomics
|
|
let (key_names, keys): (Vec<_>, Vec<_>) = query.keys.iter().map(|(a, b)| (a, b)).unzip();
|
|
let value = &query.value;
|
|
let fn_name = &query.fn_name;
|
|
let qt = &query.query_type;
|
|
let attrs = &query.attrs;
|
|
let self_receiver = &query.receiver;
|
|
|
|
query_fn_declarations.extend(quote! {
|
|
#(#attrs)*
|
|
fn #fn_name(#self_receiver, #(#key_names: #keys),*) -> #value;
|
|
});
|
|
|
|
// Special case: transparent queries don't create actual storage,
|
|
// just inline the definition
|
|
if let QueryStorage::Transparent = query.storage {
|
|
let invoke = query.invoke_tt();
|
|
query_fn_definitions.extend(quote! {
|
|
fn #fn_name(&self, #(#key_names: #keys),*) -> #value {
|
|
#invoke(self, #(#key_names),*)
|
|
}
|
|
});
|
|
continue;
|
|
}
|
|
|
|
queries_with_storage.push(fn_name);
|
|
|
|
let tracing = if let QueryStorage::Memoized | QueryStorage::LruMemoized = query.storage {
|
|
let s = format!("{trait_name}::{fn_name}");
|
|
Some(quote! {
|
|
let _p = tracing::debug_span!(#s, #(#key_names = tracing::field::debug(&#key_names)),*).entered();
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
.into_iter();
|
|
|
|
query_fn_definitions.extend(quote! {
|
|
fn #fn_name(&self, #(#key_names: #keys),*) -> #value {
|
|
#(#tracing),*
|
|
// Create a shim to force the code to be monomorphized in the
|
|
// query crate. Our experiments revealed that this makes a big
|
|
// difference in total compilation time in rust-analyzer, though
|
|
// it's not totally obvious why that should be.
|
|
fn __shim(db: &(dyn #trait_name + '_), #(#key_names: #keys),*) -> #value {
|
|
salsa::plumbing::get_query_table::<#qt>(db).get((#(#key_names),*))
|
|
}
|
|
__shim(self, #(#key_names),*)
|
|
|
|
}
|
|
});
|
|
|
|
// For input queries, we need `set_foo` etc
|
|
if let QueryStorage::Input = query.storage {
|
|
let set_fn_name = format_ident!("set_{}", fn_name);
|
|
let set_with_durability_fn_name = format_ident!("set_{}_with_durability", fn_name);
|
|
|
|
let set_fn_docs = format!(
|
|
"
|
|
Set the value of the `{fn_name}` input.
|
|
|
|
See `{fn_name}` for details.
|
|
|
|
*Note:* Setting values will trigger cancellation
|
|
of any ongoing queries; this method blocks until
|
|
those queries have been cancelled.
|
|
"
|
|
);
|
|
|
|
let set_constant_fn_docs = format!(
|
|
"
|
|
Set the value of the `{fn_name}` input with a
|
|
specific durability instead of the default of
|
|
`Durability::LOW`. You can use `Durability::MAX`
|
|
to promise that its value will never change again.
|
|
|
|
See `{fn_name}` for details.
|
|
|
|
*Note:* Setting values will trigger cancellation
|
|
of any ongoing queries; this method blocks until
|
|
those queries have been cancelled.
|
|
"
|
|
);
|
|
|
|
query_fn_declarations.extend(quote! {
|
|
# [doc = #set_fn_docs]
|
|
fn #set_fn_name(&mut self, #(#key_names: #keys,)* value__: #value);
|
|
|
|
|
|
# [doc = #set_constant_fn_docs]
|
|
fn #set_with_durability_fn_name(&mut self, #(#key_names: #keys,)* value__: #value, durability__: salsa::Durability);
|
|
});
|
|
|
|
query_fn_definitions.extend(quote! {
|
|
fn #set_fn_name(&mut self, #(#key_names: #keys,)* value__: #value) {
|
|
fn __shim(db: &mut dyn #trait_name, #(#key_names: #keys,)* value__: #value) {
|
|
salsa::plumbing::get_query_table_mut::<#qt>(db).set((#(#key_names),*), value__)
|
|
}
|
|
__shim(self, #(#key_names,)* value__)
|
|
}
|
|
|
|
fn #set_with_durability_fn_name(&mut self, #(#key_names: #keys,)* value__: #value, durability__: salsa::Durability) {
|
|
fn __shim(db: &mut dyn #trait_name, #(#key_names: #keys,)* value__: #value, durability__: salsa::Durability) {
|
|
salsa::plumbing::get_query_table_mut::<#qt>(db).set_with_durability((#(#key_names),*), value__, durability__)
|
|
}
|
|
__shim(self, #(#key_names,)* value__ ,durability__)
|
|
}
|
|
});
|
|
}
|
|
|
|
// A field for the storage struct
|
|
storage_fields.extend(quote! {
|
|
#fn_name: std::sync::Arc<<#qt as salsa::Query>::Storage>,
|
|
});
|
|
}
|
|
|
|
// Emit the trait itself.
|
|
let mut output = {
|
|
let bounds = &input.supertraits;
|
|
quote! {
|
|
#(#trait_attrs)*
|
|
#trait_vis trait #trait_name :
|
|
salsa::Database +
|
|
salsa::plumbing::HasQueryGroup<#group_struct> +
|
|
#bounds
|
|
{
|
|
#query_fn_declarations
|
|
}
|
|
}
|
|
};
|
|
|
|
// Emit the query group struct and impl of `QueryGroup`.
|
|
output.extend(quote! {
|
|
/// Representative struct for the query group.
|
|
#trait_vis struct #group_struct { }
|
|
|
|
impl salsa::plumbing::QueryGroup for #group_struct
|
|
{
|
|
type DynDb = #dyn_db;
|
|
type GroupStorage = #group_storage;
|
|
}
|
|
});
|
|
|
|
// Emit an impl of the trait
|
|
output.extend({
|
|
let bounds = input.supertraits;
|
|
quote! {
|
|
impl<DB> #trait_name for DB
|
|
where
|
|
DB: #bounds,
|
|
DB: salsa::Database,
|
|
DB: salsa::plumbing::HasQueryGroup<#group_struct>,
|
|
{
|
|
#query_fn_definitions
|
|
}
|
|
}
|
|
});
|
|
|
|
let non_transparent_queries =
|
|
|| queries.iter().filter(|q| !matches!(q.storage, QueryStorage::Transparent));
|
|
|
|
// Emit the query types.
|
|
for (query, query_index) in non_transparent_queries().zip(0_u16..) {
|
|
let fn_name = &query.fn_name;
|
|
let qt = &query.query_type;
|
|
|
|
let storage = match &query.storage {
|
|
QueryStorage::Memoized => quote!(salsa::plumbing::MemoizedStorage<Self>),
|
|
QueryStorage::LruMemoized => quote!(salsa::plumbing::LruMemoizedStorage<Self>),
|
|
QueryStorage::LruDependencies => {
|
|
quote!(salsa::plumbing::LruDependencyStorage<Self>)
|
|
}
|
|
QueryStorage::Input if query.keys.is_empty() => {
|
|
quote!(salsa::plumbing::UnitInputStorage<Self>)
|
|
}
|
|
QueryStorage::Input => quote!(salsa::plumbing::InputStorage<Self>),
|
|
QueryStorage::Interned => quote!(salsa::plumbing::InternedStorage<Self>),
|
|
QueryStorage::InternedLookup { intern_query_type } => {
|
|
quote!(salsa::plumbing::LookupInternedStorage<Self, #intern_query_type>)
|
|
}
|
|
QueryStorage::Transparent => panic!("should have been filtered"),
|
|
};
|
|
let keys = query.keys.iter().map(|(_, ty)| ty);
|
|
let value = &query.value;
|
|
let query_name = &query.query_name;
|
|
|
|
// Emit the query struct and implement the Query trait on it.
|
|
output.extend(quote! {
|
|
#[derive(Default, Debug)]
|
|
#trait_vis struct #qt;
|
|
});
|
|
|
|
output.extend(quote! {
|
|
impl #qt {
|
|
/// Get access to extra methods pertaining to this query.
|
|
/// You can also use it to invoke this query.
|
|
#trait_vis fn in_db(self, db: &#dyn_db) -> salsa::QueryTable<'_, Self>
|
|
{
|
|
salsa::plumbing::get_query_table::<#qt>(db)
|
|
}
|
|
}
|
|
});
|
|
|
|
output.extend(quote! {
|
|
impl #qt {
|
|
/// Like `in_db`, but gives access to methods for setting the
|
|
/// value of an input. Not applicable to derived queries.
|
|
///
|
|
/// # Threads, cancellation, and blocking
|
|
///
|
|
/// Mutating the value of a query cannot be done while there are
|
|
/// still other queries executing. If you are using your database
|
|
/// within a single thread, this is not a problem: you only have
|
|
/// `&self` access to the database, but this method requires `&mut
|
|
/// self`.
|
|
///
|
|
/// However, if you have used `snapshot` to create other threads,
|
|
/// then attempts to `set` will **block the current thread** until
|
|
/// those snapshots are dropped (usually when those threads
|
|
/// complete). This also implies that if you create a snapshot but
|
|
/// do not send it to another thread, then invoking `set` will
|
|
/// deadlock.
|
|
///
|
|
/// Before blocking, the thread that is attempting to `set` will
|
|
/// also set a cancellation flag. This will cause any query
|
|
/// invocations in other threads to unwind with a `Cancelled`
|
|
/// sentinel value and eventually let the `set` succeed once all
|
|
/// threads have unwound past the salsa invocation.
|
|
///
|
|
/// If your query implementations are performing expensive
|
|
/// operations without invoking another query, you can also use
|
|
/// the `Runtime::unwind_if_cancelled` method to check for an
|
|
/// ongoing cancellation and bring those operations to a close,
|
|
/// thus allowing the `set` to succeed. Otherwise, long-running
|
|
/// computations may lead to "starvation", meaning that the
|
|
/// thread attempting to `set` has to wait a long, long time. =)
|
|
#trait_vis fn in_db_mut(self, db: &mut #dyn_db) -> salsa::QueryTableMut<'_, Self>
|
|
{
|
|
salsa::plumbing::get_query_table_mut::<#qt>(db)
|
|
}
|
|
}
|
|
|
|
impl<'d> salsa::QueryDb<'d> for #qt
|
|
{
|
|
type DynDb = #dyn_db + 'd;
|
|
type Group = #group_struct;
|
|
type GroupStorage = #group_storage;
|
|
}
|
|
|
|
// ANCHOR:Query_impl
|
|
impl salsa::Query for #qt
|
|
{
|
|
type Key = (#(#keys),*);
|
|
type Value = #value;
|
|
type Storage = #storage;
|
|
|
|
const QUERY_INDEX: u16 = #query_index;
|
|
|
|
const QUERY_NAME: &'static str = #query_name;
|
|
|
|
fn query_storage<'a>(
|
|
group_storage: &'a <Self as salsa::QueryDb<'_>>::GroupStorage,
|
|
) -> &'a std::sync::Arc<Self::Storage> {
|
|
&group_storage.#fn_name
|
|
}
|
|
|
|
fn query_storage_mut<'a>(
|
|
group_storage: &'a <Self as salsa::QueryDb<'_>>::GroupStorage,
|
|
) -> &'a std::sync::Arc<Self::Storage> {
|
|
&group_storage.#fn_name
|
|
}
|
|
}
|
|
// ANCHOR_END:Query_impl
|
|
});
|
|
|
|
// Implement the QueryFunction trait for queries which need it.
|
|
if query.storage.needs_query_function() {
|
|
let span = query.fn_name.span();
|
|
|
|
let key_names: Vec<_> = query.keys.iter().map(|(pat, _)| pat).collect();
|
|
let key_pattern = if query.keys.len() == 1 {
|
|
quote! { #(#key_names),* }
|
|
} else {
|
|
quote! { (#(#key_names),*) }
|
|
};
|
|
let invoke = query.invoke_tt();
|
|
|
|
let recover = if let Some(cycle_recovery_fn) = &query.cycle {
|
|
quote! {
|
|
const CYCLE_STRATEGY: salsa::plumbing::CycleRecoveryStrategy =
|
|
salsa::plumbing::CycleRecoveryStrategy::Fallback;
|
|
fn cycle_fallback(db: &<Self as salsa::QueryDb<'_>>::DynDb, cycle: &salsa::Cycle, #key_pattern: &<Self as salsa::Query>::Key)
|
|
-> <Self as salsa::Query>::Value {
|
|
#cycle_recovery_fn(
|
|
db,
|
|
cycle,
|
|
#(#key_names),*
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
quote! {
|
|
const CYCLE_STRATEGY: salsa::plumbing::CycleRecoveryStrategy =
|
|
salsa::plumbing::CycleRecoveryStrategy::Panic;
|
|
}
|
|
};
|
|
|
|
output.extend(quote_spanned! {span=>
|
|
// ANCHOR:QueryFunction_impl
|
|
impl salsa::plumbing::QueryFunction for #qt
|
|
{
|
|
fn execute(db: &<Self as salsa::QueryDb<'_>>::DynDb, #key_pattern: <Self as salsa::Query>::Key)
|
|
-> <Self as salsa::Query>::Value {
|
|
#invoke(db, #(#key_names),*)
|
|
}
|
|
|
|
#recover
|
|
}
|
|
// ANCHOR_END:QueryFunction_impl
|
|
});
|
|
}
|
|
}
|
|
|
|
let mut fmt_ops = proc_macro2::TokenStream::new();
|
|
for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) {
|
|
fmt_ops.extend(quote! {
|
|
#query_index => {
|
|
salsa::plumbing::QueryStorageOps::fmt_index(
|
|
&*self.#fn_name, db, input.key_index(), fmt,
|
|
)
|
|
}
|
|
});
|
|
}
|
|
|
|
let mut maybe_changed_ops = proc_macro2::TokenStream::new();
|
|
for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) {
|
|
maybe_changed_ops.extend(quote! {
|
|
#query_index => {
|
|
salsa::plumbing::QueryStorageOps::maybe_changed_after(
|
|
&*self.#fn_name, db, input.key_index(), revision
|
|
)
|
|
}
|
|
});
|
|
}
|
|
|
|
let mut cycle_recovery_strategy_ops = proc_macro2::TokenStream::new();
|
|
for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) {
|
|
cycle_recovery_strategy_ops.extend(quote! {
|
|
#query_index => {
|
|
salsa::plumbing::QueryStorageOps::cycle_recovery_strategy(
|
|
&*self.#fn_name
|
|
)
|
|
}
|
|
});
|
|
}
|
|
|
|
let mut for_each_ops = proc_macro2::TokenStream::new();
|
|
for Query { fn_name, .. } in non_transparent_queries() {
|
|
for_each_ops.extend(quote! {
|
|
op(&*self.#fn_name);
|
|
});
|
|
}
|
|
|
|
// Emit query group storage struct
|
|
output.extend(quote! {
|
|
#trait_vis struct #group_storage {
|
|
#storage_fields
|
|
}
|
|
|
|
// ANCHOR:group_storage_new
|
|
impl #group_storage {
|
|
#trait_vis fn new(group_index: u16) -> Self {
|
|
#group_storage {
|
|
#(
|
|
#queries_with_storage:
|
|
std::sync::Arc::new(salsa::plumbing::QueryStorageOps::new(group_index)),
|
|
)*
|
|
}
|
|
}
|
|
}
|
|
// ANCHOR_END:group_storage_new
|
|
|
|
// ANCHOR:group_storage_methods
|
|
impl #group_storage {
|
|
#trait_vis fn fmt_index(
|
|
&self,
|
|
db: &(#dyn_db + '_),
|
|
input: salsa::DatabaseKeyIndex,
|
|
fmt: &mut std::fmt::Formatter<'_>,
|
|
) -> std::fmt::Result {
|
|
match input.query_index() {
|
|
#fmt_ops
|
|
i => panic!("salsa: impossible query index {}", i),
|
|
}
|
|
}
|
|
|
|
#trait_vis fn maybe_changed_after(
|
|
&self,
|
|
db: &(#dyn_db + '_),
|
|
input: salsa::DatabaseKeyIndex,
|
|
revision: salsa::Revision,
|
|
) -> bool {
|
|
match input.query_index() {
|
|
#maybe_changed_ops
|
|
i => panic!("salsa: impossible query index {}", i),
|
|
}
|
|
}
|
|
|
|
#trait_vis fn cycle_recovery_strategy(
|
|
&self,
|
|
db: &(#dyn_db + '_),
|
|
input: salsa::DatabaseKeyIndex,
|
|
) -> salsa::plumbing::CycleRecoveryStrategy {
|
|
match input.query_index() {
|
|
#cycle_recovery_strategy_ops
|
|
i => panic!("salsa: impossible query index {}", i),
|
|
}
|
|
}
|
|
|
|
#trait_vis fn for_each_query(
|
|
&self,
|
|
_runtime: &salsa::Runtime,
|
|
mut op: &mut dyn FnMut(&dyn salsa::plumbing::QueryStorageMassOps),
|
|
) {
|
|
#for_each_ops
|
|
}
|
|
}
|
|
// ANCHOR_END:group_storage_methods
|
|
});
|
|
output.into()
|
|
}
|
|
|
|
struct SalsaAttr {
|
|
name: String,
|
|
tts: TokenStream,
|
|
span: Span,
|
|
}
|
|
|
|
impl std::fmt::Debug for SalsaAttr {
|
|
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(fmt, "{:?}", self.name)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<syn::Attribute> for SalsaAttr {
|
|
type Error = syn::Attribute;
|
|
|
|
fn try_from(attr: syn::Attribute) -> Result<SalsaAttr, syn::Attribute> {
|
|
if is_not_salsa_attr_path(attr.path()) {
|
|
return Err(attr);
|
|
}
|
|
|
|
let span = attr.span();
|
|
let name = attr.path().segments[1].ident.to_string();
|
|
let tts = match attr.meta {
|
|
syn::Meta::Path(path) => path.into_token_stream(),
|
|
syn::Meta::List(ref list) => {
|
|
let tts = list
|
|
.into_token_stream()
|
|
.into_iter()
|
|
.skip(attr.path().to_token_stream().into_iter().count());
|
|
proc_macro2::TokenStream::from_iter(tts)
|
|
}
|
|
syn::Meta::NameValue(nv) => nv.into_token_stream(),
|
|
}
|
|
.into();
|
|
|
|
Ok(SalsaAttr { name, tts, span })
|
|
}
|
|
}
|
|
|
|
fn is_not_salsa_attr_path(path: &syn::Path) -> bool {
|
|
path.segments.first().map(|s| s.ident != "salsa").unwrap_or(true) || path.segments.len() != 2
|
|
}
|
|
|
|
fn filter_attrs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<SalsaAttr>) {
|
|
let mut other = vec![];
|
|
let mut salsa = vec![];
|
|
// Leave non-salsa attributes untouched. These are
|
|
// attributes that don't start with `salsa::` or don't have
|
|
// exactly two segments in their path.
|
|
// Keep the salsa attributes around.
|
|
for attr in attrs {
|
|
match SalsaAttr::try_from(attr) {
|
|
Ok(it) => salsa.push(it),
|
|
Err(it) => other.push(it),
|
|
}
|
|
}
|
|
(other, salsa)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Query {
|
|
fn_name: Ident,
|
|
receiver: syn::Receiver,
|
|
query_name: String,
|
|
attrs: Vec<syn::Attribute>,
|
|
query_type: Ident,
|
|
storage: QueryStorage,
|
|
keys: Vec<(Ident, syn::Type)>,
|
|
value: syn::Type,
|
|
invoke: Option<syn::Path>,
|
|
cycle: Option<syn::Path>,
|
|
}
|
|
|
|
impl Query {
|
|
fn invoke_tt(&self) -> proc_macro2::TokenStream {
|
|
match &self.invoke {
|
|
Some(i) => i.into_token_stream(),
|
|
None => self.fn_name.clone().into_token_stream(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
enum QueryStorage {
|
|
Memoized,
|
|
LruDependencies,
|
|
LruMemoized,
|
|
Input,
|
|
Interned,
|
|
InternedLookup { intern_query_type: Ident },
|
|
Transparent,
|
|
}
|
|
|
|
impl QueryStorage {
|
|
/// Do we need a `QueryFunction` impl for this type of query?
|
|
fn needs_query_function(&self) -> bool {
|
|
match self {
|
|
QueryStorage::Input
|
|
| QueryStorage::Interned
|
|
| QueryStorage::InternedLookup { .. }
|
|
| QueryStorage::Transparent => false,
|
|
QueryStorage::Memoized | QueryStorage::LruMemoized | QueryStorage::LruDependencies => {
|
|
true
|
|
}
|
|
}
|
|
}
|
|
}
|