mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-01-23 05:07:27 +00:00
Merge pull request #126 from nikomatsakis/group-struct
generate `set_X` and `set_constant_X` methods for each input
This commit is contained in:
commit
83b3ad13f8
6 changed files with 89 additions and 248 deletions
|
@ -48,8 +48,7 @@ Using salsa is as easy as 1, 2, 3...
|
|||
|
||||
To see an example of this in action, check out [the `hello_world`
|
||||
example](examples/hello_world/main.rs), which has a number of comments
|
||||
explaining how things work. The [`hello_world`
|
||||
README](examples/hello_world/README.md) has a more detailed writeup.
|
||||
explaining how things work.
|
||||
|
||||
Salsa requires at least Rust 1.30 (beta at the time of writing).
|
||||
|
||||
|
|
|
@ -156,6 +156,44 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
|
|||
}
|
||||
});
|
||||
|
||||
// For input queries, we need `set_foo` etc
|
||||
if let QueryStorage::Input = query.storage {
|
||||
let set_fn_name = Ident::new(&format!("set_{}", fn_name), fn_name.span());
|
||||
let set_constant_fn_name =
|
||||
Ident::new(&format!("set_constant_{}", fn_name), fn_name.span());
|
||||
|
||||
query_fn_declarations.extend(quote! {
|
||||
/// 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 #set_fn_name(&mut self, #(#key_names: #keys,)* value__: #value);
|
||||
|
||||
/// Set the value of the `#fn_name` input and 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 #set_constant_fn_name(&mut self, #(#key_names: #keys,)* value__: #value);
|
||||
});
|
||||
|
||||
query_fn_definitions.extend(quote! {
|
||||
fn #set_fn_name(&mut self, #(#key_names: #keys,)* value__: #value) {
|
||||
<Self as ::salsa::plumbing::GetQueryTable<#qt>>::get_query_table_mut(self).set((#(#key_names),*), value__)
|
||||
}
|
||||
|
||||
fn #set_constant_fn_name(&mut self, #(#key_names: #keys,)* value__: #value) {
|
||||
<Self as ::salsa::plumbing::GetQueryTable<#qt>>::get_query_table_mut(self).set_constant((#(#key_names),*), value__)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// A variant for the group descriptor below
|
||||
query_descriptor_variants.extend(quote! {
|
||||
#fn_name((#(#keys),*)),
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
The `hello_world` example is intended to walk through the very basics
|
||||
of a salsa setup. Here is a more detailed writeup.
|
||||
|
||||
### Step 1: Define a query group
|
||||
|
||||
A **query group** is a collection of queries (both inputs and
|
||||
functions) that are defined in one particular spot. Each query group
|
||||
represents some subset of the full set of queries you will use in your
|
||||
application. Query groups can also depend on one another: so you might
|
||||
have some basic query group A and then another query group B that uses
|
||||
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 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 {
|
||||
#[salsa::input]
|
||||
#[salsa::query_type(InputString)]
|
||||
fn input_string(&self, key: ()) -> Arc<String>;
|
||||
|
||||
fn length(&self, key: ()) -> usize;
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
application, and we wanted to invoke some of those queries from within
|
||||
this query group, we might list those query groups here. You can also
|
||||
list any other traits you want, so long as your final database type
|
||||
implements them (this lets you add custom state and so forth to your
|
||||
database).
|
||||
|
||||
Within this trait, we list out the queries that this group provides.
|
||||
Here, there are two: `input_string` and `length`. For each query, you
|
||||
specify a function signature: the parameters to the function are
|
||||
called the "key types" (in this case, we just give a single key of
|
||||
type `()`) and the return type is the "value type". You can have any
|
||||
number of key types. As you can see, though, this is not a real fn --
|
||||
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, 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 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
|
||||
|
||||
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 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:
|
||||
|
||||
```rust
|
||||
fn length(db: &impl HelloWorldDatabase, (): ()) -> usize {
|
||||
// Read the input string:
|
||||
let input_string = db.input_string(());
|
||||
|
||||
// Return its length:
|
||||
input_string.len()
|
||||
}
|
||||
```
|
||||
|
||||
Note that every query function takes two arguments: the first is your
|
||||
database, which you access via a generic that references your trait
|
||||
(e.g., `impl HelloWorldDatabase`). The second is the key -- in this
|
||||
simple example, that's just `()`.
|
||||
|
||||
**Invoking a query.** In the first line of the function we see how to
|
||||
invoke a query for a given key:
|
||||
|
||||
```rust
|
||||
let input_string = db.input_string(());
|
||||
```
|
||||
|
||||
You simply call the function and give the key you want -- in this case
|
||||
`()`.
|
||||
|
||||
### Step 3: Define the database struct
|
||||
|
||||
The final step is to create the **database struct** which will
|
||||
implement the traits from each of your query groups. This struct
|
||||
combines all the parts of your system into one whole; it can also add
|
||||
custom state of your own (such as an interner or configuration). In
|
||||
our simple example though we won't do any of that. The only field that
|
||||
you **actually** need is a reference to the **salsa runtime**; then
|
||||
you must also implement the `salsa::Database` trait to tell salsa
|
||||
where to find this runtime:
|
||||
|
||||
```rust
|
||||
#[derive(Default)]
|
||||
struct DatabaseStruct {
|
||||
runtime: salsa::Runtime<DatabaseStruct>,
|
||||
}
|
||||
|
||||
impl salsa::Database for DatabaseStruct {
|
||||
fn salsa_runtime(&self) -> &salsa::Runtime<DatabaseStruct> {
|
||||
&self.runtime
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next, you must use the `database_storage!` to specify the set of query
|
||||
groups that your database stores. This macro generates the internal
|
||||
storage struct used to store your data. To use the macro, you
|
||||
basically list out all the traits:
|
||||
|
||||
```rust
|
||||
salsa::database_storage! {
|
||||
DatabaseStruct { // <-- name of your context type
|
||||
impl HelloWorldDatabase;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `database_storage` macro will also implement the
|
||||
`HelloWorldDatabase` trait for your query context type.
|
||||
|
||||
**Use the database.** Now that we've defined our database, we can
|
||||
start using it:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut db = DatabaseStruct::default();
|
||||
|
||||
println!("Initially, the length is {}.", db.length().get(()));
|
||||
|
||||
db.query_mut(InputString)
|
||||
.set((), Arc::new(format!("Hello, world")));
|
||||
|
||||
println!("Now, the length is {}.", db.length().get(()));
|
||||
}
|
||||
```
|
||||
|
||||
One thing to notice here is how we set the value for an input query:
|
||||
|
||||
```rust
|
||||
db.query_mut(InputString)
|
||||
.set((), Arc::new(format!("Hello, world")));
|
||||
```
|
||||
|
||||
The `db.query_mut(Foo)` method takes as argument the query type that
|
||||
characterizes your query. It gives back a "mutable query table" type,
|
||||
which lets you invoke `set` to set the value of an input query. There
|
||||
is also a `query` method that gives access to other advanced methods
|
||||
in fact, the standard call `db.query_name(key)` to access a query is
|
||||
just a shorthand for `db.query(QueryType).get(key)`.
|
||||
|
||||
Finally, if we run this code:
|
||||
|
||||
```bash
|
||||
> cargo run --example hello_world
|
||||
Compiling salsa v0.2.0 (/Users/nmatsakis/versioned/salsa)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.94s
|
||||
Running `target/debug/examples/hello_world`
|
||||
Initially, the length is 0.
|
||||
Now, the length is 12.
|
||||
```
|
||||
|
||||
Amazing.
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
use salsa::Database;
|
||||
use std::sync::Arc;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -14,17 +13,15 @@ use std::sync::Arc;
|
|||
// 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>`. We can use attributes to
|
||||
// give other configuration:
|
||||
// For each query, we give the name, some input keys (here, we
|
||||
// have one key, `()`) 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`.
|
||||
// which must be explicitly set. The `salsa::query_group` method
|
||||
// will autogenerate a `set_input_string` method that can be
|
||||
// used to set the input.
|
||||
#[salsa::input]
|
||||
#[salsa::query_type(InputString)]
|
||||
fn input_string(&self, key: ()) -> Arc<String>;
|
||||
|
||||
// This is a *derived query*, meaning its value is specified by
|
||||
|
@ -56,8 +53,9 @@ fn length(db: &impl HelloWorldDatabase, (): ()) -> usize {
|
|||
|
||||
// Define the actual database struct. This struct needs to be
|
||||
// annotated with `#[salsa::database(..)]`, which contains a list of
|
||||
// query groups that this database supports. This attribute macro will generate
|
||||
// the necessary impls so that the database implements all of those traits.
|
||||
// query groups that this database supports. This attribute macro will
|
||||
// generate the necessary impls so that the database implements all of
|
||||
// those traits.
|
||||
//
|
||||
// The database struct can contain basically anything you need, but it
|
||||
// must have a `runtime` field as shown, and you must implement the
|
||||
|
@ -79,12 +77,12 @@ impl salsa::Database for DatabaseStruct {
|
|||
fn main() {
|
||||
let mut db = DatabaseStruct::default();
|
||||
|
||||
// You cannot access input_string yet, because it does not have a value. If you do, it will
|
||||
// panic. You could create an Option interface by maintaining a HashSet of inserted keys.
|
||||
// You cannot access input_string yet, because it does not have a
|
||||
// value. If you do, it will panic. You could create an Option
|
||||
// interface by maintaining a HashSet of inserted keys.
|
||||
// println!("Initially, the length is {}.", db.length(()));
|
||||
|
||||
db.query_mut(InputString)
|
||||
.set((), Arc::new(format!("Hello, world")));
|
||||
db.set_input_string((), Arc::new(format!("Hello, world")));
|
||||
|
||||
println!("Now, the length is {}.", db.length(()));
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ fn compute_one() {
|
|||
let mut db = db::DatabaseImpl::default();
|
||||
|
||||
// Will compute fibonacci(5)
|
||||
db.query_mut(UseTriangularQuery).set(5, false);
|
||||
db.set_use_triangular(5, false);
|
||||
db.compute(5);
|
||||
|
||||
db.salsa_runtime().next_revision();
|
||||
|
@ -43,11 +43,11 @@ fn compute_switch() {
|
|||
let mut db = db::DatabaseImpl::default();
|
||||
|
||||
// Will compute fibonacci(5)
|
||||
db.query_mut(UseTriangularQuery).set(5, false);
|
||||
db.set_use_triangular(5, false);
|
||||
assert_eq!(db.compute(5), 5);
|
||||
|
||||
// Change to triangular mode
|
||||
db.query_mut(UseTriangularQuery).set(5, true);
|
||||
db.set_use_triangular(5, true);
|
||||
|
||||
// Now computes triangular(5)
|
||||
assert_eq!(db.compute(5), 15);
|
||||
|
@ -100,7 +100,7 @@ fn compute_all() {
|
|||
let mut db = db::DatabaseImpl::default();
|
||||
|
||||
for i in 0..6 {
|
||||
db.query_mut(UseTriangularQuery).set(i, (i % 2) != 0);
|
||||
db.set_use_triangular(i, (i % 2) != 0);
|
||||
}
|
||||
|
||||
db.query_mut(MinQuery).set((), 0);
|
||||
|
|
|
@ -5,22 +5,22 @@ use salsa::Database;
|
|||
#[salsa::query_group]
|
||||
pub(crate) trait ConstantsDatabase: TestContext {
|
||||
#[salsa::input]
|
||||
fn constants_input(&self, key: char) -> usize;
|
||||
fn input(&self, key: char) -> usize;
|
||||
|
||||
fn constants_add(&self, keys: (char, char)) -> usize;
|
||||
fn add(&self, keys: (char, char)) -> usize;
|
||||
}
|
||||
|
||||
fn constants_add(db: &impl ConstantsDatabase, (key1, key2): (char, char)) -> usize {
|
||||
fn add(db: &impl ConstantsDatabase, (key1, key2): (char, char)) -> usize {
|
||||
db.log().add(format!("add({}, {})", key1, key2));
|
||||
db.constants_input(key1) + db.constants_input(key2)
|
||||
db.input(key1) + db.input(key2)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn invalidate_constant() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
db.query_mut(ConstantsInputQuery).set_constant('a', 44);
|
||||
db.query_mut(ConstantsInputQuery).set_constant('a', 66);
|
||||
db.set_constant_input('a', 44);
|
||||
db.set_constant_input('a', 66);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -29,13 +29,13 @@ fn invalidate_constant_1() {
|
|||
let db = &mut TestContextImpl::default();
|
||||
|
||||
// Not constant:
|
||||
db.query_mut(ConstantsInputQuery).set('a', 44);
|
||||
db.set_input('a', 44);
|
||||
|
||||
// Becomes constant:
|
||||
db.query_mut(ConstantsInputQuery).set_constant('a', 44);
|
||||
db.set_constant_input('a', 44);
|
||||
|
||||
// Invalidates:
|
||||
db.query_mut(ConstantsInputQuery).set_constant('a', 66);
|
||||
db.set_constant_input('a', 66);
|
||||
}
|
||||
|
||||
/// Test that invoking `set` on a constant is an error, even if you
|
||||
|
@ -44,54 +44,54 @@ fn invalidate_constant_1() {
|
|||
#[should_panic]
|
||||
fn set_after_constant_same_value() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
db.query_mut(ConstantsInputQuery).set_constant('a', 44);
|
||||
db.query_mut(ConstantsInputQuery).set('a', 44);
|
||||
db.set_constant_input('a', 44);
|
||||
db.set_input('a', 44);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_constant() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
|
||||
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(ConstantsAddQuery).is_constant(('a', 'b')));
|
||||
db.set_input('a', 22);
|
||||
db.set_input('b', 44);
|
||||
assert_eq!(db.add(('a', 'b')), 66);
|
||||
assert!(!db.query(AddQuery).is_constant(('a', 'b')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_constant() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
|
||||
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(ConstantsAddQuery).is_constant(('a', 'b')));
|
||||
db.set_constant_input('a', 22);
|
||||
db.set_constant_input('b', 44);
|
||||
assert_eq!(db.add(('a', 'b')), 66);
|
||||
assert!(db.query(AddQuery).is_constant(('a', 'b')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_constant() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
|
||||
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(ConstantsAddQuery).is_constant(('a', 'b')));
|
||||
db.set_constant_input('a', 22);
|
||||
db.set_input('b', 44);
|
||||
assert_eq!(db.add(('a', 'b')), 66);
|
||||
assert!(!db.query(AddQuery).is_constant(('a', 'b')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn becomes_constant_with_change() {
|
||||
let db = &mut TestContextImpl::default();
|
||||
|
||||
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(ConstantsAddQuery).is_constant(('a', 'b')));
|
||||
db.set_input('a', 22);
|
||||
db.set_input('b', 44);
|
||||
assert_eq!(db.add(('a', 'b')), 66);
|
||||
assert!(!db.query(AddQuery).is_constant(('a', 'b')));
|
||||
|
||||
db.query_mut(ConstantsInputQuery).set_constant('a', 23);
|
||||
assert_eq!(db.constants_add(('a', 'b')), 67);
|
||||
assert!(!db.query(ConstantsAddQuery).is_constant(('a', 'b')));
|
||||
db.set_constant_input('a', 23);
|
||||
assert_eq!(db.add(('a', 'b')), 67);
|
||||
assert!(!db.query(AddQuery).is_constant(('a', 'b')));
|
||||
|
||||
db.query_mut(ConstantsInputQuery).set_constant('b', 45);
|
||||
assert_eq!(db.constants_add(('a', 'b')), 68);
|
||||
assert!(db.query(ConstantsAddQuery).is_constant(('a', 'b')));
|
||||
db.set_constant_input('b', 45);
|
||||
assert_eq!(db.add(('a', 'b')), 68);
|
||||
assert!(db.query(AddQuery).is_constant(('a', 'b')));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue