diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-19 09:25:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-19 09:25:56 +0000 |
commit | 018c4950b9406055dec02ef0fb52f132e2bb1e2c (patch) | |
tree | a835ebdf2088ef88fa681f8fad45f09922c1ae9a /vendor/rust-analyzer-salsa-macros | |
parent | Adding debian version 1.75.0+dfsg1-5. (diff) | |
download | rustc-018c4950b9406055dec02ef0fb52f132e2bb1e2c.tar.xz rustc-018c4950b9406055dec02ef0fb52f132e2bb1e2c.zip |
Merging upstream version 1.76.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/rust-analyzer-salsa-macros')
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/Cargo.toml | 40 | ||||
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/LICENSE-APACHE | 1 | ||||
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/LICENSE-MIT | 1 | ||||
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/README.md | 1 | ||||
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/src/database_storage.rs | 252 | ||||
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/src/lib.rs | 148 | ||||
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/src/parenthesized.rs | 12 | ||||
-rw-r--r-- | vendor/rust-analyzer-salsa-macros/src/query_group.rs | 744 |
9 files changed, 1200 insertions, 0 deletions
diff --git a/vendor/rust-analyzer-salsa-macros/.cargo-checksum.json b/vendor/rust-analyzer-salsa-macros/.cargo-checksum.json new file mode 100644 index 000000000..437040b97 --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"7b5c035feebfb9c64a9ecae2e27e4661cafeeb55fb4d9cab0856a68e0a7fdee5","LICENSE-APACHE":"9cff568488e9d56aae60f162404acdb15d8533a0202cf01f6eb0843b264f20ff","LICENSE-MIT":"94949dd83279f3ca3d0ce42ce0fc154d0ed817444454ee2925e903eec93949b6","README.md":"4cc4e4c8a5120617ab0800cd8f722a9797f7a9c04d4a159fd42a4419f82c7c30","src/database_storage.rs":"88408eeab2225017fc9256ef5bbeec07c672e43cdca90411c7847381af39926c","src/lib.rs":"217afcfeea2a80bd809aef07e73e17462599a4e3b005201c606bbc6998852e8e","src/parenthesized.rs":"e9acb2d441548a0ab8b460dc4ebd999cd03754c81c8e258caf148f75822c645a","src/query_group.rs":"af2dabaad884d62244ff60d2815e4ac1cb8cbaccb3919565a56b4bc124d025ca"},"package":"db72b0883f3592ade2be15a10583c75e0b269ec26e1190800fda2e2ce5ae6634"}
\ No newline at end of file diff --git a/vendor/rust-analyzer-salsa-macros/Cargo.toml b/vendor/rust-analyzer-salsa-macros/Cargo.toml new file mode 100644 index 000000000..1b4d8f393 --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/Cargo.toml @@ -0,0 +1,40 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "rust-analyzer-salsa-macros" +version = "0.17.0-pre.4" +authors = ["Salsa developers"] +description = "Procedural macros for the salsa crate" +readme = "README.md" +license = "Apache-2.0 OR MIT" +repository = "https://github.com/salsa-rs/salsa" + +[lib] +name = "salsa_macros" +proc-macro = true + +[dependencies.heck] +version = "0.4" + +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "2.0" +features = [ + "full", + "extra-traits", +] diff --git a/vendor/rust-analyzer-salsa-macros/LICENSE-APACHE b/vendor/rust-analyzer-salsa-macros/LICENSE-APACHE new file mode 100644 index 000000000..1cd601d0a --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE
\ No newline at end of file diff --git a/vendor/rust-analyzer-salsa-macros/LICENSE-MIT b/vendor/rust-analyzer-salsa-macros/LICENSE-MIT new file mode 100644 index 000000000..b2cfbdc7b --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT
\ No newline at end of file diff --git a/vendor/rust-analyzer-salsa-macros/README.md b/vendor/rust-analyzer-salsa-macros/README.md new file mode 100644 index 000000000..fe8400541 --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/README.md @@ -0,0 +1 @@ +../../README.md
\ No newline at end of file diff --git a/vendor/rust-analyzer-salsa-macros/src/database_storage.rs b/vendor/rust-analyzer-salsa-macros/src/database_storage.rs new file mode 100644 index 000000000..0b70e7c42 --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/src/database_storage.rs @@ -0,0 +1,252 @@ +use heck::ToSnakeCase;
+use proc_macro::TokenStream;
+use syn::parse::{Parse, ParseStream};
+use syn::punctuated::Punctuated;
+use syn::{Ident, ItemStruct, Path, Token};
+
+type PunctuatedQueryGroups = Punctuated<QueryGroup, Token![,]>;
+
+pub(crate) fn database(args: TokenStream, input: TokenStream) -> TokenStream {
+ let args = syn::parse_macro_input!(args as QueryGroupList);
+ let input = syn::parse_macro_input!(input as ItemStruct);
+
+ let query_groups = &args.query_groups;
+ let database_name = &input.ident;
+ let visibility = &input.vis;
+ let db_storage_field = quote! { storage };
+
+ let mut output = proc_macro2::TokenStream::new();
+ output.extend(quote! { #input });
+
+ let query_group_names_snake: Vec<_> = query_groups
+ .iter()
+ .map(|query_group| {
+ let group_name = query_group.name();
+ Ident::new(&group_name.to_string().to_snake_case(), group_name.span())
+ })
+ .collect();
+
+ let query_group_storage_names: Vec<_> = query_groups
+ .iter()
+ .map(|QueryGroup { group_path }| {
+ quote! {
+ <#group_path as salsa::plumbing::QueryGroup>::GroupStorage
+ }
+ })
+ .collect();
+
+ // For each query group `foo::MyGroup` create a link to its
+ // `foo::MyGroupGroupStorage`
+ let mut storage_fields = proc_macro2::TokenStream::new();
+ let mut storage_initializers = proc_macro2::TokenStream::new();
+ let mut has_group_impls = proc_macro2::TokenStream::new();
+ for (((query_group, group_name_snake), group_storage), group_index) in query_groups
+ .iter()
+ .zip(&query_group_names_snake)
+ .zip(&query_group_storage_names)
+ .zip(0_u16..)
+ {
+ let group_path = &query_group.group_path;
+
+ // rewrite the last identifier (`MyGroup`, above) to
+ // (e.g.) `MyGroupGroupStorage`.
+ storage_fields.extend(quote! {
+ #group_name_snake: #group_storage,
+ });
+
+ // rewrite the last identifier (`MyGroup`, above) to
+ // (e.g.) `MyGroupGroupStorage`.
+ storage_initializers.extend(quote! {
+ #group_name_snake: #group_storage::new(#group_index),
+ });
+
+ // ANCHOR:HasQueryGroup
+ has_group_impls.extend(quote! {
+ impl salsa::plumbing::HasQueryGroup<#group_path> for #database_name {
+ fn group_storage(&self) -> &#group_storage {
+ &self.#db_storage_field.query_store().#group_name_snake
+ }
+ }
+ });
+ // ANCHOR_END:HasQueryGroup
+ }
+
+ // create group storage wrapper struct
+ output.extend(quote! {
+ #[doc(hidden)]
+ #visibility struct __SalsaDatabaseStorage {
+ #storage_fields
+ }
+
+ impl Default for __SalsaDatabaseStorage {
+ fn default() -> Self {
+ Self {
+ #storage_initializers
+ }
+ }
+ }
+ });
+
+ // Create a tuple (D1, D2, ...) where Di is the data for a given query group.
+ let mut database_data = vec![];
+ for QueryGroup { group_path } in query_groups {
+ database_data.push(quote! {
+ <#group_path as salsa::plumbing::QueryGroup>::GroupData
+ });
+ }
+
+ // ANCHOR:DatabaseStorageTypes
+ output.extend(quote! {
+ impl salsa::plumbing::DatabaseStorageTypes for #database_name {
+ type DatabaseStorage = __SalsaDatabaseStorage;
+ }
+ });
+ // ANCHOR_END:DatabaseStorageTypes
+
+ // ANCHOR:DatabaseOps
+ let mut fmt_ops = proc_macro2::TokenStream::new();
+ let mut maybe_changed_ops = proc_macro2::TokenStream::new();
+ let mut cycle_recovery_strategy_ops = proc_macro2::TokenStream::new();
+ let mut for_each_ops = proc_macro2::TokenStream::new();
+ for ((QueryGroup { group_path }, group_storage), group_index) in query_groups
+ .iter()
+ .zip(&query_group_storage_names)
+ .zip(0_u16..)
+ {
+ fmt_ops.extend(quote! {
+ #group_index => {
+ let storage: &#group_storage =
+ <Self as salsa::plumbing::HasQueryGroup<#group_path>>::group_storage(self);
+ storage.fmt_index(self, input, fmt)
+ }
+ });
+ maybe_changed_ops.extend(quote! {
+ #group_index => {
+ let storage: &#group_storage =
+ <Self as salsa::plumbing::HasQueryGroup<#group_path>>::group_storage(self);
+ storage.maybe_changed_after(self, input, revision)
+ }
+ });
+ cycle_recovery_strategy_ops.extend(quote! {
+ #group_index => {
+ let storage: &#group_storage =
+ <Self as salsa::plumbing::HasQueryGroup<#group_path>>::group_storage(self);
+ storage.cycle_recovery_strategy(self, input)
+ }
+ });
+ for_each_ops.extend(quote! {
+ let storage: &#group_storage =
+ <Self as salsa::plumbing::HasQueryGroup<#group_path>>::group_storage(self);
+ storage.for_each_query(runtime, &mut op);
+ });
+ }
+ output.extend(quote! {
+ impl salsa::plumbing::DatabaseOps for #database_name {
+ fn ops_database(&self) -> &dyn salsa::Database {
+ self
+ }
+
+ fn ops_salsa_runtime(&self) -> &salsa::Runtime {
+ self.#db_storage_field.salsa_runtime()
+ }
+
+ fn ops_salsa_runtime_mut(&mut self) -> &mut salsa::Runtime {
+ self.#db_storage_field.salsa_runtime_mut()
+ }
+
+ fn fmt_index(
+ &self,
+ input: salsa::DatabaseKeyIndex,
+ fmt: &mut std::fmt::Formatter<'_>,
+ ) -> std::fmt::Result {
+ match input.group_index() {
+ #fmt_ops
+ i => panic!("salsa: invalid group index {}", i)
+ }
+ }
+
+ fn maybe_changed_after(
+ &self,
+ input: salsa::DatabaseKeyIndex,
+ revision: salsa::Revision
+ ) -> bool {
+ match input.group_index() {
+ #maybe_changed_ops
+ i => panic!("salsa: invalid group index {}", i)
+ }
+ }
+
+ fn cycle_recovery_strategy(
+ &self,
+ input: salsa::DatabaseKeyIndex,
+ ) -> salsa::plumbing::CycleRecoveryStrategy {
+ match input.group_index() {
+ #cycle_recovery_strategy_ops
+ i => panic!("salsa: invalid group index {}", i)
+ }
+ }
+
+ fn for_each_query(
+ &self,
+ mut op: &mut dyn FnMut(&dyn salsa::plumbing::QueryStorageMassOps),
+ ) {
+ let runtime = salsa::Database::salsa_runtime(self);
+ #for_each_ops
+ }
+ }
+ });
+ // ANCHOR_END:DatabaseOps
+
+ output.extend(has_group_impls);
+
+ if std::env::var("SALSA_DUMP").is_ok() {
+ println!("~~~ database_storage");
+ println!("{}", output.to_string());
+ println!("~~~ database_storage");
+ }
+
+ output.into()
+}
+
+#[derive(Clone, Debug)]
+struct QueryGroupList {
+ query_groups: PunctuatedQueryGroups,
+}
+
+impl Parse for QueryGroupList {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let query_groups: PunctuatedQueryGroups =
+ input.parse_terminated(QueryGroup::parse, Token![,])?;
+ Ok(QueryGroupList { query_groups })
+ }
+}
+
+#[derive(Clone, Debug)]
+struct QueryGroup {
+ group_path: Path,
+}
+
+impl QueryGroup {
+ /// The name of the query group trait.
+ fn name(&self) -> Ident {
+ self.group_path.segments.last().unwrap().ident.clone()
+ }
+}
+
+impl Parse for QueryGroup {
+ /// ```ignore
+ /// impl HelloWorldDatabase;
+ /// ```
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let group_path: Path = input.parse()?;
+ Ok(QueryGroup { group_path })
+ }
+}
+
+struct Nothing;
+
+impl Parse for Nothing {
+ fn parse(_input: ParseStream) -> syn::Result<Self> {
+ Ok(Nothing)
+ }
+}
diff --git a/vendor/rust-analyzer-salsa-macros/src/lib.rs b/vendor/rust-analyzer-salsa-macros/src/lib.rs new file mode 100644 index 000000000..deb99ea5f --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/src/lib.rs @@ -0,0 +1,148 @@ +//! This crate provides salsa's macros and attributes.
+
+#![recursion_limit = "256"]
+
+extern crate proc_macro;
+extern crate proc_macro2;
+#[macro_use]
+extern crate quote;
+
+use proc_macro::TokenStream;
+
+mod database_storage;
+mod parenthesized;
+mod query_group;
+
+/// The decorator that defines a salsa "query group" trait. This is a
+/// trait that defines everything that a block of queries need to
+/// execute, as well as defining the queries themselves that are
+/// exported for others to use.
+///
+/// This macro declares the "prototype" for a group of queries. It will
+/// expand into a trait and a set of structs, one per query.
+///
+/// For each query, you give the name of the accessor method to invoke
+/// the query (e.g., `my_query`, below), as well as its parameter
+/// types and the output type. You also give the name for a query type
+/// (e.g., `MyQuery`, below) that represents the query, and optionally
+/// other details, such as its storage.
+///
+/// # Examples
+///
+/// The simplest example is something like this:
+///
+/// ```ignore
+/// #[salsa::query_group]
+/// trait TypeckDatabase {
+/// #[salsa::input] // see below for other legal attributes
+/// fn my_query(&self, input: u32) -> u64;
+///
+/// /// Queries can have any number of inputs (including zero); if there
+/// /// is not exactly one input, then the key type will be
+/// /// a tuple of the input types, so in this case `(u32, f32)`.
+/// fn other_query(&self, input1: u32, input2: f32) -> u64;
+/// }
+/// ```
+///
+/// Here is a list of legal `salsa::XXX` attributes:
+///
+/// - Storage attributes: control how the query data is stored and set. These
+/// are described in detail in the section below.
+/// - `#[salsa::input]`
+/// - `#[salsa::memoized]`
+/// - `#[salsa::dependencies]`
+/// - Query execution:
+/// - `#[salsa::invoke(path::to::my_fn)]` -- for a non-input, this
+/// indicates the function to call when a query must be
+/// recomputed. The default is to call a function in the same
+/// module with the same name as the query.
+/// - `#[query_type(MyQueryTypeName)]` specifies the name of the
+/// dummy struct created for the query. Default is the name of the
+/// query, in camel case, plus the word "Query" (e.g.,
+/// `MyQueryQuery` and `OtherQueryQuery` in the examples above).
+///
+/// # Storage attributes
+///
+/// Here are the possible storage values for each query. The default
+/// is `storage memoized`.
+///
+/// ## Input queries
+///
+/// Specifying `storage input` will give you an **input
+/// query**. Unlike derived queries, whose value is given by a
+/// function, input queries are explicitly set by doing
+/// `db.query(QueryType).set(key, value)` (where `QueryType` is the
+/// `type` specified for the query). Accessing a value that has not
+/// yet been set will panic. Each time you invoke `set`, we assume the
+/// value has changed, and so we will potentially re-execute derived
+/// queries that read (transitively) from this input.
+///
+/// ## Derived queries
+///
+/// Derived queries are specified by a function.
+///
+/// - `#[salsa::memoized]` (the default) -- The result is memoized
+/// between calls. If the inputs have changed, we will recompute
+/// the value, but then compare against the old memoized value,
+/// which can significantly reduce the amount of recomputation
+/// required in new revisions. This does require that the value
+/// implements `Eq`.
+/// - `#[salsa::dependencies]` -- does not cache the value, so it will
+/// be recomputed every time it is needed. We do track the inputs, however,
+/// so if they have not changed, then things that rely on this query
+/// may be known not to have changed.
+///
+/// ## Attribute combinations
+///
+/// Some attributes are mutually exclusive. For example, it is an error to add
+/// multiple storage specifiers:
+///
+/// ```compile_fail
+/// # use salsa_macros as salsa;
+/// #[salsa::query_group]
+/// trait CodegenDatabase {
+/// #[salsa::input]
+/// #[salsa::memoized]
+/// fn my_query(&self, input: u32) -> u64;
+/// }
+/// ```
+///
+/// It is also an error to annotate a function to `invoke` on an `input` query:
+///
+/// ```compile_fail
+/// # use salsa_macros as salsa;
+/// #[salsa::query_group]
+/// trait CodegenDatabase {
+/// #[salsa::input]
+/// #[salsa::invoke(typeck::my_query)]
+/// fn my_query(&self, input: u32) -> u64;
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn query_group(args: TokenStream, input: TokenStream) -> TokenStream {
+ query_group::query_group(args, input)
+}
+
+/// This attribute is placed on your database struct. It takes a list of the
+/// query groups that your database supports. The format looks like so:
+///
+/// ```rust,ignore
+/// #[salsa::database(MyQueryGroup1, MyQueryGroup2)]
+/// struct MyDatabase {
+/// runtime: salsa::Runtime<MyDatabase>, // <-- your database will need this field, too
+/// }
+/// ```
+///
+/// Here, the struct `MyDatabase` would support the two query groups
+/// `MyQueryGroup1` and `MyQueryGroup2`. In addition to the `database`
+/// attribute, the struct needs to have a `runtime` field (of type
+/// [`salsa::Runtime`]) and to implement the `salsa::Database` trait.
+///
+/// See [the `hello_world` example][hw] for more details.
+///
+/// [`salsa::Runtime`]: struct.Runtime.html
+/// [hw]: https://github.com/salsa-rs/salsa/tree/master/examples/hello_world
+#[proc_macro_attribute]
+pub fn database(args: TokenStream, input: TokenStream) -> TokenStream {
+ database_storage::database(args, input)
+}
diff --git a/vendor/rust-analyzer-salsa-macros/src/parenthesized.rs b/vendor/rust-analyzer-salsa-macros/src/parenthesized.rs new file mode 100644 index 000000000..f59c8a21d --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/src/parenthesized.rs @@ -0,0 +1,12 @@ +pub(crate) struct Parenthesized<T>(pub T);
+
+impl<T> syn::parse::Parse for Parenthesized<T>
+where
+ T: syn::parse::Parse,
+{
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let content;
+ syn::parenthesized!(content in input);
+ content.parse::<T>().map(Parenthesized)
+ }
+}
diff --git a/vendor/rust-analyzer-salsa-macros/src/query_group.rs b/vendor/rust-analyzer-salsa-macros/src/query_group.rs new file mode 100644 index 000000000..9dfc7f09a --- /dev/null +++ b/vendor/rust-analyzer-salsa-macros/src/query_group.rs @@ -0,0 +1,744 @@ +use std::{convert::TryFrom, iter::FromIterator};
+
+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::Dependencies;
+ 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 `{}` must be `&self`", query_name),
+ )
+ .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 `{:?}` of `{}`", arg, 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 `{:?}` of `{}`", ret, 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 {
+ let (key_names, keys): (Vec<_>, Vec<_>) =
+ query.keys.iter().map(|(pat, ty)| (pat, ty)).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);
+
+ query_fn_definitions.extend(quote! {
+ fn #fn_name(&self, #(#key_names: #keys),*) -> #value {
+ // 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.
+ ",
+ fn_name = fn_name
+ );
+
+ 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.
+ ",
+ fn_name = fn_name
+ );
+
+ 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::Dependencies => {
+ quote!(salsa::plumbing::DependencyStorage<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
+ }
+ }
+ // 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, 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, 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
+ });
+
+ if std::env::var("SALSA_DUMP").is_ok() {
+ println!("~~~ query_group");
+ println!("{}", output.to_string());
+ println!("~~~ query_group");
+ }
+
+ 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,
+ Dependencies,
+ 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::Dependencies => true,
+ }
+ }
+}
|