make query_group macro procedural

Switch to a procedural implementation of the `query_group!` macro,
residing in the `components/salsa_macros` subcrate.

Allow the user to override the invoked function via `salsa::invoke(...)`
and the name of the generated query type via `salsa::query_type(...)`.

In all tests, replace the `salsa::query_group! { ... }` invocations with
the new attribute-style `#[salsa::query_group]` macro, and change them
to the new naming scheme for query types (`...Query`).

Update README, examples, and documentation.
This commit is contained in:
Fabian Schuiki 2019-01-12 11:11:59 +01:00
parent cd454e986e
commit 93c30a953d
35 changed files with 768 additions and 782 deletions

View file

@ -16,6 +16,7 @@ lock_api = "0.1.4"
indexmap = "1.0.1"
log = "0.4.5"
smallvec = "0.6.5"
salsa_macros = { version = "0.9", path = "components/salsa_macros" }
[dev-dependencies]
diff = "0.1.0"

View file

@ -0,0 +1,18 @@
[package]
name = "salsa_macros"
version = "0.9.1"
authors = ["Niko Matsakis <niko@alum.mit.edu>"]
edition = "2018"
license = "Apache-2.0 OR MIT"
repository = "https://github.com/salsa-rs/salsa"
description = "Procedural macros for the salsa crate"
readme = "README.md"
[lib]
proc-macro = true
[dependencies]
heck = "0.3"
proc-macro2 = "0.4"
quote = "0.6"
syn = { version = "0.15", features = ["full", "extra-traits"] }

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1 @@
../../LICENSE-MIT

View file

@ -0,0 +1 @@
../../README.md

View file

@ -0,0 +1,369 @@
//! This crate provides salsa's macros and attributes.
#![recursion_limit = "128"]
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
use heck::CamelCase;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::ToTokens;
use syn::{parse_macro_input, AttributeArgs, FnArg, Ident, ItemTrait, ReturnType, TraitItem};
/// 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::XXX] // see below for 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(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::volatile]`
/// - `#[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 fo 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::volatile]` -- indicates that the inputs are not fully
/// captured by salsa. The result will be recomputed once per revision.
/// - `#[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:
///
/// ```ignore
/// #[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:
///
/// ```ignore
/// #[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 {
let _args = parse_macro_input!(args as AttributeArgs);
let input = parse_macro_input!(input as ItemTrait);
// println!("args: {:#?}", args);
// println!("input: {:#?}", input);
let trait_vis = input.vis;
let trait_name = input.ident;
let _generics = input.generics.clone();
// Decompose the trait into the corresponding queries.
let mut queries = vec![];
for item in input.items {
match item {
TraitItem::Method(method) => {
let mut storage = QueryStorage::Memoized;
let mut invoke = None;
let mut query_type = Ident::new(
&format!("{}Query", method.sig.ident.to_string().to_camel_case()),
Span::call_site(),
);
let mut num_storages = 0;
// Extract attributes.
let mut attrs = vec![];
for attr in method.attrs {
// Leave non-salsa attributes untouched. These are
// attributes that don't start with `salsa::` or don't have
// exactly two segments in their path.
if is_salsa_attr_path(&attr.path) {
attrs.push(attr);
continue;
}
// Keep the salsa attributes around.
let name = attr.path.segments[1].ident.to_string();
let tts = attr.tts.into();
match name.as_str() {
"memoized" => {
storage = QueryStorage::Memoized;
num_storages += 1;
}
"volatile" => {
storage = QueryStorage::Volatile;
num_storages += 1;
}
"dependencies" => {
storage = QueryStorage::Dependencies;
num_storages += 1;
}
"input" => {
storage = QueryStorage::Input;
num_storages += 1;
}
"invoke" => {
invoke = Some(parse_macro_input!(tts as Parenthesized<syn::Path>).0);
}
"query_type" => {
query_type = parse_macro_input!(tts as Parenthesized<Ident>).0;
}
_ => panic!("unknown salsa attribute `{}`", name),
}
}
// Check attribute combinations.
if num_storages > 1 {
panic!("multiple storage attributes specified");
}
if invoke.is_some() && storage == QueryStorage::Input {
panic!("#[salsa::invoke] cannot be set on #[salsa::input] queries");
}
// Extract keys.
let mut iter = method.sig.decl.inputs.iter();
match iter.next() {
Some(FnArg::SelfRef(sr)) if sr.mutability.is_none() => (),
_ => panic!(
"first argument of query `{}` must be `&self` or `&mut self`",
method.sig.ident
),
}
let mut keys = vec![];
for arg in iter {
match *arg {
FnArg::Captured(ref arg) => {
keys.push(arg.ty.clone());
}
ref a => panic!("unsupported argument `{:?}` of `{}`", a, method.sig.ident),
}
}
// Extract value.
let value = match method.sig.decl.output {
ReturnType::Type(_, ref ty) => ty.as_ref().clone(),
ref r => panic!(
"unsupported return type `{:?}` of `{}`",
r, method.sig.ident
),
};
queries.push(Query {
query_type,
fn_name: method.sig.ident.clone(),
attrs,
storage,
keys,
value,
invoke,
});
}
_ => (),
}
}
// Emit the trait itself.
let mut output = {
let mut queries_as_tokens = proc_macro2::TokenStream::new();
for query in &queries {
let key_names: &Vec<_> = &(0..query.keys.len())
.map(|i| Ident::new(&format!("key{}", i), Span::call_site()))
.collect();
let keys = &query.keys;
let value = &query.value;
let fn_name = &query.fn_name;
let qt = &query.query_type;
let attrs = &query.attrs;
let expanded = quote! {
#(#attrs)*
fn #fn_name(&self, #(#key_names: #keys),*) -> #value {
<Self as salsa::plumbing::GetQueryTable<#qt>>::get_query_table(self).get((#(#key_names),*))
}
};
queries_as_tokens.extend(expanded);
}
let attrs = &input.attrs;
let qts = queries.iter().map(|q| &q.query_type);
let bounds = &input.supertraits;
quote! {
#(#attrs)*
#trait_vis trait #trait_name : #(salsa::plumbing::GetQueryTable<#qts> +)* #bounds {
#queries_as_tokens
}
}
};
// Emit the query types.
for query in queries {
let qt = &query.query_type;
let storage = Ident::new(
match query.storage {
QueryStorage::Memoized => "MemoizedStorage",
QueryStorage::Volatile => "VolatileStorage",
QueryStorage::Dependencies => "DependencyStorage",
QueryStorage::Input => "InputStorage",
},
Span::call_site(),
);
let keys = &query.keys;
let value = &query.value;
// Emit the query struct and implement the Query trait on it.
output.extend(quote! {
#[derive(Default, Debug)]
#trait_vis struct #qt;
impl<DB> salsa::Query<DB> for #qt
where
DB: #trait_name,
{
type Key = (#(#keys),*);
type Value = #value;
type Storage = salsa::plumbing::#storage<DB, Self>;
}
});
// Implement the QueryFunction trait for all queries except inputs.
if query.storage != QueryStorage::Input {
let span = query.fn_name.span();
let key_names: &Vec<_> = &(0..query.keys.len())
.map(|i| Ident::new(&format!("key{}", i), Span::call_site()))
.collect();
let key_pattern = if query.keys.len() == 1 {
quote! { #(#key_names),* }
} else {
quote! { (#(#key_names),*) }
};
let invoke = match &query.invoke {
Some(i) => i.into_token_stream(),
None => query.fn_name.into_token_stream(),
};
output.extend(quote_spanned! {span=>
impl<DB> salsa::plumbing::QueryFunction<DB> for #qt
where
DB: #trait_name,
{
fn execute(db: &DB, #key_pattern: <Self as salsa::Query<DB>>::Key)
-> <Self as salsa::Query<DB>>::Value {
#invoke(db, #(#key_names),*)
}
}
});
}
}
output.into()
}
fn is_salsa_attr_path(path: &syn::Path) -> bool {
path.segments
.first()
.map(|s| s.value().ident != "salsa")
.unwrap_or(true)
|| path.segments.len() != 2
}
#[derive(Debug)]
struct Query {
fn_name: Ident,
attrs: Vec<syn::Attribute>,
query_type: Ident,
storage: QueryStorage,
keys: Vec<syn::Type>,
value: syn::Type,
invoke: Option<syn::Path>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum QueryStorage {
Memoized,
Volatile,
Dependencies,
Input,
}
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)
}
}

View file

@ -1,23 +1,16 @@
use crate::compiler;
use std::sync::Arc;
salsa::query_group! {
pub trait ClassTableDatabase: compiler::CompilerDatabase {
/// Get the fields.
fn fields(class: DefId) -> Arc<Vec<DefId>> {
type Fields;
}
#[salsa::query_group]
pub trait ClassTableDatabase: compiler::CompilerDatabase {
/// Get the fields.
fn fields(&self, class: DefId) -> Arc<Vec<DefId>>;
/// Get the list of all classes
fn all_classes() -> Arc<Vec<DefId>> {
type AllClasses;
}
/// Get the list of all classes
fn all_classes(&self) -> Arc<Vec<DefId>>;
/// Get the list of all fields
fn all_fields() -> Arc<Vec<DefId>> {
type AllFields;
}
}
/// Get the list of all fields
fn all_fields(&self) -> Arc<Vec<DefId>>;
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]

View file

@ -39,9 +39,9 @@ impl salsa::Database for DatabaseImpl {
salsa::database_storage! {
pub struct DatabaseImplStorage for DatabaseImpl {
impl class_table::ClassTableDatabase {
fn all_classes() for class_table::AllClasses;
fn all_fields() for class_table::AllFields;
fn fields() for class_table::Fields;
fn all_classes() for class_table::AllClassesQuery;
fn all_fields() for class_table::AllFieldsQuery;
fn fields() for class_table::FieldsQuery;
}
}
}

View file

@ -12,34 +12,33 @@ the queries from A and adds a few more. (These relationships must form
a DAG at present, but that is due to Rust's restrictions around
supertraits, which are likely to be lifted.)
Each query group is defined via an invocation of `salsa::query_group!`
macro. This is the invocation used in `hello_world`:
Each query group is defined via a trait with the
`#[salsa::query_group]` decorator attached to it. `salsa::query_group`
is a procedural macro that will process the trait -- it will produce
not only the trait you specified, but also various additional types
you can later use and name.
```rust
salsa::query_group! {
trait HelloWorldDatabase: salsa::Database {
fn input_string(key: ()) -> Arc<String> {
type InputString;
storage input;
}
#[salsa::query_group]
trait HelloWorldDatabase: salsa::Database {
#[salsa::input]
#[salsa::query_type(InputString)]
fn input_string(&self, key: ()) -> Arc<String>;
fn length(key: ()) -> usize {
type Length;
}
}
fn length(&self, key: ()) -> usize;
}
```
This invocation will in fact expand to a number of things you can
later use and name. First and foremost is the **query group trait**,
here called `HelloWorldDatabase`. As the name suggests, this trait
will ultimately be implemented by the **database**, which is the
struct in your application that contains the store for all queries and
any other global state that persists beyond a single query execution.
In writing your application, though, we never work with a concrete
database struct: instead we work against a generic struct through
traits, thus capturing the subset of functionality that we actually
need.
Each query group trait represents a self-contained block of queries
that can invoke each other and so forth. Your final database may
implement many such traits, thus combining many groups of queries into
the final program. Query groups are thus kind of analogous to Rust
crates: they represent a kind of "library" of queries that your final
program can use. Since we don't know the full set of queries that our
code may be combined with, when implementing a query group we don't
with a concrete database struct: instead we work against a generic
struct through traits, thus capturing the subset of functionality that
we actually need.
The `HelloWorldDatabase` trait has one supertrait:
`salsa::Database`. If we were defining more query groups in our
@ -59,19 +58,19 @@ the "fn body" is obviously not real Rust syntax. Rather, it's just
used to specify a few bits of metadata about the query. We'll see how
to define the fn body in the next step.
**For each query.** For each query, we must **always** define a `type`
(e.g., `type InputString;`). The macro will define a type with this
name alongside the trait: you can use this name later to specify which
query you are talking about. This is needed for some of the more
advanced methods (we'll discuss them later).
**For each query.** For each query, the procedural macro will emit a
"query type", which is a kind of dummy struct that can be used to
refer to the query (we'll see an example of referencing this struct
later). For a query `foo_bar`, the struct is by default named
`FooBarQuery` -- but that name can be overridden with the
`#[salsa::query_type]` attribute. In our example, we override the
query type for `input_string` to be `InputString` but left `length`
alone (so it defaults to `LengthQuery`).
You can also optionally define the **storage** for a query via a
declaration like `storage <s>;`. The most common kind of storage is
either *memoized* (the default) or *input*. An *input* is a special
sort of query that is not defined by a function: rather, it gets its
values via explicit `set` operations (we'll see them later). In our
case, we define one input query (`input_string`) and one memoized
query (`length`).
You can also use the `#[salsa::input]` attribute to designate
the "inputs" to the system. The values for input queries are not
generated via a function but rather by explicit `set` operations,
as we'll see later. They are the starting points for your computation.
### Step 2: Define the query functions
@ -79,9 +78,9 @@ Once you've defined your query group, you have to give the function
definition for every non-input query. In our case, that is the query
`length`. To do this, you simply define a function with the
appropriate name in the same module as the query group; if you would
prefer to use a different name or location, you write `use fn
path::to::other_fn;` in the query definition to tell us where to find
it.
prefer to use a different name or location, you add an attribute like
`#[salsa::invoke(path::to::other_fn)]` in the query definition to tell
us where to find it.
The query function for `length` looks like:

View file

@ -12,34 +12,24 @@ use std::sync::Arc;
// the queries from A and adds a few more. (These relationships must form
// a DAG at present, but that is due to Rust's restrictions around
// supertraits, which are likely to be lifted.)
salsa::query_group! {
trait HelloWorldDatabase: salsa::Database {
// For each query, we give the name, input type (here, `()`)
// and the output type `Arc<String>`. Inside the "fn body" we
// give some other configuration.
fn input_string(key: ()) -> Arc<String> {
// The type we will generate to represent this query.
type InputString;
#[salsa::query_group]
trait HelloWorldDatabase: salsa::Database {
// For each query, we give the name, input type (here, `()`)
// and the output type `Arc<String>`. We can use attributes to
// give other configuration:
//
// - `salsa::input` indicates that this is an "input" to the system,
// which must be explicitly set.
// - `salsa::query_type` controls the name of the dummy struct
// that represents this query. We'll see it referenced
// later. The default would have been `InputStringQuery`.
#[salsa::input]
#[salsa::query_type(InputString)]
fn input_string(&self, key: ()) -> Arc<String>;
// Specify the queries' "storage" -- in this case, this is
// an *input query*, which means that its value changes
// only when it is explicitly *set* (see the `main`
// function below).
storage input;
}
// This is a *derived query*, meaning its value is specified by
// a function (see Step 2, below).
fn length(key: ()) -> usize {
type Length;
// No explicit storage defaults to `storage memoized;`
//
// The function that defines this query is (by default) a
// function with the same name as the query in the
// containing module (e.g., `length`).
}
}
// This is a *derived query*, meaning its value is specified by
// a function (see Step 2, below).
fn length(&self, key: ()) -> usize;
}
///////////////////////////////////////////////////////////////////////////
@ -86,7 +76,7 @@ salsa::database_storage! {
struct DatabaseStorage for DatabaseStruct {
impl HelloWorldDatabase {
fn input_string() for InputString;
fn length() for Length;
fn length() for LengthQuery;
}
}
}

View file

@ -474,293 +474,6 @@ where
}
}
/// A macro that helps in defining the "context trait" of a given
/// module. 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
/// trait TypeckDatabase {
/// query_group! {
/// /// Comments or other attributes can go here
/// fn my_query(input: u32) -> u64 {
/// type MyQuery;
/// storage memoized; // optional, this is the default
/// use fn path::to::fn; // optional, default is `my_query`
/// }
///
/// /// Queries can have any number of inputs; the key type will be
/// /// a tuple of the input types, so in this case `(u32, f32)`.
/// fn other_query(input1: u32, input2: f32) -> u64 {
/// type OtherQuery;
/// }
/// }
/// }
/// ```
///
/// # Storage modes
///
/// 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 explicit 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.
///
/// - `storage memoized` -- 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`.
/// - `storage volatile` -- indicates that the inputs are not fully
/// captured by salsa. The result will be recomputed once per revision.
/// - `storage 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.
#[macro_export]
macro_rules! query_group {
(
$(#[$attr:meta])* $v:vis trait $name:ident { $($t:tt)* }
) => {
$crate::query_group! {
attr[$(#[$attr])*];
headers[$v, $name, ];
tokens[{ $($t)* }];
}
};
(
$(#[$attr:meta])* $v:vis trait $name:ident : $($t:tt)*
) => {
$crate::query_group! {
attr[$(#[$attr])*];
headers[$v, $name, ];
tokens[$($t)*];
}
};
// Base case: found the trait body
(
attr[$($trait_attr:tt)*];
headers[$v:vis, $query_trait:ident, $($header:tt)*];
tokens[{
$(
$(#[$method_attr:meta])*
fn $method_name:ident($($key_name:ident: $key_ty:ty),* $(,)*) -> $value_ty:ty {
type $QueryType:ident;
$(storage $storage:tt;)* // FIXME(rust-lang/rust#48075) should be `?`
$(use fn $fn_path:path;)* // FIXME(rust-lang/rust#48075) should be `?`
}
)*
}];
) => {
$($trait_attr)* $v trait $query_trait: $($crate::plumbing::GetQueryTable<$QueryType> +)* $($header)* {
$(
$(#[$method_attr])*
fn $method_name(&self, $($key_name: $key_ty),*) -> $value_ty {
<Self as $crate::plumbing::GetQueryTable<$QueryType>>::get_query_table(self)
.get(($($key_name),*))
}
)*
}
$(
#[derive(Default, Debug)]
$v struct $QueryType;
impl<DB> $crate::Query<DB> for $QueryType
where
DB: $query_trait,
{
type Key = ($($key_ty),*);
type Value = $value_ty;
type Storage = $crate::query_group! { @storage_ty[DB, Self, $($storage)*] };
}
$crate::query_group! {
@query_fn[
storage($($storage)*);
method_name($method_name);
fn_path($($fn_path)*);
db_trait($query_trait);
query_type($QueryType);
key($($key_name: $key_ty),*);
]
}
)*
};
(
@query_fn[
storage(input);
method_name($method_name:ident);
fn_path();
$($rest:tt)*
]
) => {
// do nothing for `storage input`, presuming they did not write an explicit `use fn`
};
(
@query_fn[
storage(input);
method_name($method_name:ident);
fn_path($fn_path:path);
$($rest:tt)*
]
) => {
// error for `storage input` with an explicit `use fn`
compile_error! {
"cannot have `storage input` combined with `use fn`"
}
};
(
@query_fn[
storage($($storage:ident)*);
method_name($method_name:ident);
fn_path();
$($rest:tt)*
]
) => {
// default to `use fn $method_name`
$crate::query_group! {
@query_fn[
storage($($storage)*);
method_name($method_name);
fn_path($method_name);
$($rest)*
]
}
};
// Handle fns of one argument: once parenthesized patterns are stable on beta,
// we can remove this special case.
(
@query_fn[
storage($($storage:ident)*);
method_name($method_name:ident);
fn_path($fn_path:path);
db_trait($DbTrait:path);
query_type($QueryType:ty);
key($key_name:ident: $key_ty:ty);
]
) => {
impl<DB> $crate::plumbing::QueryFunction<DB> for $QueryType
where DB: $DbTrait
{
fn execute(db: &DB, $key_name: <Self as $crate::Query<DB>>::Key)
-> <Self as $crate::Query<DB>>::Value
{
$fn_path(db, $key_name)
}
}
};
// Handle fns of N arguments: once parenthesized patterns are stable on beta,
// we can use this code for all cases.
(
@query_fn[
storage($($storage:ident)*);
method_name($method_name:ident);
fn_path($fn_path:path);
db_trait($DbTrait:path);
query_type($QueryType:ty);
key($($key_name:ident: $key_ty:ty),*);
]
) => {
impl<DB> $crate::plumbing::QueryFunction<DB> for $QueryType
where DB: $DbTrait
{
fn execute(db: &DB, ($($key_name),*): <Self as $crate::Query<DB>>::Key)
-> <Self as $crate::Query<DB>>::Value
{
$fn_path(db, $($key_name),*)
}
}
};
// Recursive case: found some more part of the trait header.
// Keep pulling out tokens until we find the body.
(
attr[$($attr:tt)*];
headers[$($headers:tt)*];
tokens[$token:tt $($tokens:tt)*];
) => {
$crate::query_group! {
attr[$($attr)*];
headers[$($headers)* $token];
tokens[$($tokens)*];
}
};
// Generate storage type
(
// Default case:
@storage_ty[$DB:ident, $Self:ident, ]
) => {
$crate::query_group! { @storage_ty[$DB, $Self, memoized] }
};
(
@storage_ty[$DB:ident, $Self:ident, memoized]
) => {
$crate::plumbing::MemoizedStorage<$DB, $Self>
};
(
@storage_ty[$DB:ident, $Self:ident, volatile]
) => {
$crate::plumbing::VolatileStorage<$DB, $Self>
};
(
@storage_ty[$DB:ident, $Self:ident, dependencies]
) => {
$crate::plumbing::DependencyStorage<$DB, $Self>
};
(
@storage_ty[$DB:ident, $Self:ident, input]
) => {
$crate::plumbing::InputStorage<DB, Self>
};
(
@storage_ty[$DB:ident, $Self:ident, $storage:tt]
) => {
compile_error! {
"invalid storage specification"
}
};
}
/// This macro generates the "query storage" that goes into your database.
/// It requires you to list all of the query groups that you need as well
/// as the queries within those groups. The format looks like so:
@ -917,3 +630,10 @@ macro_rules! database_storage {
)*
};
}
// Re-export the procedural macros.
#[allow(unused_imports)]
#[macro_use]
extern crate salsa_macros;
#[doc(hidden)]
pub use salsa_macros::*;

View file

@ -12,32 +12,23 @@ impl salsa::Database for DatabaseImpl {
salsa::database_storage! {
pub struct DatabaseImplStorage for DatabaseImpl {
impl Database {
fn memoized_a() for MemoizedA;
fn memoized_b() for MemoizedB;
fn volatile_a() for VolatileA;
fn volatile_b() for VolatileB;
fn memoized_a() for MemoizedAQuery;
fn memoized_b() for MemoizedBQuery;
fn volatile_a() for VolatileAQuery;
fn volatile_b() for VolatileBQuery;
}
}
}
salsa::query_group! {
trait Database: salsa::Database {
// `a` and `b` depend on each other and form a cycle
fn memoized_a() -> () {
type MemoizedA;
}
fn memoized_b() -> () {
type MemoizedB;
}
fn volatile_a() -> () {
type VolatileA;
storage volatile;
}
fn volatile_b() -> () {
type VolatileB;
storage volatile;
}
}
#[salsa::query_group]
trait Database: salsa::Database {
// `a` and `b` depend on each other and form a cycle
fn memoized_a(&self) -> ();
fn memoized_b(&self) -> ();
#[salsa::volatile]
fn volatile_a(&self) -> ();
#[salsa::volatile]
fn volatile_b(&self) -> ();
}
fn memoized_a(db: &impl Database) -> () {

View file

@ -16,13 +16,13 @@ impl salsa::Database for DatabaseImpl {
salsa::database_storage! {
pub struct DatabaseImplStorage for DatabaseImpl {
impl group::GcDatabase {
fn min() for group::Min;
fn max() for group::Max;
fn use_triangular() for group::UseTriangular;
fn fibonacci() for group::Fibonacci;
fn triangular() for group::Triangular;
fn compute() for group::Compute;
fn compute_all() for group::ComputeAll;
fn min() for group::MinQuery;
fn max() for group::MaxQuery;
fn use_triangular() for group::UseTriangularQuery;
fn fibonacci() for group::FibonacciQuery;
fn triangular() for group::TriangularQuery;
fn compute() for group::ComputeQuery;
fn compute_all() for group::ComputeAllQuery;
}
}
}

View file

@ -18,19 +18,19 @@ fn compute_one() {
let mut db = db::DatabaseImpl::default();
// Will compute fibonacci(5)
db.query_mut(UseTriangular).set(5, false);
db.query_mut(UseTriangularQuery).set(5, false);
db.compute(5);
db.salsa_runtime().next_revision();
assert_keys! {
db,
Triangular => (),
Fibonacci => (0, 1, 2, 3, 4, 5),
Compute => (5),
UseTriangular => (5),
Min => (),
Max => (),
TriangularQuery => (),
FibonacciQuery => (0, 1, 2, 3, 4, 5),
ComputeQuery => (5),
UseTriangularQuery => (5),
MinQuery => (),
MaxQuery => (),
}
// Memoized, but will compute fibonacci(5) again
@ -39,12 +39,12 @@ fn compute_one() {
assert_keys! {
db,
Triangular => (),
Fibonacci => (5),
Compute => (5),
UseTriangular => (5),
Min => (),
Max => (),
TriangularQuery => (),
FibonacciQuery => (5),
ComputeQuery => (5),
UseTriangularQuery => (5),
MinQuery => (),
MaxQuery => (),
}
}
@ -53,11 +53,11 @@ fn compute_switch() {
let mut db = db::DatabaseImpl::default();
// Will compute fibonacci(5)
db.query_mut(UseTriangular).set(5, false);
db.query_mut(UseTriangularQuery).set(5, false);
assert_eq!(db.compute(5), 5);
// Change to triangular mode
db.query_mut(UseTriangular).set(5, true);
db.query_mut(UseTriangularQuery).set(5, true);
// Now computes triangular(5)
assert_eq!(db.compute(5), 15);
@ -66,12 +66,12 @@ fn compute_switch() {
// are not relevant to the most recent value of `Compute`
assert_keys! {
db,
Triangular => (0, 1, 2, 3, 4, 5),
Fibonacci => (0, 1, 2, 3, 4, 5),
Compute => (5),
UseTriangular => (5),
Min => (),
Max => (),
TriangularQuery => (0, 1, 2, 3, 4, 5),
FibonacciQuery => (0, 1, 2, 3, 4, 5),
ComputeQuery => (5),
UseTriangularQuery => (5),
MinQuery => (),
MaxQuery => (),
}
db.sweep_all(SweepStrategy::default());
@ -79,12 +79,12 @@ fn compute_switch() {
// Now we just have `Triangular` and not `Fibonacci`
assert_keys! {
db,
Triangular => (0, 1, 2, 3, 4, 5),
Fibonacci => (),
Compute => (5),
UseTriangular => (5),
Min => (),
Max => (),
TriangularQuery => (0, 1, 2, 3, 4, 5),
FibonacciQuery => (),
ComputeQuery => (5),
UseTriangularQuery => (5),
MinQuery => (),
MaxQuery => (),
}
// Now run `compute` *again* in next revision.
@ -95,12 +95,12 @@ fn compute_switch() {
// We keep triangular, but just the outermost one.
assert_keys! {
db,
Triangular => (5),
Fibonacci => (),
Compute => (5),
UseTriangular => (5),
Min => (),
Max => (),
TriangularQuery => (5),
FibonacciQuery => (),
ComputeQuery => (5),
UseTriangularQuery => (5),
MinQuery => (),
MaxQuery => (),
}
}
@ -110,11 +110,11 @@ fn compute_all() {
let mut db = db::DatabaseImpl::default();
for i in 0..6 {
db.query_mut(UseTriangular).set(i, (i % 2) != 0);
db.query_mut(UseTriangularQuery).set(i, (i % 2) != 0);
}
db.query_mut(Min).set((), 0);
db.query_mut(Max).set((), 6);
db.query_mut(MinQuery).set((), 0);
db.query_mut(MaxQuery).set((), 6);
db.compute_all();
db.salsa_runtime().next_revision();
@ -123,42 +123,42 @@ fn compute_all() {
assert_keys! {
db,
Triangular => (1, 3, 5),
Fibonacci => (0, 2, 4),
Compute => (0, 1, 2, 3, 4, 5),
ComputeAll => (()),
UseTriangular => (0, 1, 2, 3, 4, 5),
Min => (()),
Max => (()),
TriangularQuery => (1, 3, 5),
FibonacciQuery => (0, 2, 4),
ComputeQuery => (0, 1, 2, 3, 4, 5),
ComputeAllQuery => (()),
UseTriangularQuery => (0, 1, 2, 3, 4, 5),
MinQuery => (()),
MaxQuery => (()),
}
// Reduce the range to exclude index 5.
db.query_mut(Max).set((), 5);
db.query_mut(MaxQuery).set((), 5);
db.compute_all();
assert_keys! {
db,
Triangular => (1, 3, 5),
Fibonacci => (0, 2, 4),
Compute => (0, 1, 2, 3, 4, 5),
ComputeAll => (()),
UseTriangular => (0, 1, 2, 3, 4, 5),
Min => (()),
Max => (()),
TriangularQuery => (1, 3, 5),
FibonacciQuery => (0, 2, 4),
ComputeQuery => (0, 1, 2, 3, 4, 5),
ComputeAllQuery => (()),
UseTriangularQuery => (0, 1, 2, 3, 4, 5),
MinQuery => (()),
MaxQuery => (()),
}
db.sweep_all(SweepStrategy::default());
// We no longer used `Compute(5)` and `Triangular(5)`; note that
// `UseTriangular(5)` is not collected, as it is an input.
// `UseTriangularQuery(5)` is not collected, as it is an input.
assert_keys! {
db,
Triangular => (1, 3),
Fibonacci => (0, 2, 4),
Compute => (0, 1, 2, 3, 4),
ComputeAll => (()),
UseTriangular => (0, 1, 2, 3, 4, 5),
Min => (()),
Max => (()),
TriangularQuery => (1, 3),
FibonacciQuery => (0, 2, 4),
ComputeQuery => (0, 1, 2, 3, 4),
ComputeAllQuery => (()),
UseTriangularQuery => (0, 1, 2, 3, 4, 5),
MinQuery => (()),
MaxQuery => (()),
}
}

View file

@ -1,5 +1,5 @@
use crate::db;
use crate::group::{Fibonacci, GcDatabase};
use crate::group::{FibonacciQuery, GcDatabase};
use salsa::debug::DebugQueryTable;
use salsa::{Database, SweepStrategy};
@ -9,7 +9,7 @@ fn sweep_default() {
db.fibonacci(5);
let k: Vec<_> = db.query(Fibonacci).keys();
let k: Vec<_> = db.query(FibonacciQuery).keys();
assert_eq!(k.len(), 6);
db.salsa_runtime().next_revision();
@ -20,7 +20,7 @@ fn sweep_default() {
// fibonacci is a constant, so it will not be invalidated,
// hence we keep 3 and 5 but remove the rest.
db.sweep_all(SweepStrategy::default());
let mut k: Vec<_> = db.query(Fibonacci).keys();
let mut k: Vec<_> = db.query(FibonacciQuery).keys();
k.sort();
assert_eq!(k, vec![3, 5]);
@ -31,7 +31,7 @@ fn sweep_default() {
// Same but we discard values this time.
db.sweep_all(SweepStrategy::default().discard_values());
let mut k: Vec<_> = db.query(Fibonacci).keys();
let mut k: Vec<_> = db.query(FibonacciQuery).keys();
k.sort();
assert_eq!(k, vec![3, 5]);

View file

@ -1,38 +1,23 @@
use crate::log::HasLog;
salsa::query_group! {
pub(crate) trait GcDatabase: salsa::Database + HasLog {
fn min() -> usize {
type Min;
storage input;
}
#[salsa::query_group]
pub(crate) trait GcDatabase: salsa::Database + HasLog {
#[salsa::input]
fn min(&self) -> usize;
fn max() -> usize {
type Max;
storage input;
}
#[salsa::input]
fn max(&self) -> usize;
fn use_triangular(key: usize) -> bool {
type UseTriangular;
storage input;
}
#[salsa::input]
fn use_triangular(&self, key: usize) -> bool;
fn fibonacci(key: usize) -> usize {
type Fibonacci;
}
fn fibonacci(&self, key: usize) -> usize;
fn triangular(key: usize) -> usize {
type Triangular;
}
fn triangular(&self, key: usize) -> usize;
fn compute(key: usize) -> usize {
type Compute;
}
fn compute(&self, key: usize) -> usize;
fn compute_all() -> Vec<usize> {
type ComputeAll;
}
}
fn compute_all(&self) -> Vec<usize>;
}
fn fibonacci(db: &impl GcDatabase, key: usize) -> usize {

View file

@ -1,5 +1,5 @@
use crate::db;
use crate::group::{Fibonacci, GcDatabase};
use crate::group::{FibonacciQuery, GcDatabase};
use salsa::debug::DebugQueryTable;
use salsa::{Database, SweepStrategy};
@ -13,7 +13,7 @@ fn one_rev() {
db.fibonacci(5);
let k: Vec<_> = db.query(Fibonacci).keys();
let k: Vec<_> = db.query(FibonacciQuery).keys();
assert_eq!(k.len(), 6);
// Everything was used in this revision, so
@ -28,7 +28,7 @@ fn two_rev_nothing() {
db.fibonacci(5);
let k: Vec<_> = db.query(Fibonacci).keys();
let k: Vec<_> = db.query(FibonacciQuery).keys();
assert_eq!(k.len(), 6);
db.salsa_runtime().next_revision();
@ -37,7 +37,7 @@ fn two_rev_nothing() {
// everything gets collected.
db.sweep_all(SweepStrategy::default());
let k: Vec<_> = db.query(Fibonacci).keys();
let k: Vec<_> = db.query(FibonacciQuery).keys();
assert_eq!(k.len(), 0);
}
@ -47,7 +47,7 @@ fn two_rev_one_use() {
db.fibonacci(5);
let k: Vec<_> = db.query(Fibonacci).keys();
let k: Vec<_> = db.query(FibonacciQuery).keys();
assert_eq!(k.len(), 6);
db.salsa_runtime().next_revision();
@ -58,7 +58,7 @@ fn two_rev_one_use() {
// hence we keep `fibonacci(5)` but remove 0..=4.
db.sweep_all(SweepStrategy::default());
let k: Vec<_> = db.query(Fibonacci).keys();
let k: Vec<_> = db.query(FibonacciQuery).keys();
assert_eq!(k, vec![5]);
}
@ -68,7 +68,7 @@ fn two_rev_two_uses() {
db.fibonacci(5);
let k: Vec<_> = db.query(Fibonacci).keys();
let k: Vec<_> = db.query(FibonacciQuery).keys();
assert_eq!(k.len(), 6);
db.salsa_runtime().next_revision();
@ -80,7 +80,7 @@ fn two_rev_two_uses() {
// hence we keep 3 and 5 but remove the rest.
db.sweep_all(SweepStrategy::default());
let mut k: Vec<_> = db.query(Fibonacci).keys();
let mut k: Vec<_> = db.query(FibonacciQuery).keys();
k.sort();
assert_eq!(k, vec![3, 5]);
}

View file

@ -2,17 +2,12 @@ use crate::implementation::{TestContext, TestContextImpl};
use salsa::debug::DebugQueryTable;
use salsa::Database;
salsa::query_group! {
pub(crate) trait ConstantsDatabase: TestContext {
fn constants_input(key: char) -> usize {
type ConstantsInput;
storage input;
}
#[salsa::query_group]
pub(crate) trait ConstantsDatabase: TestContext {
#[salsa::input]
fn constants_input(&self, key: char) -> usize;
fn constants_add(keys: (char, char)) -> usize {
type ConstantsAdd;
}
}
fn constants_add(&self, keys: (char, char)) -> usize;
}
fn constants_add(db: &impl ConstantsDatabase, (key1, key2): (char, char)) -> usize {
@ -24,8 +19,8 @@ fn constants_add(db: &impl ConstantsDatabase, (key1, key2): (char, char)) -> usi
#[should_panic]
fn invalidate_constant() {
let db = &mut TestContextImpl::default();
db.query_mut(ConstantsInput).set_constant('a', 44);
db.query_mut(ConstantsInput).set_constant('a', 66);
db.query_mut(ConstantsInputQuery).set_constant('a', 44);
db.query_mut(ConstantsInputQuery).set_constant('a', 66);
}
#[test]
@ -34,13 +29,13 @@ fn invalidate_constant_1() {
let db = &mut TestContextImpl::default();
// Not constant:
db.query_mut(ConstantsInput).set('a', 44);
db.query_mut(ConstantsInputQuery).set('a', 44);
// Becomes constant:
db.query_mut(ConstantsInput).set_constant('a', 44);
db.query_mut(ConstantsInputQuery).set_constant('a', 44);
// Invalidates:
db.query_mut(ConstantsInput).set_constant('a', 66);
db.query_mut(ConstantsInputQuery).set_constant('a', 66);
}
/// Test that invoking `set` on a constant is an error, even if you
@ -49,54 +44,54 @@ fn invalidate_constant_1() {
#[should_panic]
fn set_after_constant_same_value() {
let db = &mut TestContextImpl::default();
db.query_mut(ConstantsInput).set_constant('a', 44);
db.query_mut(ConstantsInput).set('a', 44);
db.query_mut(ConstantsInputQuery).set_constant('a', 44);
db.query_mut(ConstantsInputQuery).set('a', 44);
}
#[test]
fn not_constant() {
let db = &mut TestContextImpl::default();
db.query_mut(ConstantsInput).set('a', 22);
db.query_mut(ConstantsInput).set('b', 44);
db.query_mut(ConstantsInputQuery).set('a', 22);
db.query_mut(ConstantsInputQuery).set('b', 44);
assert_eq!(db.constants_add(('a', 'b')), 66);
assert!(!db.query(ConstantsAdd).is_constant(('a', 'b')));
assert!(!db.query(ConstantsAddQuery).is_constant(('a', 'b')));
}
#[test]
fn is_constant() {
let db = &mut TestContextImpl::default();
db.query_mut(ConstantsInput).set_constant('a', 22);
db.query_mut(ConstantsInput).set_constant('b', 44);
db.query_mut(ConstantsInputQuery).set_constant('a', 22);
db.query_mut(ConstantsInputQuery).set_constant('b', 44);
assert_eq!(db.constants_add(('a', 'b')), 66);
assert!(db.query(ConstantsAdd).is_constant(('a', 'b')));
assert!(db.query(ConstantsAddQuery).is_constant(('a', 'b')));
}
#[test]
fn mixed_constant() {
let db = &mut TestContextImpl::default();
db.query_mut(ConstantsInput).set_constant('a', 22);
db.query_mut(ConstantsInput).set('b', 44);
db.query_mut(ConstantsInputQuery).set_constant('a', 22);
db.query_mut(ConstantsInputQuery).set('b', 44);
assert_eq!(db.constants_add(('a', 'b')), 66);
assert!(!db.query(ConstantsAdd).is_constant(('a', 'b')));
assert!(!db.query(ConstantsAddQuery).is_constant(('a', 'b')));
}
#[test]
fn becomes_constant_with_change() {
let db = &mut TestContextImpl::default();
db.query_mut(ConstantsInput).set('a', 22);
db.query_mut(ConstantsInput).set('b', 44);
db.query_mut(ConstantsInputQuery).set('a', 22);
db.query_mut(ConstantsInputQuery).set('b', 44);
assert_eq!(db.constants_add(('a', 'b')), 66);
assert!(!db.query(ConstantsAdd).is_constant(('a', 'b')));
assert!(!db.query(ConstantsAddQuery).is_constant(('a', 'b')));
db.query_mut(ConstantsInput).set_constant('a', 23);
db.query_mut(ConstantsInputQuery).set_constant('a', 23);
assert_eq!(db.constants_add(('a', 'b')), 67);
assert!(!db.query(ConstantsAdd).is_constant(('a', 'b')));
assert!(!db.query(ConstantsAddQuery).is_constant(('a', 'b')));
db.query_mut(ConstantsInput).set_constant('b', 45);
db.query_mut(ConstantsInputQuery).set_constant('b', 45);
assert_eq!(db.constants_add(('a', 'b')), 68);
assert!(db.query(ConstantsAdd).is_constant(('a', 'b')));
assert!(db.query(ConstantsAddQuery).is_constant(('a', 'b')));
}

View file

@ -41,28 +41,28 @@ impl TestContextImpl {
salsa::database_storage! {
pub(crate) struct TestContextImplStorage for TestContextImpl {
impl constants::ConstantsDatabase {
fn constants_input() for constants::ConstantsInput;
fn constants_derived() for constants::ConstantsAdd;
fn constants_input() for constants::ConstantsInputQuery;
fn constants_derived() for constants::ConstantsAddQuery;
}
impl memoized_dep_inputs::MemoizedDepInputsContext {
fn dep_memoized2() for memoized_dep_inputs::Memoized2;
fn dep_memoized1() for memoized_dep_inputs::Memoized1;
fn dep_derived1() for memoized_dep_inputs::Derived1;
fn dep_input1() for memoized_dep_inputs::Input1;
fn dep_input2() for memoized_dep_inputs::Input2;
fn dep_memoized2() for memoized_dep_inputs::DepMemoized2Query;
fn dep_memoized1() for memoized_dep_inputs::DepMemoized1Query;
fn dep_derived1() for memoized_dep_inputs::DepDerived1Query;
fn dep_input1() for memoized_dep_inputs::DepInput1Query;
fn dep_input2() for memoized_dep_inputs::DepInput2Query;
}
impl memoized_inputs::MemoizedInputsContext {
fn max() for memoized_inputs::Max;
fn input1() for memoized_inputs::Input1;
fn input2() for memoized_inputs::Input2;
fn max() for memoized_inputs::MaxQuery;
fn input1() for memoized_inputs::Input1Query;
fn input2() for memoized_inputs::Input2Query;
}
impl memoized_volatile::MemoizedVolatileContext {
fn memoized2() for memoized_volatile::Memoized2;
fn memoized1() for memoized_volatile::Memoized1;
fn volatile() for memoized_volatile::Volatile;
fn memoized2() for memoized_volatile::Memoized2Query;
fn memoized1() for memoized_volatile::Memoized1Query;
fn volatile() for memoized_volatile::VolatileQuery;
}
}
}

View file

@ -1,27 +1,16 @@
use crate::implementation::{TestContext, TestContextImpl};
use salsa::Database;
salsa::query_group! {
pub(crate) trait MemoizedDepInputsContext: TestContext {
fn dep_memoized2() -> usize {
type Memoized2;
}
fn dep_memoized1() -> usize {
type Memoized1;
}
fn dep_derived1() -> usize {
type Derived1;
storage dependencies;
}
fn dep_input1() -> usize {
type Input1;
storage input;
}
fn dep_input2() -> usize {
type Input2;
storage input;
}
}
#[salsa::query_group]
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: &impl MemoizedDepInputsContext) -> usize {
@ -43,7 +32,7 @@ fn dep_derived1(db: &impl MemoizedDepInputsContext) -> usize {
fn revalidate() {
let db = &mut TestContextImpl::default();
db.query_mut(Input1).set((), 0);
db.query_mut(DepInput1Query).set((), 0);
// Initial run starts from Memoized2:
let v = db.dep_memoized2();
@ -53,19 +42,19 @@ fn revalidate() {
// 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.query_mut(Input1).set((), 44);
db.query_mut(DepInput1Query).set((), 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.query_mut(Input1).set((), 45);
db.query_mut(DepInput1Query).set((), 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.query_mut(Input2).set((), 45);
db.query_mut(DepInput2Query).set((), 45);
let v = db.dep_memoized2();
assert_eq!(v, 44);
db.assert_log(&[]);

View file

@ -1,20 +1,13 @@
use crate::implementation::{TestContext, TestContextImpl};
use salsa::Database;
salsa::query_group! {
pub(crate) trait MemoizedInputsContext: TestContext {
fn max() -> usize {
type Max;
}
fn input1() -> usize {
type Input1;
storage input;
}
fn input2() -> usize {
type Input2;
storage input;
}
}
#[salsa::query_group]
pub(crate) trait MemoizedInputsContext: TestContext {
fn max(&self) -> usize;
#[salsa::input]
fn input1(&self) -> usize;
#[salsa::input]
fn input2(&self) -> usize;
}
fn max(db: &impl MemoizedInputsContext) -> usize {
@ -26,8 +19,8 @@ fn max(db: &impl MemoizedInputsContext) -> usize {
fn revalidate() {
let db = &mut TestContextImpl::default();
db.query_mut(Input1).set((), 0);
db.query_mut(Input2).set((), 0);
db.query_mut(Input1Query).set((), 0);
db.query_mut(Input2Query).set((), 0);
let v = db.max();
assert_eq!(v, 0);
@ -37,7 +30,7 @@ fn revalidate() {
assert_eq!(v, 0);
db.assert_log(&[]);
db.query_mut(Input1).set((), 44);
db.query_mut(Input1Query).set((), 44);
db.assert_log(&[]);
let v = db.max();
@ -48,11 +41,11 @@ fn revalidate() {
assert_eq!(v, 44);
db.assert_log(&[]);
db.query_mut(Input1).set((), 44);
db.query_mut(Input1Query).set((), 44);
db.assert_log(&[]);
db.query_mut(Input2).set((), 66);
db.query_mut(Input2Query).set((), 66);
db.assert_log(&[]);
db.query_mut(Input1).set((), 64);
db.query_mut(Input1Query).set((), 64);
db.assert_log(&[]);
let v = db.max();
@ -70,14 +63,14 @@ fn revalidate() {
fn set_after_no_change() {
let db = &mut TestContextImpl::default();
db.query_mut(Input2).set((), 0);
db.query_mut(Input2Query).set((), 0);
db.query_mut(Input1).set((), 44);
db.query_mut(Input1Query).set((), 44);
let v = db.max();
assert_eq!(v, 44);
db.assert_log(&["Max invoked"]);
db.query_mut(Input1).set((), 44);
db.query_mut(Input1Query).set((), 44);
let v = db.max();
assert_eq!(v, 44);
db.assert_log(&["Max invoked"]);

View file

@ -1,21 +1,14 @@
use crate::implementation::{TestContext, TestContextImpl};
use salsa::Database;
salsa::query_group! {
pub(crate) trait MemoizedVolatileContext: TestContext {
// Queries for testing a "volatile" value wrapped by
// memoization.
fn memoized2() -> usize {
type Memoized2;
}
fn memoized1() -> usize {
type Memoized1;
}
fn volatile() -> usize {
type Volatile;
storage volatile;
}
}
#[salsa::query_group]
pub(crate) trait MemoizedVolatileContext: TestContext {
// Queries for testing a "volatile" value wrapped by
// memoization.
fn memoized2(&self) -> usize;
fn memoized1(&self) -> usize;
#[salsa::volatile]
fn volatile(&self) -> usize;
}
fn memoized2(db: &impl MemoizedVolatileContext) -> usize {

View file

@ -1,10 +1,7 @@
salsa::query_group! {
trait MyDatabase: salsa::Database {
fn my_query(key: ()) -> () {
type MyQuery;
use fn another_module::another_name;
}
}
#[salsa::query_group]
trait MyDatabase: salsa::Database {
#[salsa::invoke(another_module::another_name)]
fn my_query(&self, key: ()) -> ();
}
mod another_module {

View file

@ -1,17 +1,12 @@
use salsa::{Database, ParallelDatabase, Snapshot};
use std::panic::{self, AssertUnwindSafe};
salsa::query_group! {
trait PanicSafelyDatabase: salsa::Database {
fn one() -> usize {
type One;
storage input;
}
#[salsa::query_group]
trait PanicSafelyDatabase: salsa::Database {
#[salsa::input]
fn one(&self) -> usize;
fn panic_safely() -> () {
type PanicSafely;
}
}
fn panic_safely(&self) -> ();
}
fn panic_safely(db: &impl PanicSafelyDatabase) -> () {
@ -40,8 +35,8 @@ impl salsa::ParallelDatabase for DatabaseStruct {
salsa::database_storage! {
struct DatabaseStorage for DatabaseStruct {
impl PanicSafelyDatabase {
fn one() for One;
fn panic_safely() for PanicSafely;
fn one() for OneQuery;
fn panic_safely() for PanicSafelyQuery;
}
}
}
@ -59,7 +54,7 @@ fn should_panic_safely() {
assert!(result.is_err());
// Set `db.one` to 1 and assert ok
db.query_mut(One).set((), 1);
db.query_mut(OneQuery).set((), 1);
let result = panic::catch_unwind(AssertUnwindSafe(|| db.panic_safely()));
assert!(result.is_ok())
}

View file

@ -1,5 +1,5 @@
use crate::setup::{
CancelationFlag, Canceled, Input, Knobs, ParDatabase, ParDatabaseImpl, WithValue,
CancelationFlag, Canceled, InputQuery, Knobs, ParDatabase, ParDatabaseImpl, WithValue,
};
use salsa::{Database, ParallelDatabase};
@ -34,10 +34,10 @@ fn in_par_get_set_cancellation_immediate() {
check_cancelation(|flag| {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 100);
db.query_mut(Input).set('b', 010);
db.query_mut(Input).set('c', 001);
db.query_mut(Input).set('d', 0);
db.query_mut(InputQuery).set('a', 100);
db.query_mut(InputQuery).set('b', 010);
db.query_mut(InputQuery).set('c', 001);
db.query_mut(InputQuery).set('d', 0);
let thread1 = std::thread::spawn({
let db = db.snapshot();
@ -56,7 +56,7 @@ fn in_par_get_set_cancellation_immediate() {
db.wait_for(1);
// Try to set the input. This will signal cancellation.
db.query_mut(Input).set('d', 1000);
db.query_mut(InputQuery).set('d', 1000);
// This should re-compute the value (even though no input has changed).
let thread2 = std::thread::spawn({
@ -77,10 +77,10 @@ fn in_par_get_set_cancellation_transitive() {
check_cancelation(|flag| {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 100);
db.query_mut(Input).set('b', 010);
db.query_mut(Input).set('c', 001);
db.query_mut(Input).set('d', 0);
db.query_mut(InputQuery).set('a', 100);
db.query_mut(InputQuery).set('b', 010);
db.query_mut(InputQuery).set('c', 001);
db.query_mut(InputQuery).set('d', 0);
let thread1 = std::thread::spawn({
let db = db.snapshot();
@ -99,7 +99,7 @@ fn in_par_get_set_cancellation_transitive() {
db.wait_for(1);
// Try to set the input. This will signal cancellation.
db.query_mut(Input).set('d', 1000);
db.query_mut(InputQuery).set('d', 1000);
// This should re-compute the value (even though no input has changed).
let thread2 = std::thread::spawn({
@ -119,7 +119,7 @@ fn no_back_dating_in_cancellation() {
check_cancelation(|flag| {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 1);
db.query_mut(InputQuery).set('a', 1);
let thread1 = std::thread::spawn({
let db = db.snapshot();
move || {
@ -136,7 +136,7 @@ fn no_back_dating_in_cancellation() {
db.wait_for(1);
// Set unrelated input to bump revision
db.query_mut(Input).set('b', 2);
db.query_mut(InputQuery).set('b', 2);
// Here we should recompuet the whole chain again, clearing the cancellation
// state. If we get `usize::max()` here, it is a bug!
@ -144,8 +144,8 @@ fn no_back_dating_in_cancellation() {
assert_canceled!(flag, thread1);
db.query_mut(Input).set('a', 3);
db.query_mut(Input).set('a', 4);
db.query_mut(InputQuery).set('a', 3);
db.query_mut(InputQuery).set('a', 4);
assert_eq!(db.sum3("ab"), 6);
})
}
@ -160,7 +160,7 @@ fn no_back_dating_in_cancellation() {
fn transitive_cancellation() {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 1);
db.query_mut(InputQuery).set('a', 1);
let thread1 = std::thread::spawn({
let db = db.snapshot();
move || {
@ -176,7 +176,7 @@ fn transitive_cancellation() {
db.wait_for(1);
db.query_mut(Input).set('b', 2);
db.query_mut(InputQuery).set('b', 2);
// Check that when we call `sum3_drop_sum` we don't wind up having
// to actually re-execute it, because the result of `sum2` winds

View file

@ -1,4 +1,4 @@
use crate::setup::{Input, ParDatabase, ParDatabaseImpl};
use crate::setup::{InputQuery, ParDatabase, ParDatabaseImpl};
use crate::signal::Signal;
use salsa::{Database, ParallelDatabase};
use std::sync::Arc;
@ -10,7 +10,7 @@ use std::sync::Arc;
fn in_par_get_set_cancellation() {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 1);
db.query_mut(InputQuery).set('a', 1);
let signal = Arc::new(Signal::default());
@ -50,7 +50,7 @@ fn in_par_get_set_cancellation() {
signal.wait_for(1);
// This will block until thread1 drops the revision lock.
db.query_mut(Input).set('a', 2);
db.query_mut(InputQuery).set('a', 2);
db.input('a')
}

View file

@ -1,4 +1,4 @@
use crate::setup::{Input, ParDatabase, ParDatabaseImpl};
use crate::setup::{InputQuery, ParDatabase, ParDatabaseImpl};
use salsa::{Database, ParallelDatabase};
/// Test two `sum` queries (on distinct keys) executing in different
@ -7,12 +7,12 @@ use salsa::{Database, ParallelDatabase};
fn in_par_two_independent_queries() {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 100);
db.query_mut(Input).set('b', 010);
db.query_mut(Input).set('c', 001);
db.query_mut(Input).set('d', 200);
db.query_mut(Input).set('e', 020);
db.query_mut(Input).set('f', 002);
db.query_mut(InputQuery).set('a', 100);
db.query_mut(InputQuery).set('b', 010);
db.query_mut(InputQuery).set('c', 001);
db.query_mut(InputQuery).set('d', 200);
db.query_mut(InputQuery).set('e', 020);
db.query_mut(InputQuery).set('f', 002);
let thread1 = std::thread::spawn({
let db = db.snapshot();

View file

@ -1,4 +1,4 @@
use crate::setup::{Input, ParDatabase, ParDatabaseImpl};
use crate::setup::{InputQuery, ParDatabase, ParDatabaseImpl};
use salsa::{Database, ParallelDatabase};
/// Test where a read and a set are racing with one another.
@ -7,9 +7,9 @@ use salsa::{Database, ParallelDatabase};
fn in_par_get_set_race() {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 100);
db.query_mut(Input).set('b', 010);
db.query_mut(Input).set('c', 001);
db.query_mut(InputQuery).set('a', 100);
db.query_mut(InputQuery).set('b', 010);
db.query_mut(InputQuery).set('c', 001);
let thread1 = std::thread::spawn({
let db = db.snapshot();
@ -20,7 +20,7 @@ fn in_par_get_set_race() {
});
let thread2 = std::thread::spawn(move || {
db.query_mut(Input).set('a', 1000);
db.query_mut(InputQuery).set('a', 1000);
db.sum("a")
});

View file

@ -5,41 +5,26 @@ use salsa::Snapshot;
use std::cell::Cell;
use std::sync::Arc;
salsa::query_group! {
pub(crate) trait ParDatabase: Knobs + salsa::ParallelDatabase {
fn input(key: char) -> usize {
type Input;
storage input;
}
#[salsa::query_group]
pub(crate) trait ParDatabase: Knobs + salsa::ParallelDatabase {
#[salsa::input]
fn input(&self, key: char) -> usize;
fn sum(key: &'static str) -> usize {
type Sum;
}
fn sum(&self, key: &'static str) -> usize;
/// Invokes `sum`
fn sum2(key: &'static str) -> usize {
type Sum2;
}
/// Invokes `sum`
fn sum2(&self, key: &'static str) -> usize;
/// Invokes `sum` but doesn't really care about the result.
fn sum2_drop_sum(key: &'static str) -> usize {
type Sum2Drop;
}
/// Invokes `sum` but doesn't really care about the result.
fn sum2_drop_sum(&self, key: &'static str) -> usize;
/// Invokes `sum2`
fn sum3(key: &'static str) -> usize {
type Sum3;
}
/// Invokes `sum2`
fn sum3(&self, key: &'static str) -> usize;
/// Invokes `sum2_drop_sum`
fn sum3_drop_sum(key: &'static str) -> usize {
type Sum3Drop;
}
/// Invokes `sum2_drop_sum`
fn sum3_drop_sum(&self, key: &'static str) -> usize;
fn snapshot_me() -> () {
type SnapshotMe;
}
}
fn snapshot_me(&self) -> ();
}
#[derive(PartialEq, Eq)]
@ -252,13 +237,13 @@ impl Knobs for ParDatabaseImpl {
salsa::database_storage! {
pub struct DatabaseImplStorage for ParDatabaseImpl {
impl ParDatabase {
fn input() for Input;
fn sum() for Sum;
fn sum2() for Sum2;
fn sum2_drop_sum() for Sum2Drop;
fn sum3() for Sum3;
fn sum3_drop_sum() for Sum3Drop;
fn snapshot_me() for SnapshotMe;
fn input() for InputQuery;
fn sum() for SumQuery;
fn sum2() for Sum2Query;
fn sum2_drop_sum() for Sum2DropSumQuery;
fn sum3() for Sum3Query;
fn sum3_drop_sum() for Sum3DropSumQuery;
fn snapshot_me() for SnapshotMeQuery;
}
}
}

View file

@ -12,21 +12,14 @@ const N_READER_OPS: usize = 100;
struct Canceled;
type Cancelable<T> = Result<T, Canceled>;
salsa::query_group! {
trait StressDatabase: salsa::Database {
fn a(key: usize) -> usize {
type A;
storage input;
}
#[salsa::query_group]
trait StressDatabase: salsa::Database {
#[salsa::input]
fn a(&self, key: usize) -> usize;
fn b(key: usize) -> Cancelable<usize> {
type B;
}
fn b(&self, key: usize) -> Cancelable<usize>;
fn c(key: usize) -> Cancelable<usize> {
type C;
}
}
fn c(&self, key: usize) -> Cancelable<usize>;
}
fn b(db: &impl StressDatabase, key: usize) -> Cancelable<usize> {
@ -62,9 +55,9 @@ impl salsa::ParallelDatabase for StressDatabaseImpl {
salsa::database_storage! {
pub struct DatabaseImplStorage for StressDatabaseImpl {
impl StressDatabase {
fn a() for A;
fn b() for B;
fn c() for C;
fn a() for AQuery;
fn b() for BQuery;
fn c() for CQuery;
}
}
}
@ -157,7 +150,7 @@ impl WriteOp {
fn execute(self, db: &mut StressDatabaseImpl) {
match self {
WriteOp::SetA(key, value) => {
db.query_mut(A).set(key, value);
db.query_mut(AQuery).set(key, value);
}
}
}
@ -179,13 +172,13 @@ impl ReadOp {
},
ReadOp::Gc(query, strategy) => match query {
Query::A => {
db.query(A).sweep(strategy);
db.query(AQuery).sweep(strategy);
}
Query::B => {
db.query(B).sweep(strategy);
db.query(BQuery).sweep(strategy);
}
Query::C => {
db.query(C).sweep(strategy);
db.query(CQuery).sweep(strategy);
}
},
ReadOp::GcAll(strategy) => {
@ -199,7 +192,7 @@ impl ReadOp {
fn stress_test() {
let mut db = StressDatabaseImpl::default();
for i in 0..10 {
db.query_mut(A).set(i, i);
db.query_mut(AQuery).set(i, i);
}
let mut rng = rand::thread_rng();

View file

@ -1,4 +1,4 @@
use crate::setup::{Input, Knobs, ParDatabase, ParDatabaseImpl, WithValue};
use crate::setup::{InputQuery, Knobs, ParDatabase, ParDatabaseImpl, WithValue};
use salsa::{Database, ParallelDatabase};
use std::panic::{self, AssertUnwindSafe};
@ -10,9 +10,9 @@ use std::panic::{self, AssertUnwindSafe};
fn true_parallel_different_keys() {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 100);
db.query_mut(Input).set('b', 010);
db.query_mut(Input).set('c', 001);
db.query_mut(InputQuery).set('a', 100);
db.query_mut(InputQuery).set('b', 010);
db.query_mut(InputQuery).set('c', 001);
// Thread 1 will signal stage 1 when it enters and wait for stage 2.
let thread1 = std::thread::spawn({
@ -50,9 +50,9 @@ fn true_parallel_different_keys() {
fn true_parallel_same_keys() {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 100);
db.query_mut(Input).set('b', 010);
db.query_mut(Input).set('c', 001);
db.query_mut(InputQuery).set('a', 100);
db.query_mut(InputQuery).set('b', 010);
db.query_mut(InputQuery).set('c', 001);
// Thread 1 will wait_for a barrier in the start of `sum`
let thread1 = std::thread::spawn({
@ -91,7 +91,7 @@ fn true_parallel_same_keys() {
fn true_parallel_propagate_panic() {
let mut db = ParDatabaseImpl::default();
db.query_mut(Input).set('a', 1);
db.query_mut(InputQuery).set('a', 1);
// `thread1` will wait_for a barrier in the start of `sum`. Once it can
// continue, it will panic.

View file

@ -1,20 +1,13 @@
use salsa::Database;
salsa::query_group! {
trait HelloWorldDatabase: salsa::Database {
fn input() -> String {
type Input;
storage input;
}
#[salsa::query_group]
trait HelloWorldDatabase: salsa::Database {
#[salsa::input]
fn input(&self) -> String;
fn length() -> usize {
type Length;
}
fn length(&self) -> usize;
fn double_length() -> usize {
type DoubleLength;
}
}
fn double_length(&self) -> usize;
}
fn length(db: &impl HelloWorldDatabase) -> usize {
@ -41,9 +34,9 @@ impl salsa::Database for DatabaseStruct {
salsa::database_storage! {
struct DatabaseStorage for DatabaseStruct {
impl HelloWorldDatabase {
fn input() for Input;
fn length() for Length;
fn double_length() for DoubleLength;
fn input() for InputQuery;
fn length() for LengthQuery;
fn double_length() for DoubleLengthQuery;
}
}
}
@ -51,9 +44,9 @@ salsa::database_storage! {
#[test]
fn normal() {
let mut db = DatabaseStruct::default();
db.query_mut(Input).set((), format!("Hello, world"));
db.query_mut(InputQuery).set((), format!("Hello, world"));
assert_eq!(db.double_length(), 24);
db.query_mut(Input).set((), format!("Hello, world!"));
db.query_mut(InputQuery).set((), format!("Hello, world!"));
assert_eq!(db.double_length(), 26);
}
@ -67,7 +60,7 @@ fn use_without_set() {
#[test]
fn using_set_unchecked_on_input() {
let mut db = DatabaseStruct::default();
db.query_mut(Input)
db.query_mut(InputQuery)
.set_unchecked((), format!("Hello, world"));
assert_eq!(db.double_length(), 24);
}
@ -75,12 +68,12 @@ fn using_set_unchecked_on_input() {
#[test]
fn using_set_unchecked_on_input_after() {
let mut db = DatabaseStruct::default();
db.query_mut(Input).set((), format!("Hello, world"));
db.query_mut(InputQuery).set((), format!("Hello, world"));
assert_eq!(db.double_length(), 24);
// If we use `set_unchecked`, we don't notice that `double_length`
// is out of date. Oh well, don't do that.
db.query_mut(Input)
db.query_mut(InputQuery)
.set_unchecked((), format!("Hello, world!"));
assert_eq!(db.double_length(), 24);
}
@ -91,7 +84,7 @@ fn using_set_unchecked() {
// Use `set_unchecked` to intentionally set the wrong value,
// demonstrating that the code never runs.
db.query_mut(Length).set_unchecked((), 24);
db.query_mut(LengthQuery).set_unchecked((), 24);
assert_eq!(db.double_length(), 48);
}

View file

@ -10,8 +10,8 @@ pub struct DatabaseImpl {
salsa::database_storage! {
pub struct DatabaseImplStorage for DatabaseImpl {
impl queries::Database {
fn memoized() for queries::Memoized;
fn volatile() for queries::Volatile;
fn memoized() for queries::MemoizedQuery;
fn volatile() for queries::VolatileQuery;
}
}
}

View file

@ -2,16 +2,11 @@ pub(crate) trait Counter: salsa::Database {
fn increment(&self) -> usize;
}
salsa::query_group! {
pub(crate) trait Database: Counter {
fn memoized() -> usize {
type Memoized;
}
fn volatile() -> usize {
type Volatile;
storage volatile;
}
}
#[salsa::query_group]
pub(crate) trait Database: Counter {
fn memoized(&self) -> usize;
#[salsa::volatile]
fn volatile(&self) -> usize;
}
/// Because this query is memoized, we only increment the counter

View file

@ -1,28 +1,17 @@
use salsa::Database;
salsa::query_group! {
trait HelloWorldDatabase: salsa::Database {
fn input(a: u32, b: u32) -> u32 {
type Input;
storage input;
}
#[salsa::query_group]
trait HelloWorldDatabase: salsa::Database {
#[salsa::input]
fn input(&self, a: u32, b: u32) -> u32;
fn none() -> u32 {
type None;
}
fn none(&self) -> u32;
fn one(k: u32) -> u32 {
type One;
}
fn one(&self, k: u32) -> u32;
fn two(a: u32, b: u32) -> u32 {
type Two;
}
fn two(&self, a: u32, b: u32) -> u32;
fn trailing(a: u32, b: u32,) -> u32 {
type Trailing;
}
}
fn trailing(&self, a: u32, b: u32) -> u32;
}
fn none(_db: &impl HelloWorldDatabase) -> u32 {
@ -55,11 +44,11 @@ impl salsa::Database for DatabaseStruct {
salsa::database_storage! {
struct DatabaseStorage for DatabaseStruct {
impl HelloWorldDatabase {
fn input() for Input;
fn none() for None;
fn one() for One;
fn two() for Two;
fn trailing() for Trailing;
fn input() for InputQuery;
fn none() for NoneQuery;
fn one() for OneQuery;
fn two() for TwoQuery;
fn trailing() for TrailingQuery;
}
}
}
@ -69,7 +58,7 @@ fn execute() {
let mut db = DatabaseStruct::default();
// test what happens with inputs:
db.query_mut(Input).set((1, 2), 3);
db.query_mut(InputQuery).set((1, 2), 3);
assert_eq!(db.input(1, 2), 3);
assert_eq!(db.none(), 22);