From a46829298439d44047761adb46c9285bef2a7da3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 25 Jan 2019 05:16:57 -0500 Subject: [PATCH] generate `set_X` and `set_constant_X` methods for each input Convert some of the tests to use them --- README.md | 3 +- components/salsa-macros/src/query_group.rs | 38 ++++ examples/hello_world/README.md | 194 --------------------- examples/hello_world/main.rs | 28 ++- tests/gc/derived_tests.rs | 8 +- tests/incremental/constants.rs | 66 +++---- 6 files changed, 89 insertions(+), 248 deletions(-) delete mode 100644 examples/hello_world/README.md diff --git a/README.md b/README.md index 0032b333..e8c66c75 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/components/salsa-macros/src/query_group.rs b/components/salsa-macros/src/query_group.rs index b0e88386..b1471516 100644 --- a/components/salsa-macros/src/query_group.rs +++ b/components/salsa-macros/src/query_group.rs @@ -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) { + >::get_query_table_mut(self).set((#(#key_names),*), value__) + } + + fn #set_constant_fn_name(&mut self, #(#key_names: #keys,)* value__: #value) { + >::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),*)), diff --git a/examples/hello_world/README.md b/examples/hello_world/README.md deleted file mode 100644 index e7f803cc..00000000 --- a/examples/hello_world/README.md +++ /dev/null @@ -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; - - 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, -} - -impl salsa::Database for DatabaseStruct { - fn salsa_runtime(&self) -> &salsa::Runtime { - &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. - diff --git a/examples/hello_world/main.rs b/examples/hello_world/main.rs index 37ea7b76..f288ced9 100644 --- a/examples/hello_world/main.rs +++ b/examples/hello_world/main.rs @@ -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`. 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`. 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; // 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(())); } diff --git a/tests/gc/derived_tests.rs b/tests/gc/derived_tests.rs index f58a345c..87f19162 100644 --- a/tests/gc/derived_tests.rs +++ b/tests/gc/derived_tests.rs @@ -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); diff --git a/tests/incremental/constants.rs b/tests/incremental/constants.rs index 6c2c0aaf..443b125f 100644 --- a/tests/incremental/constants.rs +++ b/tests/incremental/constants.rs @@ -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'))); }