This commit is contained in:
Axel Karjalainen 2025-12-22 12:22:14 +01:00 committed by GitHub
commit 626686ab80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 179 additions and 18 deletions

View file

@ -39,7 +39,7 @@ The database is also used to implement interning (making a canonical version of
## Inputs
Every Salsa program begins with an **input**.
Every Salsa program begins with an **input**. See the [`#[input]` attribute macro's documentation](https://docs.rs/salsa/latest/salsa/attr.input.html) for options.
Inputs are special structs that define the starting point of your program.
Everything else in your program is ultimately a deterministic function of these inputs.
@ -123,7 +123,7 @@ This gives the ability to set the [durability](./reference/durability.md) and ot
## Tracked functions
Once you've defined your inputs, the next thing to define are **tracked functions**:
Once you've defined your inputs, the next thing to define are **tracked functions**. See the [`#[tracked]` attribute macro's documentation](https://docs.rs/salsa/latest/salsa/attr.tracked.html) for options.
```rust
#[salsa::tracked]
@ -149,7 +149,7 @@ Tracked functions can return any clone-able type. A clone is required since, whe
## Tracked structs
**Tracked structs** are intermediate structs created during your computation.
**Tracked structs** are intermediate structs created during your computation. See the [`#[tracked]` attribute macro's documentation](https://docs.rs/salsa/latest/salsa/attr.tracked.html) for options.
Like inputs, their fields are stored inside the database, and the struct itself just wraps an id.
Unlike inputs, they can only be created inside a tracked function, and their fields can never change once they are created (until the next revision, at least).
Getter methods are provided to read the fields, but there are no setter methods.
@ -180,6 +180,8 @@ fn parse_file(db: &dyn crate::Db, file: ProgramFile) -> Ast {
### `#[id]` fields
<!-- FIXME: #[id] fields only currently exist on interned structs -->
When a tracked function is re-executed because its inputs have changed, the tracked structs it creates in the new execution are matched against those from the old execution, and the values of their fields are compared.
If the field values have not changed, then other tracked functions that only read those fields will not be re-executed.
@ -246,6 +248,7 @@ Specifying is only possible for tracked functions that take a single tracked str
The final kind of Salsa struct are **interned structs**.
Interned structs are useful for quick equality comparison.
They are commonly used to represent strings or other primitive values.
See the [`#[interned]` attribute macro's documentation](https://docs.rs/salsa/latest/salsa/attr.interned.html) for options.
Most compilers, for example, will define a type to represent a user identifier:
@ -273,7 +276,7 @@ You can access the fields of an interned struct using a getter, like `word.text(
## Accumulators
The final Salsa concept are **accumulators**. Accumulators are a way to report errors or other "side channel" information that is separate from the main return value of your function.
The final Salsa concept are **accumulators**. Accumulators are a way to report errors or other "side channel" information that is separate from the main return value of your function. See the [`#[accumulator]` attribute macro's documentation](https://docs.rs/salsa/latest/salsa/attr.accumulator.html) for options.
To create an accumulator, you declare a type as an _accumulator_:

View file

@ -17,6 +17,10 @@ quote = "1.0"
syn = { version = "2.0.104", features = ["full", "visit-mut"] }
synstructure = "0.13.2"
[dev-dependencies]
# For doc-tests
salsa.path = "../../"
[features]
default = []
persistence = []

View file

@ -4,13 +4,6 @@ use syn::parse::Nothing;
use crate::hygiene::Hygiene;
use crate::token_stream_with_error;
// Source:
//
// #[salsa::db]
// pub struct Database {
// storage: salsa::Storage<Self>,
// }
pub(crate) fn db(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,

View file

@ -55,11 +55,78 @@ pub fn accumulator(args: TokenStream, input: TokenStream) -> TokenStream {
accumulator::accumulator(args, input)
}
/// Implements a custom database trait.
///
/// Apply this on a custom database trait's definition and the [`struct`] and [`impl`] items of
/// implementors.
///
/// When applied to [`struct`] items, this macro implements the necessary supertraits required for `salsa::Database`.
///
/// When applied to [`trait`] and [`impl`] items, this macro adds some hidden trait methods required for [`#[tracked]`](fn@tracked) functions.
///
/// # Example
///
/// ```
/// use std::path::PathBuf;
///
/// #[salsa::input]
/// struct File {
// Doesn't work without the std::path:: prefix...
/// path: std::path::PathBuf,
/// #[returns(ref)]
/// contents: String,
/// }
///
/// #[salsa::db]
/// trait Db: salsa::Database {
/// fn input(&self, path: PathBuf) -> std::io::Result<File>;
/// }
///
/// #[salsa::db]
/// #[derive(Clone)]
/// pub struct MyDatabase {
/// storage: salsa::Storage<Self>,
/// }
///
/// #[salsa::db]
/// impl salsa::Database for MyDatabase {}
///
/// #[salsa::db]
/// impl Db for MyDatabase {
/// fn input(&self, path: PathBuf) -> std::io::Result<File> {
/// todo!()
/// }
/// }
/// ```
///
/// [`struct`]: https://doc.rust-lang.org/std/keyword.struct.html
/// [`impl`]: https://doc.rust-lang.org/std/keyword.impl.html
/// [`trait`]: https://doc.rust-lang.org/std/keyword.trait.html
#[proc_macro_attribute]
pub fn db(args: TokenStream, input: TokenStream) -> TokenStream {
db::db(args, input)
}
/// Creates interned structs.
///
/// **Container attributes:**
///
/// - `debug`: Generate a [`Debug`](std::fmt::Debug) implementation for the struct.
/// - `singleton`: Marks the struct as a singleton. There is a maximum of one instance of a singleton struct in a Salsa database. Singletons additionally have `get` and `try_get` methods, and their `new` method sets the singleton.
/// - TODO
///
/// **Field attributes:**
///
/// - TODO
///
/// # Example
///
/// ```
/// #[salsa::interned]
/// struct MyInterned<'db> {
/// field: String,
/// }
/// ```
#[proc_macro_attribute]
pub fn interned(args: TokenStream, input: TokenStream) -> TokenStream {
interned::interned(args, input)
@ -70,11 +137,112 @@ pub fn supertype(input: TokenStream) -> TokenStream {
supertype::supertype(input)
}
/// Creates input structs.
///
/// **Container attributes:**
///
/// - `debug`: Generate a [`Debug`](std::fmt::Debug) implementation for the struct.
/// - `singleton`: Marks the struct as a singleton. There is a maximum of one instance of a singleton struct in a Salsa database. Singletons additionally have `get` and `try_get` methods, and their `new` method sets the singleton.
/// - TODO
///
/// **Field attributes:**
///
/// - `default`: Marks the field as tracked.
/// - `returns(copy | clone | ref | deref | as_ref | as_deref)`: Configure the "return mode" (default: `clone`)
/// - `no_eq`: Signal that the output type does not implement the `Eq` trait (incompatible with `cycle_fn`)
/// - `get`: Name of the getter function (default: field name)
/// - `set`: Name of the setter function (default: `set_` + field name)
///
/// # Example
///
/// ```
/// use std::path::PathBuf;
///
/// #[salsa::input]
/// struct File {
// Doesn't work without the std::path:: prefix...
/// path: std::path::PathBuf,
/// #[returns(ref)]
/// contents: String,
/// }
/// ```
#[proc_macro_attribute]
pub fn input(args: TokenStream, input: TokenStream) -> TokenStream {
input::input(args, input)
}
/// Creates tracked structs, functions and [`impl`]s.
///
/// # Tracked structs
///
/// **Tracked structs** are usually used as parameters to tracked functions. They can only be created inside tracked functions.
///
/// **Container attributes:**
///
/// - `debug`: Generate a [`Debug`](std::fmt::Debug) implementation for the struct.
/// - `singleton`: Marks the struct as a singleton. There is a maximum of one instance of a singleton struct in a Salsa database. Singletons additionally have `get` and `try_get` methods, and their `new` method sets the singleton.
/// - `data`: TODO
/// - `constructor_name`: TODO
/// - `heap_size = <path>`: Function to calculate the heap memory usage of memoized values (type: `fn(&Fields) -> usize`, default: none)
/// - `persist(serialize = <path>, deserialize = <path>)` (Only with <span class="stab portability"><code>persistence</code></span> feature)
/// * Type of `serialize`: `fn(&Fields<'_>, S) -> Result<S::Ok, S::Error> where S: serde::Serializer`
/// * Type of `deserialize`: `fn(D) -> Result<Fields<'static>, D::Error> where D: serde::Deserializer<'de>`
///
/// **Field attributes:**
///
/// - `tracked`: Marks the field as tracked. TODO: what does this actually mean? Fields without this attribute must implement [`Hash`](std::hash::Hash).
/// - `returns(copy | clone | ref | deref | as_ref | as_deref)`: Configure the "return mode" (default: `clone`)
/// - `no_eq`: Signal that the output type does not implement the `Eq` trait (incompatible with `cycle_fn`)
/// - `get`: Name of the getter function (default: field name)
/// - `maybe_update`: TODO
///
/// # Tracked functions
///
/// When you call a **tracked function**, Salsa will track which inputs it accesses and memoize the return value based on it. This data is saved in the database. When it's called again, the inputs are compared. If they're identical, the first call's return value is returned.
///
/// Tracked functions always take the database as the first argument and can take [`#[input]`](fn@input), [`#[tracked]`](fn@tracked), [`#[interned]`](fn@interned) and [`#[accumulator]`](fn@accumulator) structs for the rest of the arguments.
///
/// **Attributes:**
///
/// - `returns(copy | clone | ref | deref | as_ref | as_deref)`: Configure the "return mode" (default: `clone`)
/// - `specify`: Signal that the value can be externally specified (only works with a single Salsa struct as the input. incompatible with `lru`)
/// - `no_eq`: Signal that the output type does not implement the `Eq` trait (incompatible with `cycle_fn`)
// Explicitly not documented: - `unsafe(non_update_return_type)`
/// - `cycle_fn = <path>`: TODO
/// - `cycle_initial = <path>`: TODO
/// - `cycle_result = <path>`: TODO
/// - `lru = <usize>`: Set the LRU capacity (default: 0)
/// - `heap_size = <path>`: Function to calculate the heap memory usage of memoized values (type: `fn(&Output) -> usize`, default: none)
/// - `self_ty = <Ty>`: Set the self type of the tracked impl, merely to refine the query name
/// - `persist` (Only with <span class="stab portability"><code>persistence</code></span> feature)
///
/// # Tracked [`impl`]s
///
/// TODO
///
/// # Example
///
/// ```
/// #[salsa::tracked]
/// struct MyTracked<'db> {
/// value: u32,
/// #[returns(ref)]
/// links: Vec<MyTracked<'db>>,
/// }
///
/// #[salsa::tracked]
/// fn sum<'db>(db: &'db dyn salsa::Database, input: MyTracked<'db>) -> u32 {
/// input.value(db)
/// + input
/// .links(db)
/// .iter()
/// .map(|&file| sum(db, file))
/// .sum::<u32>()
/// }
///
/// ```
///
/// [`impl`]: https://doc.rust-lang.org/std/keyword.impl.html
#[proc_macro_attribute]
pub fn tracked(args: TokenStream, input: TokenStream) -> TokenStream {
tracked::tracked(args, input)

View file

@ -7,13 +7,6 @@ use crate::hygiene::Hygiene;
use crate::options::{AllowedOptions, AllowedPersistOptions, Options};
use crate::{db_lifetime, fn_util};
// Source:
//
// #[salsa::db]
// pub struct Database {
// storage: salsa::Storage<Self>,
// }
pub(crate) fn tracked_fn(args: proc_macro::TokenStream, item: ItemFn) -> syn::Result<TokenStream> {
let hygiene = Hygiene::from2(&item);
let args: FnArgs = syn::parse(args)?;