├── .devcontainer └── devcontainer.json ├── .dir-locals.el ├── .github ├── dependabot.yml └── workflows │ ├── book.yml │ └── test.yml ├── .gitignore ├── Cargo.toml ├── FAQ.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASES.md ├── benches ├── compare.rs └── incremental.rs ├── book ├── .gitignore ├── book.toml ├── mermaid-init.js ├── mermaid.css ├── mermaid.min.js ├── netlify.sh └── src │ ├── SUMMARY.md │ ├── about_salsa.md │ ├── common_patterns.md │ ├── common_patterns │ └── on_demand_inputs.md │ ├── cycles.md │ ├── cycles │ └── fallback.md │ ├── derived-query-maybe-changed-after.drawio.svg │ ├── derived-query-read.drawio.svg │ ├── how_salsa_works.md │ ├── meta.md │ ├── overview.md │ ├── plumbing.md │ ├── plumbing │ ├── cycles.md │ ├── database.md │ ├── database_and_runtime.md │ ├── db_lifetime.md │ ├── derived_flowchart.md │ ├── diagram.md │ ├── fetch.md │ ├── generated_code.md │ ├── jars_and_ingredients.md │ ├── maybe_changed_after.md │ ├── query_groups.md │ ├── query_ops.md │ ├── salsa_crate.md │ ├── terminology.md │ ├── terminology │ │ ├── LRU.md │ │ ├── backdate.md │ │ ├── changed_at.md │ │ ├── dependency.md │ │ ├── derived_query.md │ │ ├── durability.md │ │ ├── ingredient.md │ │ ├── input_query.md │ │ ├── memo.md │ │ ├── query.md │ │ ├── query_function.md │ │ ├── revision.md │ │ ├── salsa_item.md │ │ ├── salsa_struct.md │ │ ├── untracked.md │ │ └── verified.md │ └── tracked_structs.md │ ├── reference.md │ ├── reference │ ├── algorithm.md │ └── durability.md │ ├── tuning.md │ ├── tutorial.md │ ├── tutorial │ ├── accumulators.md │ ├── checker.md │ ├── db.md │ ├── debug.md │ ├── interpreter.md │ ├── ir.md │ ├── jar.md │ ├── parser.md │ └── structure.md │ └── videos.md ├── components ├── salsa-macro-rules │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── macro_if.rs │ │ ├── maybe_backdate.rs │ │ ├── maybe_clone.rs │ │ ├── maybe_default.rs │ │ ├── setup_accumulator_impl.rs │ │ ├── setup_input_struct.rs │ │ ├── setup_interned_struct.rs │ │ ├── setup_method_body.rs │ │ ├── setup_tracked_fn.rs │ │ ├── setup_tracked_struct.rs │ │ └── unexpected_cycle_recovery.rs └── salsa-macros │ ├── Cargo.toml │ └── src │ ├── accumulator.rs │ ├── db.rs │ ├── db_lifetime.rs │ ├── debug.rs │ ├── fn_util.rs │ ├── hygiene.rs │ ├── input.rs │ ├── interned.rs │ ├── lib.rs │ ├── options.rs │ ├── salsa_struct.rs │ ├── tracked.rs │ ├── tracked_fn.rs │ ├── tracked_impl.rs │ ├── tracked_struct.rs │ ├── update.rs │ └── xform.rs ├── examples ├── calc │ ├── compile.rs │ ├── db.rs │ ├── ir.rs │ ├── main.rs │ ├── parser.rs │ └── type_check.rs └── lazy-input │ ├── inputs │ ├── a │ ├── aa │ ├── b │ └── start │ └── main.rs ├── justfile ├── src ├── accumulator.rs ├── active_query.rs ├── array.rs ├── attach.rs ├── cancelled.rs ├── cycle.rs ├── database.rs ├── database_impl.rs ├── durability.rs ├── event.rs ├── function.rs ├── function │ ├── accumulated.rs │ ├── backdate.rs │ ├── delete.rs │ ├── diff_outputs.rs │ ├── execute.rs │ ├── fetch.rs │ ├── inputs.rs │ ├── lru.rs │ ├── maybe_changed_after.rs │ ├── memo.rs │ └── specify.rs ├── hash.rs ├── id.rs ├── ingredient.rs ├── input.rs ├── input │ ├── input_field.rs │ └── setter.rs ├── interned.rs ├── key.rs ├── lib.rs ├── nonce.rs ├── revision.rs ├── runtime.rs ├── runtime │ └── dependency_graph.rs ├── salsa_struct.rs ├── storage.rs ├── table.rs ├── table │ ├── memo.rs │ ├── sync.rs │ └── util.rs ├── tracked_struct.rs ├── tracked_struct │ └── tracked_field.rs ├── update.rs ├── views.rs ├── zalsa.rs └── zalsa_local.rs └── tests ├── accumulate-chain.rs ├── accumulate-custom-clone.rs ├── accumulate-custom-debug.rs ├── accumulate-dag.rs ├── accumulate-execution-order.rs ├── accumulate-from-tracked-fn.rs ├── accumulate-no-duplicates.rs ├── accumulate-reuse-workaround.rs ├── accumulate-reuse.rs ├── accumulate.rs ├── common └── mod.rs ├── compile-fail ├── accumulator_incompatibles.rs ├── accumulator_incompatibles.stderr ├── get-on-private-interned-field.rs ├── get-on-private-interned-field.stderr ├── get-on-private-tracked-field.rs ├── get-on-private-tracked-field.stderr ├── get-set-on-private-input-field.rs ├── get-set-on-private-input-field.stderr ├── input_struct_incompatibles.rs ├── input_struct_incompatibles.stderr ├── interned_struct_incompatibles.rs ├── interned_struct_incompatibles.stderr ├── lru_can_not_be_used_with_specify.rs ├── lru_can_not_be_used_with_specify.stderr ├── panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs ├── panic-when-reading-fields-of-tracked-structs-from-older-revisions.stderr ├── salsa_fields_incompatibles.rs ├── salsa_fields_incompatibles.stderr ├── singleton_only_for_input.rs ├── singleton_only_for_input.stderr ├── span-input-setter.rs ├── span-input-setter.stderr ├── span-tracked-getter.rs ├── span-tracked-getter.stderr ├── specify-does-not-work-if-the-key-is-a-salsa-input.rs ├── specify-does-not-work-if-the-key-is-a-salsa-input.stderr ├── specify-does-not-work-if-the-key-is-a-salsa-interned.rs ├── specify-does-not-work-if-the-key-is-a-salsa-interned.stderr ├── tracked_fn_incompatibles.rs ├── tracked_fn_incompatibles.stderr ├── tracked_impl_incompatibles.rs ├── tracked_impl_incompatibles.stderr ├── tracked_method_incompatibles.rs ├── tracked_method_incompatibles.stderr ├── tracked_method_on_untracked_impl.rs ├── tracked_method_on_untracked_impl.stderr ├── tracked_struct_incompatibles.rs └── tracked_struct_incompatibles.stderr ├── compile_fail.rs ├── cycles.rs ├── debug.rs ├── deletion-cascade.rs ├── deletion-drops.rs ├── deletion.rs ├── elided-lifetime-in-tracked-fn.rs ├── expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs ├── expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs ├── hello_world.rs ├── input_default.rs ├── input_field_durability.rs ├── input_setter_preserves_durability.rs ├── interned-struct-with-lifetime.rs ├── is_send_sync.rs ├── lru.rs ├── mutate_in_place.rs ├── override_new_get_set.rs ├── panic-when-creating-tracked-struct-outside-of-tracked-fn.rs ├── parallel ├── main.rs ├── parallel_cancellation.rs ├── parallel_cycle_all_recover.rs ├── parallel_cycle_mid_recover.rs ├── parallel_cycle_none_recover.rs ├── parallel_cycle_one_recover.rs ├── setup.rs └── signal.rs ├── preverify-struct-with-leaked-data-2.rs ├── preverify-struct-with-leaked-data.rs ├── singleton.rs ├── specify-only-works-if-the-key-is-created-in-the-current-query.rs ├── synthetic_write.rs ├── tracked-struct-id-field-bad-eq.rs ├── tracked-struct-id-field-bad-hash.rs ├── tracked-struct-unchanged-in-new-rev.rs ├── tracked-struct-value-field-bad-eq.rs ├── tracked-struct-value-field-not-eq.rs ├── tracked_fn_constant.rs ├── tracked_fn_high_durability_dependency.rs ├── tracked_fn_no_eq.rs ├── tracked_fn_on_input.rs ├── tracked_fn_on_input_with_high_durability.rs ├── tracked_fn_on_interned.rs ├── tracked_fn_on_tracked.rs ├── tracked_fn_on_tracked_specify.rs ├── tracked_fn_read_own_entity.rs ├── tracked_fn_read_own_specify.rs ├── tracked_fn_return_ref.rs ├── tracked_method.rs ├── tracked_method_inherent_return_ref.rs ├── tracked_method_on_tracked_struct.rs ├── tracked_method_trait_return_ref.rs ├── tracked_struct_db1_lt.rs ├── tracked_struct_durability.rs ├── tracked_with_intern.rs ├── tracked_with_struct_db.rs └── warnings ├── main.rs ├── needless_borrow.rs ├── needless_lifetimes.rs └── unused_variable_db.rs /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/rust 3 | { 4 | "name": "Rust", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye", 7 | "features": { 8 | "ghcr.io/devcontainers-contrib/features/ripgrep:1": {} 9 | } 10 | 11 | // Use 'mounts' to make the cargo cache persistent in a Docker Volume. 12 | // "mounts": [ 13 | // { 14 | // "source": "devcontainer-cargo-cache-${devcontainerId}", 15 | // "target": "/usr/local/cargo", 16 | // "type": "volume" 17 | // } 18 | // ] 19 | 20 | // Features to add to the dev container. More info: https://containers.dev/features. 21 | // "features": {}, 22 | 23 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 24 | // "forwardPorts": [], 25 | 26 | // Use 'postCreateCommand' to run commands after the container is created. 27 | // "postCreateCommand": "rustc --version", 28 | 29 | // Configure tool-specific properties. 30 | // "customizations": {}, 31 | 32 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 33 | // "remoteUser": "root" 34 | } 35 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((rust-mode (rust-format-on-save . t))) 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/book.yml: -------------------------------------------------------------------------------- 1 | name: Book 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | paths: 9 | - "book/**" 10 | - ".github/workflows/book.yml" 11 | merge_group: 12 | 13 | jobs: 14 | book: 15 | name: Book 16 | runs-on: ubuntu-latest 17 | env: 18 | MDBOOK_VERSION: "0.4.40" 19 | MDBOOK_LINKCHECK_VERSION: "0.7.7" 20 | MDBOOK_MERMAID_VERSION: "0.13.0" 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Install mdbook 24 | run: | 25 | curl -L https://github.com/rust-lang/mdBook/releases/download/v$MDBOOK_VERSION/mdbook-v$MDBOOK_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.cargo/bin 26 | curl -L https://github.com/badboy/mdbook-mermaid/releases/download/v$MDBOOK_MERMAID_VERSION/mdbook-mermaid-v$MDBOOK_MERMAID_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.cargo/bin 27 | curl -L https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/download/v$MDBOOK_LINKCHECK_VERSION/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -O 28 | unzip mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -d ~/.cargo/bin 29 | chmod +x ~/.cargo/bin/mdbook-linkcheck 30 | - name: Build 31 | run: mdbook build 32 | working-directory: book 33 | - uses: actions/upload-artifact@v4 34 | with: 35 | name: book 36 | path: book/book/html 37 | 38 | deploy: 39 | name: Deploy 40 | runs-on: ubuntu-latest 41 | needs: book 42 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 43 | concurrency: 44 | group: github-pages 45 | cancel-in-progress: true 46 | permissions: 47 | contents: read 48 | pages: write 49 | id-token: write 50 | steps: 51 | - uses: actions/download-artifact@v4 52 | with: 53 | name: book 54 | - uses: actions/configure-pages@v5 55 | - uses: actions/upload-pages-artifact@v3 56 | with: 57 | path: . 58 | - uses: actions/deploy-pages@v4 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | TAGS 5 | nikom 6 | .idea 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "salsa" 3 | version = "0.18.0" 4 | authors = ["Salsa developers"] 5 | edition = "2021" 6 | license = "Apache-2.0 OR MIT" 7 | repository = "https://github.com/salsa-rs/salsa" 8 | description = "A generic framework for on-demand, incrementalized computation (experimental)" 9 | 10 | [dependencies] 11 | arc-swap = "1" 12 | crossbeam = "0.8" 13 | dashmap = "6" 14 | hashlink = "0.9" 15 | indexmap = "2" 16 | append-only-vec = "0.1.5" 17 | tracing = "0.1" 18 | parking_lot = "0.12" 19 | rustc-hash = "2" 20 | salsa-macro-rules = { version = "0.1.0", path = "components/salsa-macro-rules" } 21 | salsa-macros = { path = "components/salsa-macros" } 22 | smallvec = "1" 23 | lazy_static = "1" 24 | 25 | [dev-dependencies] 26 | annotate-snippets = "0.11.4" 27 | derive-new = "0.6.0" 28 | codspeed-criterion-compat = { version = "2.6.0", default-features = false } 29 | expect-test = "1.4.0" 30 | eyre = "0.6.8" 31 | notify-debouncer-mini = "0.4.1" 32 | ordered-float = "4.2.1" 33 | rustversion = "1.0" 34 | test-log = { version ="0.2.11", features = ["trace"] } 35 | trybuild = "1.0" 36 | 37 | [[bench]] 38 | name = "compare" 39 | harness = false 40 | 41 | 42 | [[bench]] 43 | name = "incremental" 44 | harness = false 45 | 46 | [workspace] 47 | members = ["components/salsa-macro-rules", "components/salsa-macros"] 48 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently asked questions 2 | 3 | ## Why is it called salsa? 4 | 5 | I like salsa! Don't you?! Well, ok, there's a bit more to it. The 6 | underlying algorithm for figuring out which bits of code need to be 7 | re-executed after any given change is based on the algorithm used in 8 | rustc. Michael Woerister and I first described the rustc algorithm in 9 | terms of two colors, red and green, and hence we called it the 10 | "red-green algorithm". This made me think of the New Mexico State 11 | Question --- ["Red or green?"][nm] --- which refers to chile 12 | (salsa). Although this version no longer uses colors (we borrowed 13 | revision counters from Glimmer, instead), I still like the name. 14 | 15 | [nm]: https://www.sos.state.nm.us/about-new-mexico/state-question/ 16 | 17 | ## What is the relationship between salsa and an Entity-Component System (ECS)? 18 | 19 | You may have noticed that Salsa "feels" a lot like an ECS in some 20 | ways. That's true -- Salsa's queries are a bit like *components* (and 21 | the keys to the queries are a bit like *entities*). But there is one 22 | big difference: **ECS is -- at its heart -- a mutable system**. You 23 | can get or set a component of some entity whenever you like. In 24 | contrast, salsa's queries **define "derived values" via pure 25 | computations**. 26 | 27 | Partly as a consequence, ECS doesn't handle incremental updates for 28 | you. When you update some component of some entity, you have to ensure 29 | that other entities' components are updated appropriately. 30 | 31 | Finally, ECS offers interesting metadata and "aspect-like" facilities, 32 | such as iterating over all entities that share certain components. 33 | Salsa has no analogue to that. 34 | 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # salsa 2 | 3 | [![Test](https://github.com/salsa-rs/salsa/workflows/Test/badge.svg)](https://github.com/salsa-rs/salsa/actions?query=workflow%3ATest) 4 | [![Book](https://github.com/salsa-rs/salsa/workflows/Book/badge.svg)](https://github.com/salsa-rs/salsa/actions?query=workflow%3ABook) 5 | [![Released API docs](https://docs.rs/salsa/badge.svg)](https://docs.rs/salsa) 6 | [![Crates.io](https://img.shields.io/crates/v/salsa.svg)](https://crates.io/crates/salsa) 7 | 8 | *A generic framework for on-demand, incrementalized computation.* 9 | 10 | Salsa Logo 11 | 12 | ## Obligatory warning 13 | 14 | Very much a WORK IN PROGRESS at this point. Ready for experimental use 15 | but expect frequent breaking changes. 16 | 17 | ## Credits 18 | 19 | This system is heavily inspired by [adapton](http://adapton.org/), [glimmer](https://github.com/glimmerjs/glimmer-vm), and rustc's query 20 | system. So credit goes to Eduard-Mihai Burtescu, Matthew Hammer, 21 | Yehuda Katz, and Michael Woerister. 22 | 23 | ## Key idea 24 | 25 | The key idea of `salsa` is that you define your program as a set of 26 | **queries**. Every query is used like function `K -> V` that maps from 27 | some key of type `K` to a value of type `V`. Queries come in two basic 28 | varieties: 29 | 30 | - **Inputs**: the base inputs to your system. You can change these 31 | whenever you like. 32 | - **Functions**: pure functions (no side effects) that transform your 33 | inputs into other values. The results of queries are memoized to 34 | avoid recomputing them a lot. When you make changes to the inputs, 35 | we'll figure out (fairly intelligently) when we can re-use these 36 | memoized values and when we have to recompute them. 37 | 38 | ## Want to learn more? 39 | 40 | To learn more about Salsa, try one of the following: 41 | 42 | - read the [heavily commented `hello_world` example](https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs); 43 | - check out the [Salsa book](https://salsa-rs.github.io/salsa); 44 | - [中文版](https://rust-chinese-translation.github.io/salsa-book) 45 | - watch one of our [videos](https://salsa-rs.github.io/salsa/videos.html). 46 | 47 | ## Getting in touch 48 | 49 | The bulk of the discussion happens in the [issues](https://github.com/salsa-rs/salsa/issues) 50 | and [pull requests](https://github.com/salsa-rs/salsa/pulls), 51 | but we have a [zulip chat](https://salsa.zulipchat.com/) as well. 52 | 53 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # 0.13.0 2 | 3 | - **Breaking change:** adopt the new `Durability` API proposed in [RFC #6] 4 | - this replaces and generalizes the existing concepts of constants 5 | - **Breaking change:** remove "volatile" queries 6 | - instead, create a normal query which invokes the 7 | `report_untracked_read` method on the salsa runtime 8 | - introduce "slots", an optimization to salsa's internal workings 9 | - document `#[salsa::requires]` attribute, which permits private dependencies 10 | - Adopt `AtomicU64` for `runtimeId` (#182) 11 | - use `ptr::eq` and `ptr::hash` for readability 12 | - upgrade parking lot, rand dependencies 13 | 14 | [RFC #6]: https://github.com/salsa-rs/salsa-rfcs/pull/6 15 | -------------------------------------------------------------------------------- /benches/incremental.rs: -------------------------------------------------------------------------------- 1 | use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Criterion}; 2 | use salsa::Setter; 3 | 4 | #[salsa::input] 5 | struct Input { 6 | field: usize, 7 | } 8 | 9 | #[salsa::tracked] 10 | struct Tracked<'db> { 11 | number: usize, 12 | } 13 | 14 | #[salsa::tracked(return_ref)] 15 | fn index<'db>(db: &'db dyn salsa::Database, input: Input) -> Vec> { 16 | (0..input.field(db)).map(|i| Tracked::new(db, i)).collect() 17 | } 18 | 19 | #[salsa::tracked] 20 | fn root(db: &dyn salsa::Database, input: Input) -> usize { 21 | let index = index(db, input); 22 | index.len() 23 | } 24 | 25 | fn many_tracked_structs(criterion: &mut Criterion) { 26 | criterion.bench_function("many_tracked_structs", |b| { 27 | b.iter_batched_ref( 28 | || { 29 | let db = salsa::DatabaseImpl::new(); 30 | 31 | let input = Input::new(&db, 1_000); 32 | let input2 = Input::new(&db, 1); 33 | 34 | // prewarm cache 35 | let _ = root(&db, input); 36 | let _ = root(&db, input2); 37 | 38 | (db, input, input2) 39 | }, 40 | |(db, input, input2)| { 41 | // Make a change, but fetch the result for the other input 42 | input2.set_field(db).to(2); 43 | 44 | let result = root(db, *input); 45 | 46 | assert_eq!(result, 1_000); 47 | }, 48 | BatchSize::LargeInput, 49 | ); 50 | }); 51 | } 52 | 53 | criterion_group!(benches, many_tracked_structs); 54 | criterion_main!(benches); 55 | -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Salsa Contributors"] 3 | multilingual = false 4 | src = "src" 5 | title = "Salsa" 6 | 7 | [build] 8 | create-missing = false 9 | 10 | [preprocess.links] 11 | 12 | [output.html] 13 | additional-css =["mermaid.css"] 14 | additional-js =["mermaid.min.js", "mermaid-init.js"] 15 | 16 | [output.linkcheck] 17 | # follow-web-links = true --- this is commented out b/c of false errors 18 | traverse-parent-directories = false 19 | exclude = ['bilibili\.com'] 20 | [preprocessor] 21 | [preprocessor.mermaid] 22 | command = "mdbook-mermaid" 23 | -------------------------------------------------------------------------------- /book/mermaid-init.js: -------------------------------------------------------------------------------- 1 | mermaid.initialize({startOnLoad:true}); 2 | -------------------------------------------------------------------------------- /book/netlify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Script meant to be run from netlify 4 | 5 | set -x 6 | 7 | MDBOOK_VERSION='0.4.12' 8 | MDBOOK_LINKCHECK_VERSION='0.7.4' 9 | MDBOOK_MERMAID_VERSION='0.8.3' 10 | 11 | curl -L https://github.com/rust-lang/mdBook/releases/download/v$MDBOOK_VERSION/mdbook-v$MDBOOK_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.cargo/bin 12 | curl -L https://github.com/badboy/mdbook-mermaid/releases/download/v$MDBOOK_MERMAID_VERSION/mdbook-mermaid-v$MDBOOK_MERMAID_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.cargo/bin 13 | curl -L https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/download/v$MDBOOK_LINKCHECK_VERSION/mdbook-linkcheck.v$MDBOOK_LINKCHECK_VERSION.x86_64-unknown-linux-gnu.zip -O 14 | unzip mdbook-linkcheck.v$MDBOOK_LINKCHECK_VERSION.x86_64-unknown-linux-gnu.zip -d ~/.cargo/bin 15 | chmod +x ~/.cargo/bin/mdbook-linkcheck 16 | 17 | mdbook build 18 | mkdir versions 19 | mv book/html/* versions 20 | -------------------------------------------------------------------------------- /book/src/about_salsa.md: -------------------------------------------------------------------------------- 1 | # About salsa 2 | 3 | Salsa is a Rust framework for writing incremental, on-demand programs 4 | -- these are programs that want to adapt to changes in their inputs, 5 | continuously producing a new output that is up-to-date. Salsa is based 6 | on the the incremental recompilation techniques that we built for 7 | rustc, and many (but not all) of its users are building compilers or 8 | other similar tooling. 9 | 10 | If you'd like to learn more about Salsa, check out: 11 | 12 | - The [overview](./overview.md), for a brief summary. 13 | - The [tutorial](./tutorial.md), for a detailed look. 14 | - You can also watch some of our [videos](./videos.md), though the content there is rather out of date. 15 | 16 | If you'd like to chat about Salsa, or you think you might like to 17 | contribute, please jump on to our Zulip instance at 18 | [salsa.zulipchat.com](https://salsa.zulipchat.com/). 19 | -------------------------------------------------------------------------------- /book/src/common_patterns.md: -------------------------------------------------------------------------------- 1 | # Common patterns 2 | 3 | This section documents patterns for using Salsa. 4 | -------------------------------------------------------------------------------- /book/src/common_patterns/on_demand_inputs.md: -------------------------------------------------------------------------------- 1 | # On-Demand (Lazy) Inputs 2 | 3 | Salsa inputs work best if you can easily provide all of the inputs upfront. 4 | However sometimes the set of inputs is not known beforehand. 5 | 6 | A typical example is reading files from disk. 7 | While it is possible to eagerly scan a particular directory and create an in-memory file tree as salsa input structs, a more straight-forward approach is to read the files lazily. 8 | That is, when a query requests the text of a file for the first time: 9 | 10 | 1. Read the file from disk and cache it. 11 | 2. Setup a file-system watcher for this path. 12 | 3. Update the cached file when the watcher sends a change notification. 13 | 14 | This is possible to achieve in salsa, by caching the inputs in your database structs and adding a method to the database trait to retrieve them out of this cache. 15 | 16 | A complete, runnable file-watching example can be found in [the lazy-input example](https://github.com/salsa-rs/salsa/tree/master/examples/lazy-input). 17 | 18 | The setup looks roughly like this: 19 | 20 | ```rust,ignore 21 | {{#include ../../../examples/lazy-input/main.rs:db}} 22 | ``` 23 | 24 | - We declare a method on the `Db` trait that gives us a `File` input on-demand (it only requires a `&dyn Db` not a `&mut dyn Db`). 25 | - There should only be one input struct per file, so we implement that method using a cache (`DashMap` is like a `RwLock`). 26 | 27 | The driving code that's doing the top-level queries is then in charge of updating the file contents when a file-change notification arrives. 28 | It does this by updating the Salsa input in the same way that you would update any other input. 29 | 30 | Here we implement a simple driving loop, that recompiles the code whenever a file changes. 31 | You can use the logs to check that only the queries that could have changed are re-evaluated. 32 | 33 | ```rust,ignore 34 | {{#include ../../../examples/lazy-input/main.rs:main}} 35 | ``` 36 | -------------------------------------------------------------------------------- /book/src/cycles.md: -------------------------------------------------------------------------------- 1 | # Cycle handling 2 | 3 | By default, when Salsa detects a cycle in the computation graph, Salsa will panic with a [`salsa::Cycle`] as the panic value. The [`salsa::Cycle`] structure that describes the cycle, which can be useful for diagnosing what went wrong. 4 | 5 | [`salsa::cycle`]: https://github.com/salsa-rs/salsa/blob/0f9971ad94d5d137f1192fde2b02ccf1d2aca28c/src/lib.rs#L654-L672 6 | -------------------------------------------------------------------------------- /book/src/cycles/fallback.md: -------------------------------------------------------------------------------- 1 | # Recovering via fallback 2 | 3 | Panicking when a cycle occurs is ok for situations where you believe a cycle is impossible. But sometimes cycles can result from illegal user input and cannot be statically prevented. In these cases, you might prefer to gracefully recover from a cycle rather than panicking the entire query. Salsa supports that with the idea of *cycle recovery*. 4 | 5 | To use cycle recovery, you annotate potential participants in the cycle with the `recovery_fn` argument to `#[salsa::tracked]`, e.g. `#[salsa::tracked(recovery_fn=my_recovery_fn)]`. When a cycle occurs, if any participant P has recovery information, then no panic occurs. Instead, the execution of P is aborted and P will execute the recovery function to generate its result. Participants in the cycle that do not have recovery information continue executing as normal, using this recovery result. 6 | 7 | The recovery function has a similar signature to a query function. It is given a reference to your database along with a `salsa::Cycle` describing the cycle that occurred and the arguments to the tracked function that caused the cycle; it returns the result of the query. Example: 8 | 9 | ```rust 10 | fn my_recover_fn( 11 | db: &dyn MyDatabase, 12 | cycle: &salsa::Cycle, 13 | arg1: T1, 14 | ... 15 | argN: TN, 16 | ) -> MyResultValue 17 | ``` 18 | 19 | See [the tests](https://github.com/salsa-rs/salsa/blob/cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3/tests/cycles.rs#L132-L141) for an example. 20 | 21 | **Important:** Although the recovery function is given a `db` handle, you should be careful to avoid creating a cycle from within recovery or invoking queries that may be participating in the current cycle. Attempting to do so can result in inconsistent results. 22 | -------------------------------------------------------------------------------- /book/src/how_salsa_works.md: -------------------------------------------------------------------------------- 1 | # How Salsa works 2 | 3 | ## Video available 4 | 5 | To get the most complete introduction to Salsa's inner workings, check 6 | out [the "How Salsa Works" video](https://youtu.be/_muY4HjSqVw). If 7 | you'd like a deeper dive, [the "Salsa in more depth" 8 | video](https://www.youtube.com/watch?v=i_IhACacPRY) digs into the 9 | details of the incremental algorithm. 10 | 11 | > If you're in China, watch videos on ["How Salsa Works"](https://www.bilibili.com/video/BV1Df4y1A7t3/), ["Salsa In More Depth"](https://www.bilibili.com/video/BV1AM4y1G7E4/). 12 | 13 | ## Key idea 14 | 15 | The key idea of `salsa` is that you define your program as a set of 16 | **queries**. Every query is used like a function `K -> V` that maps from 17 | some key of type `K` to a value of type `V`. Queries come in two basic 18 | varieties: 19 | 20 | - **Inputs**: the base inputs to your system. You can change these 21 | whenever you like. 22 | - **Functions**: pure functions (no side effects) that transform your 23 | inputs into other values. The results of queries are memoized to 24 | avoid recomputing them a lot. When you make changes to the inputs, 25 | we'll figure out (fairly intelligently) when we can re-use these 26 | memoized values and when we have to recompute them. 27 | 28 | ## How to use Salsa in three easy steps 29 | 30 | Using Salsa is as easy as 1, 2, 3... 31 | 32 | 1. Define one or more **query groups** that contain the inputs 33 | and queries you will need. We'll start with one such group, but 34 | later on you can use more than one to break up your system into 35 | components (or spread your code across crates). 36 | 2. Define the **query functions** where appropriate. 37 | 3. Define the **database**, which contains the storage for all 38 | the inputs/queries you will be using. The query struct will contain 39 | the storage for all of the inputs/queries and may also contain 40 | anything else that your code needs (e.g., configuration data). 41 | 42 | To see an example of this in action, check out [the `hello_world` 43 | example][hello_world], which has a number of comments explaining how 44 | things work. 45 | 46 | [hello_world]: https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs 47 | 48 | ## Digging into the plumbing 49 | 50 | Check out the [plumbing](plumbing.md) chapter to see a deeper explanation of the 51 | code that Salsa generates and how it connects to the Salsa library. 52 | -------------------------------------------------------------------------------- /book/src/meta.md: -------------------------------------------------------------------------------- 1 | # Meta: about the book itself 2 | 3 | ## Linking policy 4 | 5 | We try to avoid links that easily become fragile. 6 | 7 | **Do:** 8 | 9 | * Link to `docs.rs` types to document the public API, but modify the link to use `latest` as the version. 10 | * Link to modules in the source code. 11 | * Create ["named anchors"] and embed source code directly. 12 | 13 | ["named anchors"]: https://rust-lang.github.io/mdBook/format/mdbook.html?highlight=ANCHOR#including-portions-of-a-file 14 | 15 | **Don't:** 16 | 17 | * Link to direct lines on github, even within a specific commit, unless you are trying to reference a historical piece of code ("how things were at the time"). -------------------------------------------------------------------------------- /book/src/plumbing.md: -------------------------------------------------------------------------------- 1 | # Plumbing 2 | 3 | This chapter documents the code that salsa generates and its "inner workings". 4 | We refer to this as the "plumbing". 5 | 6 | ## Overview 7 | 8 | The plumbing section is broken up into chapters: 9 | 10 | - The [jars and ingredients](./plumbing/jars_and_ingredients.md) covers how each salsa item (like a tracked function) specifies what data it needs and runtime, and how links between items work. 11 | - The [database and runtime](./plumbing/database_and_runtime.md) covers the data structures that are used at runtime to coordinate workers, trigger cancellation, track which functions are active and what dependencies they have accrued, and so forth. 12 | - The [query operations](./plumbing/query_ops.md) chapter describes how the major operations on function ingredients work. This text was written for an older version of salsa but the logic is the same: 13 | - The [maybe changed after](./plumbing/maybe_changed_after.md) operation determines when a memoized value for a tracked function is out of date. 14 | - The [fetch](./plumbing/fetch.md) operation computes the most recent value. 15 | - The [derived queries flowchart](./plumbing/derived_flowchart.md) depicts the logic in flowchart form. 16 | - The [cycle handling](./plumbing/cycles.md) handling chapter describes what happens when cycles occur. 17 | - The [terminology](./plumbing/terminology.md) section describes various words that appear throughout. 18 | -------------------------------------------------------------------------------- /book/src/plumbing/derived_flowchart.md: -------------------------------------------------------------------------------- 1 | # Derived queries flowchart 2 | 3 | Derived queries are by far the most complex. This flowchart documents the flow of the [maybe changed after] and [fetch] operations. This flowchart can be edited on [draw.io]: 4 | 5 | [draw.io]: https://draw.io 6 | [fetch]: ./fetch.md 7 | [maybe changed after]: ./maybe_changed_after.md 8 | 9 | 10 |
11 | 12 | ![Flowchart](../derived-query-read.drawio.svg) 13 | 14 |
15 | -------------------------------------------------------------------------------- /book/src/plumbing/generated_code.md: -------------------------------------------------------------------------------- 1 | # Generated code 2 | 3 | This page walks through the ["Hello, World!"] example and explains the code that 4 | it generates. Please take it with a grain of salt: while we make an effort to 5 | keep this documentation up to date, this sort of thing can fall out of date 6 | easily. See the page history below for major updates. 7 | 8 | ["Hello, World!"]: https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs 9 | 10 | If you'd like to see for yourself, you can set the environment variable 11 | `SALSA_DUMP` to 1 while the procedural macro runs, and it will dump the full 12 | output to stdout. I recommend piping the output through rustfmt. 13 | 14 | ## Sources 15 | 16 | The main parts of the source that we are focused on are as follows. 17 | 18 | ### Query group 19 | 20 | ```rust,ignore 21 | {{#include ../../../examples/hello_world/main.rs:trait}} 22 | ``` 23 | 24 | ### Database 25 | 26 | ```rust,ignore 27 | {{#include ../../../examples/hello_world/main.rs:database}} 28 | ``` 29 | -------------------------------------------------------------------------------- /book/src/plumbing/query_ops.md: -------------------------------------------------------------------------------- 1 | # Query operations 2 | 3 | Each of the query storage struct implements the `QueryStorageOps` trait found in the [`plumbing`] module: 4 | 5 | ```rust,no_run,noplayground 6 | {{#include ../../../src/plumbing.rs:QueryStorageOps}} 7 | ``` 8 | 9 | which defines the basic operations that all queries support. The most important are these two: 10 | 11 | * [maybe changed after](./maybe_changed_after.md): Returns true if the value of the query (for the given key) may have changed since the given revision. 12 | * [Fetch](./fetch.md): Returns the up-to-date value for the given K (or an error in the case of an "unrecovered" cycle). 13 | 14 | [`plumbing`]: https://github.com/salsa-rs/salsa/blob/master/src/plumbing.rs 15 | -------------------------------------------------------------------------------- /book/src/plumbing/salsa_crate.md: -------------------------------------------------------------------------------- 1 | # Runtime 2 | 3 | This section documents the contents of the salsa crate. The salsa crate contains code that interacts with the [generated code] to create the complete "salsa experience". 4 | 5 | [generated code]: ./generated_code.md 6 | 7 | ## Major types 8 | 9 | The crate has a few major types. 10 | 11 | ### The [`salsa::Storage`] struct 12 | 13 | The [`salsa::Storage`] struct is what users embed into their database. It consists of two main parts: 14 | 15 | * The "query store", which is the [generated storage struct](./database.md#the-database-storage-struct). 16 | * The [`salsa::Runtime`]. 17 | 18 | ### The [`salsa::Runtime`] struct 19 | 20 | The [`salsa::Runtime`] struct stores the data that is used to track which queries are being executed and to coordinate between them. The `Runtime` is embedded within the [`salsa::Storage`] struct. 21 | 22 | **Important**. The `Runtime` does **not** store the actual data from the queries; they live alongside it in the [`salsa::Storage`] struct. This ensures that the type of `Runtime` is not generic which is needed to ensure dyn safety. 23 | 24 | #### Threading 25 | 26 | There is one [`salsa::Runtime`] for each active thread, and each of them has a unique [`RuntimeId`]. The `Runtime` state itself is divided into; 27 | 28 | * `SharedState`, accessible from all runtimes; 29 | * `LocalState`, accessible only from this runtime. 30 | 31 | [`salsa::Runtime`]: https://docs.rs/salsa/latest/salsa/struct.Runtime.html 32 | [`salsa::Storage`]: https://docs.rs/salsa/latest/salsa/struct.Storage.html 33 | [`RuntimeId`]: https://docs.rs/salsa/0.16.1/salsa/struct.RuntimeId.html 34 | 35 | ### Query storage implementations and support code 36 | 37 | For each kind of query (input, derived, interned, etc) there is a corresponding "storage struct" that contains the code to implement it. For example, derived queries are implemented by the `DerivedStorage` struct found in the [`salsa::derived`] module. 38 | 39 | [`salsa::derived`]: https://github.com/salsa-rs/salsa/blob/master/src/derived.rs 40 | 41 | Storage structs like `DerivedStorage` are generic over a query type `Q`, which corresponds to the [query structs] in the generated code. The query structs implement the `Query` trait which gives basic info such as the key and value type of the query and its ability to recover from cycles. In some cases, the `Q` type is expected to implement additional traits: derived queries, for example, implement `QueryFunction`, which defines the code that will execute when the query is called. 42 | 43 | [query structs]: ./query_groups.md#for-each-query-a-query-struct 44 | 45 | The storage structs, in turn, implement key traits from the plumbing module. The most notable is the `QueryStorageOps`, which defines the [basic operations that can be done on a query](./query_ops.md). 46 | -------------------------------------------------------------------------------- /book/src/plumbing/terminology.md: -------------------------------------------------------------------------------- 1 | # Terminology 2 | -------------------------------------------------------------------------------- /book/src/plumbing/terminology/LRU.md: -------------------------------------------------------------------------------- 1 | # LRU 2 | 3 | The [`set_lru_capacity`](https://docs.rs/salsa/0.16.1/salsa/struct.QueryTableMut.html#method.set_lru_capacity) method can be used to fix the maximum capacity for a query at a specific number of values. If more values are added after that point, then salsa will drop the values from older [memos] to conserve memory (we always retain the [dependency] information for those memos, however, so that we can still compute whether values may have changed, even if we don't know what that value is). 4 | 5 | [memos]: ./memo.md 6 | [dependency]: ./dependency.md 7 | -------------------------------------------------------------------------------- /book/src/plumbing/terminology/backdate.md: -------------------------------------------------------------------------------- 1 | # Backdate 2 | 3 | *Backdating* is when we mark a value that was computed in revision R as having last changed in some earlier revision. This is done when we have an older [memo] M and we can compare the two values to see that, while the [dependencies] to M may have changed, the result of the [query function] did not. 4 | 5 | [memo]: ./memo.md 6 | [dependencies]: ./dependency.md 7 | [query function]: ./query_function.md -------------------------------------------------------------------------------- /book/src/plumbing/terminology/changed_at.md: -------------------------------------------------------------------------------- 1 | # Changed at 2 | 3 | The *changed at* revision for a [memo] is the [revision] in which that memo's value last changed. Typically, this is the same as the revision in which the [query function] was last executed, but it may be an earlier revision if the memo was [backdated]. 4 | 5 | [query function]: ./query_function.md 6 | [backdated]: ./backdate.md 7 | [revision]: ./revision.md 8 | [memo]: ./memo.md -------------------------------------------------------------------------------- /book/src/plumbing/terminology/dependency.md: -------------------------------------------------------------------------------- 1 | # Dependency 2 | 3 | A *dependency* of a [query] Q is some other query Q1 that was invoked as part of computing the value for Q (typically, invoking by Q's [query function]). 4 | 5 | [query]: ./query.md 6 | [query function]: ./query_function.md -------------------------------------------------------------------------------- /book/src/plumbing/terminology/derived_query.md: -------------------------------------------------------------------------------- 1 | # Derived query 2 | 3 | A *derived query* is a [query] whose value is defined by the result of a user-provided [query function]. That function is executed to get the result of the query. Unlike [input queries], the result of a derived queries can always be recomputed whenever needed simply by re-executing the function. 4 | 5 | [query]: ./query.md 6 | [query function]: ./query_function.md 7 | [input queries]: ./input_query.md -------------------------------------------------------------------------------- /book/src/plumbing/terminology/durability.md: -------------------------------------------------------------------------------- 1 | # Durability 2 | 3 | *Durability* is an optimization that we use to avoid checking the [dependencies] of a [query] individually. 4 | 5 | [dependencies]: ./dependency.md 6 | [query]: ./query.md 7 | -------------------------------------------------------------------------------- /book/src/plumbing/terminology/ingredient.md: -------------------------------------------------------------------------------- 1 | # Ingredient 2 | 3 | An *ingredient* is an individual piece of storage used to create a [salsa item](./salsa_item.md) 4 | See the [jars and ingredients](../jars_and_ingredients.md) chapter for more details. -------------------------------------------------------------------------------- /book/src/plumbing/terminology/input_query.md: -------------------------------------------------------------------------------- 1 | # Input query 2 | 3 | An *input query* is a [query] whose value is explicitly set by the user. When that value is set, a [durability] can also be provided. 4 | 5 | [query]: ./query.md 6 | [durability]: ./durability.md -------------------------------------------------------------------------------- /book/src/plumbing/terminology/memo.md: -------------------------------------------------------------------------------- 1 | # Memo 2 | 3 | A *memo* stores information about the last time that a [query function] for some [query] Q was executed: 4 | 5 | * Typically, it contains the value that was returned from that function, so that we don't have to execute it again. 6 | * However, this is not always true: some queries don't cache their result values, and values can also be dropped as a result of [LRU] collection. In those cases, the memo just stores [dependency] information, which can still be useful to determine if other queries that have Q as a [dependency] may have changed. 7 | * The revision in which the memo last [verified]. 8 | * The [changed at] revision in which the memo's value last changed. (Note that it may be [backdated].) 9 | * The minimum durability of the memo's [dependencies]. 10 | * The complete set of [dependencies], if available, or a marker that the memo has an [untracked dependency]. 11 | 12 | [revision]: ./revision.md 13 | [backdated]: ./backdate.md 14 | [dependencies]: ./dependency.md 15 | [dependency]: ./dependency.md 16 | [untracked dependency]: ./untracked.md 17 | [verified]: ./verified.md 18 | [query]: ./query.md 19 | [query function]: ./query_function.md 20 | [changed at]: ./changed_at.md 21 | [LRU]: ./LRU.md -------------------------------------------------------------------------------- /book/src/plumbing/terminology/query.md: -------------------------------------------------------------------------------- 1 | # Query 2 | -------------------------------------------------------------------------------- /book/src/plumbing/terminology/query_function.md: -------------------------------------------------------------------------------- 1 | # Query function 2 | 3 | The *query function* is the user-provided function that we execute to compute the value of a [derived query]. Salsa assumed that all query functions are a 'pure' function of their [dependencies] unless the user reports an [untracked read]. Salsa always assumes that functions have no important side-effects (i.e., that they don't send messages over the network whose results you wish to observe) and thus that it doesn't have to re-execute functions unless it needs their return value. 4 | 5 | [derived query]: ./derived_query.md 6 | [dependencies]: ./dependency.md 7 | [untracked read]: ./untracked.md -------------------------------------------------------------------------------- /book/src/plumbing/terminology/revision.md: -------------------------------------------------------------------------------- 1 | # Revision 2 | 3 | A *revision* is a monotonically increasing integer that we use to track the "version" of the database. Each time the value of an [input query] is modified, we create a new revision. 4 | 5 | [input query]: ./input_query.md -------------------------------------------------------------------------------- /book/src/plumbing/terminology/salsa_item.md: -------------------------------------------------------------------------------- 1 | # Salsa item 2 | 3 | A salsa item is something that is decorated with a `#[salsa::foo]` macro, like a tracked function or struct. 4 | See the [jars and ingredients](../jars_and_ingredients.md) chapter for more details. -------------------------------------------------------------------------------- /book/src/plumbing/terminology/salsa_struct.md: -------------------------------------------------------------------------------- 1 | # Salsa struct 2 | 3 | A salsa struct is a struct decorated with one of the salsa macros: 4 | 5 | * `#[salsa::tracked]` 6 | * `#[salsa::input]` 7 | * `#[salsa::interned]` 8 | 9 | See the [salsa overview](../../overview.md) for more details. -------------------------------------------------------------------------------- /book/src/plumbing/terminology/untracked.md: -------------------------------------------------------------------------------- 1 | # Untracked dependency 2 | 3 | An *untracked dependency* is an indication that the result of a [derived query] depends on something not visible to the salsa database. Untracked dependencies are created by invoking [`report_untracked_read`](https://docs.rs/salsa/0.16.1/salsa/struct.Runtime.html#method.report_untracked_read) or [`report_synthetic_read`](https://docs.rs/salsa/0.16.1/salsa/struct.Runtime.html#method.report_synthetic_read). When an untracked dependency is present, [derived queries] are always re-executed if the durability check fails (see the description of the [fetch operation] for more details). 4 | 5 | [derived query]: ./derived_query.md 6 | [derived queries]: ./derived_query.md 7 | [fetch operation]: ../fetch.md#derived-queries 8 | -------------------------------------------------------------------------------- /book/src/plumbing/terminology/verified.md: -------------------------------------------------------------------------------- 1 | # Verified 2 | 3 | A [memo] is *verified* in a revision R if we have checked that its value is still up-to-date (i.e., if we were to reexecute the [query function], we are guaranteed to get the same result). Each memo tracks the revision in which it was last verified to avoid repeatedly checking whether dependencies have changed during the [fetch] and [maybe changed after] operations. 4 | 5 | [query function]: ./query_function.md 6 | [fetch]: ../fetch.md 7 | [maybe changed after]: ../maybe_changed_after.md 8 | [memo]: ./memo.md -------------------------------------------------------------------------------- /book/src/plumbing/tracked_structs.md: -------------------------------------------------------------------------------- 1 | # Tracked structs 2 | 3 | Tracked structs are stored in a special way to reduce their costs. 4 | 5 | Tracked structs are created via a `new` operation. 6 | 7 | ## The tracked struct and tracked field ingredients 8 | 9 | For a single tracked struct we create multiple ingredients. 10 | The **tracked struct ingredient** is the ingredient created first. 11 | It offers methods to create new instances of the struct and therefore 12 | has unique access to the interner and hashtables used to create the struct id. 13 | It also shares access to a hashtable that stores the `ValueStruct` that 14 | contains the field data. 15 | 16 | For each field, we create a **tracked field ingredient** that moderates access 17 | to a particular field. All of these ingredients use that same shared hashtable 18 | to access the `ValueStruct` instance for a given id. The `ValueStruct` 19 | contains both the field values but also the revisions when they last changed value. 20 | 21 | ## Each tracked struct has a globally unique id 22 | 23 | This will begin by creating a _globally unique, 32-bit id_ for the tracked struct. It is created by interning a combination of 24 | 25 | - the currently executing query; 26 | - a u64 hash of the `#[id]` fields; 27 | - a _disambiguator_ that makes this hash unique within the current query. i.e., when a query starts executing, it creates an empty map, and the first time a tracked struct with a given hash is created, it gets disambiguator 0. The next one will be given 1, etc. 28 | 29 | ## Each tracked struct has a `ValueStruct` storing its data 30 | 31 | The struct and field ingredients share access to a hashmap that maps 32 | each field id to a value struct: 33 | 34 | ```rust,ignore 35 | {{#include ../../../src/tracked_struct.rs:ValueStruct}} 36 | ``` 37 | 38 | The value struct stores the values of the fields but also the revisions when 39 | that field last changed. Each time the struct is recreated in a new revision, 40 | the old and new values for its fields are compared and a new revision is created. 41 | 42 | ## The macro generates the tracked struct `Configuration` 43 | 44 | The "configuration" for a tracked struct defines not only the types of the fields, 45 | but also various important operations such as extracting the hashable id fields 46 | and updating the "revisions" to track when a field last changed: 47 | 48 | ```rust,ignore 49 | {{#include ../../../src/tracked_struct.rs:Configuration}} 50 | ``` 51 | -------------------------------------------------------------------------------- /book/src/reference.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | -------------------------------------------------------------------------------- /book/src/reference/durability.md: -------------------------------------------------------------------------------- 1 | # Durability 2 | 3 | "Durability" is an optimization that can greatly improve the performance of your salsa programs. 4 | Durability specifies the probability that an input's value will change. 5 | The default is "low durability". 6 | But when you set the value of an input, you can manually specify a higher durability, 7 | typically `Durability::HIGH`. 8 | Salsa tracks when tracked functions only consume values of high durability 9 | and, if no high durability input has changed, it can skip traversing their 10 | dependencies. 11 | 12 | Typically "high durability" values are things like data read from the standard library 13 | or other inputs that aren't actively being edited by the end user. -------------------------------------------------------------------------------- /book/src/tuning.md: -------------------------------------------------------------------------------- 1 | # Tuning Salsa 2 | 3 | ## LRU Cache 4 | 5 | You can specify an LRU cache size for any non-input query: 6 | 7 | ```rs 8 | let lru_capacity: usize = 128; 9 | base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity); 10 | ``` 11 | 12 | The default is `0`, which disables LRU-caching entirely. 13 | 14 | Note that there is no garbage collection for keys and 15 | results of old queries, so LRU caches are currently the 16 | only knob available for avoiding unbounded memory usage 17 | for long-running apps built on Salsa. 18 | 19 | ## Intern Queries 20 | 21 | Intern queries can make key lookup cheaper, save memory, and 22 | avoid the need for [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html). 23 | 24 | Interning is especially useful for queries that involve nested, 25 | tree-like data structures. 26 | 27 | See: 28 | 29 | - The [`compiler` example](https://github.com/salsa-rs/salsa/blob/master/examples/compiler/main.rs), 30 | which uses interning. 31 | 32 | ## Cancellation 33 | 34 | Queries that are no longer needed due to concurrent writes or changes in dependencies are cancelled 35 | by Salsa. Each access of an intermediate query is a potential cancellation point. Cancellation is 36 | implemented via panicking, and Salsa internals are intended to be panic-safe. 37 | 38 | If you have a query that contains a long loop which does not execute any intermediate queries, 39 | salsa won't be able to cancel it automatically. You may wish to check for cancellation yourself 40 | by invoking `db.unwind_if_cancelled()`. 41 | 42 | For more details on cancellation, see the tests for cancellation behavior in the Salsa repo. 43 | -------------------------------------------------------------------------------- /book/src/tutorial.md: -------------------------------------------------------------------------------- 1 | # Tutorial: calc 2 | 3 | This tutorial walks through an end-to-end example of using Salsa. 4 | It does not assume you know anything about salsa, 5 | but reading the [overview](./overview.md) first is probably a good idea to get familiar with the basic concepts. 6 | 7 | Our goal is define a compiler/interpreter for a simple language called `calc`. 8 | The `calc` compiler takes programs like the following and then parses and executes them: 9 | 10 | ``` 11 | fn area_rectangle(w, h) = w * h 12 | fn area_circle(r) = 3.14 * r * r 13 | print area_rectangle(3, 4) 14 | print area_circle(1) 15 | print 11 * 2 16 | ``` 17 | 18 | When executed, this program prints `12`, `3.14`, and `22`. 19 | 20 | If the program contains errors (e.g., a reference to an undefined function), it prints those out too. 21 | And, of course, it will be reactive, so small changes to the input don't require recompiling (or rexecuting, necessarily) the entire thing. 22 | -------------------------------------------------------------------------------- /book/src/tutorial/accumulators.md: -------------------------------------------------------------------------------- 1 | # Defining the parser: reporting errors 2 | 3 | The last interesting case in the parser is how to handle a parse error. 4 | Because Salsa functions are memoized and may not execute, they should not have side-effects, 5 | so we don't just want to call `eprintln!`. 6 | If we did so, the error would only be reported the first time the function was called, but not 7 | on subsequent calls in the situation where the simply returns its memoized value. 8 | 9 | Salsa defines a mechanism for managing this called an **accumulator**. 10 | In our case, we define an accumulator struct called `Diagnostics` in the `ir` module: 11 | 12 | ```rust 13 | {{#include ../../../examples/calc/ir.rs:diagnostic}} 14 | ``` 15 | 16 | Accumulator structs are always newtype structs with a single field, in this case of type `Diagnostic`. 17 | Memoized functions can _push_ `Diagnostic` values onto the accumulator. 18 | Later, you can invoke a method to find all the values that were pushed by the memoized functions 19 | or any functions that they called 20 | (e.g., we could get the set of `Diagnostic` values produced by the `parse_statements` function). 21 | 22 | The `Parser::report_error` method contains an example of pushing a diagnostic: 23 | 24 | ```rust 25 | {{#include ../../../examples/calc/parser.rs:report_error}} 26 | ``` 27 | 28 | To get the set of diagnostics produced by `parse_errors`, or any other memoized function, 29 | we invoke the associated `accumulated` function: 30 | 31 | ```rust 32 | let accumulated: Vec = 33 | parse_statements::accumulated::(db); 34 | // ----------- 35 | // Use turbofish to specify 36 | // the diagnostics type. 37 | ``` 38 | 39 | `accumulated` takes the database `db` as argument and returns a `Vec`. 40 | -------------------------------------------------------------------------------- /book/src/tutorial/checker.md: -------------------------------------------------------------------------------- 1 | # Defining the checker 2 | -------------------------------------------------------------------------------- /book/src/tutorial/db.md: -------------------------------------------------------------------------------- 1 | # Defining the database struct 2 | 3 | Now that we have defined a [jar](./jar.md), we need to create the **database struct**. 4 | The database struct is where all the jars come together. 5 | Typically it is only used by the "driver" of your application; 6 | the one which starts up the program, supplies the inputs, and relays the outputs. 7 | 8 | In `calc`, the database struct is in the [`db`] module, and it looks like this: 9 | 10 | [`db`]: https://github.com/salsa-rs/salsa/blob/master/examples/calc/db.rs 11 | 12 | ```rust 13 | {{#include ../../../examples/calc/db.rs:db_struct}} 14 | ``` 15 | 16 | The `#[salsa::db(...)]` attribute takes a list of all the jars to include. 17 | The struct must have a field named `storage` whose type is `salsa::Storage`, but it can also contain whatever other fields you want. 18 | The `storage` struct owns all the data for the jars listed in the `db` attribute. 19 | 20 | The `salsa::db` attribute autogenerates a bunch of impls for things like the `salsa::HasJar` trait that we saw earlier. 21 | 22 | ## Implementing the `salsa::Database` trait 23 | 24 | In addition to the struct itself, we must add an impl of `salsa::Database`: 25 | 26 | ```rust 27 | {{#include ../../../examples/calc/db.rs:db_impl}} 28 | ``` 29 | 30 | ## Implementing the `salsa::ParallelDatabase` trait 31 | 32 | If you want to permit accessing your database from multiple threads at once, then you also need to implement the `ParallelDatabase` trait: 33 | 34 | ```rust 35 | {{#include ../../../examples/calc/db.rs:par_db_impl}} 36 | ``` 37 | 38 | ## Implementing the traits for each jar 39 | 40 | The `Database` struct also needs to implement the [database traits for each jar](./jar.md#database-trait-for-the-jar). 41 | In our case, though, we already wrote that impl as a [blanket impl alongside the jar itself](./jar.md#implementing-the-database-trait-for-the-jar), 42 | so no action is needed. 43 | This is the recommended strategy unless your trait has custom members that depend on fields of the `Database` itself 44 | (for example, sometimes the `Database` holds some kind of custom resource that you want to give access to). 45 | -------------------------------------------------------------------------------- /book/src/tutorial/debug.md: -------------------------------------------------------------------------------- 1 | # Defining the parser: debug impls and testing 2 | 3 | As the final part of the parser, we need to write some tests. 4 | To do so, we will create a database, set the input source text, run the parser, and check the result. 5 | Before we can do that, though, we have to address one question: how do we inspect the value of an interned type like `Expression`? 6 | 7 | ## The `DebugWithDb` trait 8 | 9 | Because an interned type like `Expression` just stores an integer, the traditional `Debug` trait is not very useful. 10 | To properly print a `Expression`, you need to access the Salsa database to find out what its value is. 11 | To solve this, `salsa` provides a `DebugWithDb` trait that acts like the regular `Debug`, but takes a database as argument. 12 | For types that implement this trait, you can invoke the `debug` method. 13 | This returns a temporary that implements the ordinary `Debug` trait, allowing you to write something like 14 | 15 | ```rust 16 | eprintln!("Expression = {:?}", expr.debug(db)); 17 | ``` 18 | 19 | and get back the output you expect. 20 | 21 | The `DebugWithDb` trait is automatically derived for all `#[input]`, `#[interned]`, and `#[tracked]` structs. 22 | 23 | ## Forwarding to the ordinary `Debug` trait 24 | 25 | For consistency, it is sometimes useful to have a `DebugWithDb` implementation even for types, like `Op`, that are just ordinary enums. You can do that like so: 26 | 27 | ```rust 28 | {{#include ../../../examples/calc/ir.rs:op_debug_impl}} 29 | ``` 30 | 31 | ## Writing the unit test 32 | 33 | Now that we have our `DebugWithDb` impls in place, we can write a simple unit test harness. 34 | The `parse_string` function below creates a database, sets the source text, and then invokes the parser: 35 | 36 | ```rust 37 | {{#include ../../../examples/calc/parser.rs:parse_string}} 38 | ``` 39 | 40 | Combined with the [`expect-test`](https://crates.io/crates/expect-test) crate, we can then write unit tests like this one: 41 | 42 | ```rust 43 | {{#include ../../../examples/calc/parser.rs:parse_print}} 44 | ``` 45 | -------------------------------------------------------------------------------- /book/src/tutorial/interpreter.md: -------------------------------------------------------------------------------- 1 | # Defining the interpreter 2 | -------------------------------------------------------------------------------- /book/src/tutorial/structure.md: -------------------------------------------------------------------------------- 1 | # Basic structure 2 | 3 | Before we do anything with Salsa, let's talk about the basic structure of the calc compiler. 4 | Part of Salsa's design is that you are able to write programs that feel 'pretty close' to what a natural Rust program looks like. 5 | 6 | ## Example program 7 | 8 | This is our example calc program: 9 | 10 | ``` 11 | x = 5 12 | y = 10 13 | z = x + y * 3 14 | print z 15 | ``` 16 | 17 | ## Parser 18 | 19 | The calc compiler takes as input a program, represented by a string: 20 | 21 | ```rust 22 | struct ProgramSource { 23 | text: String 24 | } 25 | ``` 26 | 27 | The first thing it does it to parse that string into a series of statements that look something like the following pseudo-Rust:[^lexer] 28 | 29 | ```rust 30 | enum Statement { 31 | /// Defines `fn () = ` 32 | Function(Function), 33 | /// Defines `print ` 34 | Print(Expression), 35 | } 36 | 37 | /// Defines `fn () = ` 38 | struct Function { 39 | name: FunctionId, 40 | args: Vec, 41 | body: Expression 42 | } 43 | ``` 44 | 45 | where an expression is something like this (pseudo-Rust, because the `Expression` enum is recursive): 46 | 47 | ```rust 48 | enum Expression { 49 | Op(Expression, Op, Expression), 50 | Number(f64), 51 | Variable(VariableId), 52 | Call(FunctionId, Vec), 53 | } 54 | 55 | enum Op { 56 | Add, 57 | Subtract, 58 | Multiply, 59 | Divide, 60 | } 61 | ``` 62 | 63 | Finally, for function/variable names, the `FunctionId` and `VariableId` types will be interned strings: 64 | 65 | ```rust 66 | type FunctionId = /* interned string */; 67 | type VariableId = /* interned string */; 68 | ``` 69 | 70 | [^lexer]: Because calc is so simple, we don't have to bother separating out the lexer from the parser. 71 | 72 | ## Checker 73 | 74 | The "checker" has the job of ensuring that the user only references variables that have been defined. 75 | We're going to write the checker in a "context-less" style, 76 | which is a bit less intuitive but allows for more incremental re-use. 77 | The idea is to compute, for a given expression, which variables it references. 78 | Then there is a function `check` which ensures that those variables are a subset of those that are already defined. 79 | 80 | ## Interpreter 81 | 82 | The interpreter will execute the program and print the result. We don't bother with much incremental re-use here, 83 | though it's certainly possible. 84 | -------------------------------------------------------------------------------- /book/src/videos.md: -------------------------------------------------------------------------------- 1 | # Videos 2 | 3 | There is currently one video available on the newest version of Salsa: 4 | 5 | - [Salsa Architecture Walkthrough](https://www.youtube.com/watch?v=vrnNvAAoQFk), 6 | which covers many aspects of the redesigned architecture. 7 | 8 | There are also two videos on the older version Salsa, but they are rather 9 | outdated: 10 | 11 | - [How Salsa Works](https://youtu.be/_muY4HjSqVw), which gives a high-level 12 | introduction to the key concepts involved and shows how to use Salsa; 13 | - [Salsa In More Depth](https://www.youtube.com/watch?v=i_IhACacPRY), which digs 14 | into the incremental algorithm and explains -- at a high-level -- how Salsa is 15 | implemented. 16 | 17 | > If you're in China, watch videos on 18 | > [How Salsa Works](https://www.bilibili.com/video/BV1Df4y1A7t3/), 19 | > [Salsa In More Depth](https://www.bilibili.com/video/BV1AM4y1G7E4/). 20 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "salsa-macro-rules" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate defines various `macro_rules` macros 2 | //! used as part of Salsa's internal plumbing. 3 | //! These macros are re-exported under `salsa::plumbing``. 4 | //! The procedural macros emit calls to these 5 | //! `macro_rules` macros after doing error checking. 6 | //! 7 | //! Using `macro_rules` macro definitions is generally 8 | //! more ergonomic and also permits true hygiene for local variables 9 | //! (sadly not items). 10 | //! 11 | //! Currently the only way to have a macro that is re-exported 12 | //! from a submodule is to use multiple crates, hence the existence 13 | //! of this crate. 14 | 15 | mod macro_if; 16 | mod maybe_backdate; 17 | mod maybe_clone; 18 | mod maybe_default; 19 | mod setup_accumulator_impl; 20 | mod setup_input_struct; 21 | mod setup_interned_struct; 22 | mod setup_method_body; 23 | mod setup_tracked_fn; 24 | mod setup_tracked_struct; 25 | mod unexpected_cycle_recovery; 26 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/macro_if.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! macro_if { 3 | (true => $($t:tt)*) => { 4 | $($t)* 5 | }; 6 | 7 | (false => $($t:tt)*) => { 8 | }; 9 | 10 | (if true { $($t:tt)* } else { $($f:tt)*}) => { 11 | $($t)* 12 | }; 13 | 14 | (if false { $($t:tt)* } else { $($f:tt)*}) => { 15 | $($f)* 16 | }; 17 | 18 | (if0 0 { $($t:tt)* } else { $($f:tt)*}) => { 19 | $($t)* 20 | }; 21 | 22 | (if0 $n:literal { $($t:tt)* } else { $($f:tt)*}) => { 23 | $($f)* 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/maybe_backdate.rs: -------------------------------------------------------------------------------- 1 | /// Conditionally update field value and backdate revisions 2 | #[macro_export] 3 | macro_rules! maybe_backdate { 4 | ( 5 | ($maybe_clone:ident, no_backdate, $maybe_default:ident), 6 | $field_ty:ty, 7 | $old_field_place:expr, 8 | $new_field_place:expr, 9 | $revision_place:expr, 10 | $current_revision:expr, 11 | $zalsa:ident, 12 | 13 | ) => { 14 | $zalsa::always_update( 15 | &mut $revision_place, 16 | $current_revision, 17 | &mut $old_field_place, 18 | $new_field_place, 19 | ); 20 | }; 21 | 22 | ( 23 | ($maybe_clone:ident, backdate, $maybe_default:ident), 24 | $field_ty:ty, 25 | $old_field_place:expr, 26 | $new_field_place:expr, 27 | $revision_place:expr, 28 | $current_revision:expr, 29 | $zalsa:ident, 30 | ) => { 31 | if $zalsa::UpdateDispatch::<$field_ty>::maybe_update( 32 | std::ptr::addr_of_mut!($old_field_place), 33 | $new_field_place, 34 | ) { 35 | $revision_place = $current_revision; 36 | } 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/maybe_clone.rs: -------------------------------------------------------------------------------- 1 | /// Generate either `field_ref_expr` or a clone of that expr. 2 | /// 3 | /// Used when generating field getters. 4 | #[macro_export] 5 | macro_rules! maybe_clone { 6 | ( 7 | (no_clone, $maybe_backdate:ident, $maybe_default:ident), 8 | $field_ty:ty, 9 | $field_ref_expr:expr, 10 | ) => { 11 | $field_ref_expr 12 | }; 13 | 14 | ( 15 | (clone, $maybe_backdate:ident, $maybe_default:ident), 16 | $field_ty:ty, 17 | $field_ref_expr:expr, 18 | ) => { 19 | std::clone::Clone::clone($field_ref_expr) 20 | }; 21 | } 22 | 23 | #[macro_export] 24 | macro_rules! maybe_cloned_ty { 25 | ( 26 | (no_clone, $maybe_backdate:ident, $maybe_default:ident), 27 | $db_lt:lifetime, 28 | $field_ty:ty 29 | ) => { 30 | & $db_lt $field_ty 31 | }; 32 | 33 | ( 34 | (clone, $maybe_backdate:ident, $maybe_default:ident), 35 | $db_lt:lifetime, 36 | $field_ty:ty 37 | ) => { 38 | $field_ty 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/maybe_default.rs: -------------------------------------------------------------------------------- 1 | /// Generate either `field_ref_expr` or `field_ty::default` 2 | /// 3 | /// Used when generating an input's builder. 4 | #[macro_export] 5 | macro_rules! maybe_default { 6 | ( 7 | ($maybe_clone:ident, $maybe_backdate:ident, default), 8 | $field_ty:ty, 9 | $field_ref_expr:expr, 10 | ) => { 11 | <$field_ty>::default() 12 | }; 13 | 14 | ( 15 | ($maybe_clone:ident, $maybe_backdate:ident, required), 16 | $field_ty:ty, 17 | $field_ref_expr:expr, 18 | ) => { 19 | $field_ref_expr 20 | }; 21 | } 22 | 23 | #[macro_export] 24 | macro_rules! maybe_default_tt { 25 | (($maybe_clone:ident, $maybe_backdate:ident, default) => $($t:tt)*) => { 26 | $($t)* 27 | }; 28 | 29 | (($maybe_clone:ident, $maybe_backdate:ident, required) => $($t:tt)*) => { 30 | 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/setup_accumulator_impl.rs: -------------------------------------------------------------------------------- 1 | /// Macro for setting up a function that must intern its arguments. 2 | #[macro_export] 3 | macro_rules! setup_accumulator_impl { 4 | ( 5 | // Name of the struct 6 | Struct: $Struct:ident, 7 | 8 | // Annoyingly macro-rules hygiene does not extend to items defined in the macro. 9 | // We have the procedural macro generate names for those items that are 10 | // not used elsewhere in the user's code. 11 | unused_names: [ 12 | $zalsa:ident, 13 | $zalsa_struct:ident, 14 | $CACHE:ident, 15 | $ingredient:ident, 16 | ] 17 | ) => { 18 | const _: () = { 19 | use salsa::plumbing as $zalsa; 20 | use salsa::plumbing::accumulator as $zalsa_struct; 21 | 22 | static $CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Struct>> = 23 | $zalsa::IngredientCache::new(); 24 | 25 | fn $ingredient(db: &dyn $zalsa::Database) -> &$zalsa_struct::IngredientImpl<$Struct> { 26 | $CACHE.get_or_create(db, || { 27 | db.zalsa().add_or_lookup_jar_by_type(&<$zalsa_struct::JarImpl<$Struct>>::default()) 28 | }) 29 | } 30 | 31 | impl $zalsa::Accumulator for $Struct { 32 | const DEBUG_NAME: &'static str = stringify!($Struct); 33 | 34 | fn accumulate(self, db: &Db) 35 | where 36 | Db: ?Sized + $zalsa::Database, 37 | { 38 | let db = db.as_dyn_database(); 39 | $ingredient(db).push(db, self); 40 | } 41 | } 42 | }; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/setup_method_body.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! setup_method_body { 3 | ( 4 | salsa_tracked_attr: #[$salsa_tracked_attr:meta], 5 | self: $self:ident, 6 | self_ty: $self_ty:ty, 7 | db_lt: $($db_lt:lifetime)?, 8 | db: $db:ident, 9 | db_ty: ($($db_ty:tt)*), 10 | input_ids: [$($input_id:ident),*], 11 | input_tys: [$($input_ty:ty),*], 12 | output_ty: $output_ty:ty, 13 | inner_fn_name: $inner_fn_name:ident, 14 | inner_fn: $inner_fn:item, 15 | 16 | // Annoyingly macro-rules hygiene does not extend to items defined in the macro. 17 | // We have the procedural macro generate names for those items that are 18 | // not used elsewhere in the user's code. 19 | unused_names: [ 20 | $InnerTrait:ident, 21 | ] 22 | ) => { 23 | { 24 | trait $InnerTrait<$($db_lt)?> { 25 | fn $inner_fn_name($self, db: $($db_ty)*, $($input_id: $input_ty),*) -> $output_ty; 26 | } 27 | 28 | impl<$($db_lt)?> $InnerTrait<$($db_lt)?> for $self_ty { 29 | $inner_fn 30 | } 31 | 32 | #[$salsa_tracked_attr] 33 | fn $inner_fn_name<$($db_lt)?>(db: $($db_ty)*, this: $self_ty, $($input_id: $input_ty),*) -> $output_ty { 34 | <$self_ty as $InnerTrait>::$inner_fn_name(this, db, $($input_id),*) 35 | } 36 | 37 | $inner_fn_name($db, $self, $($input_id),*) 38 | } 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/unexpected_cycle_recovery.rs: -------------------------------------------------------------------------------- 1 | // Macro that generates the body of the cycle recovery function 2 | // for the case where no cycle recovery is possible. This has to be 3 | // a macro because it can take a variadic number of arguments. 4 | #[macro_export] 5 | macro_rules! unexpected_cycle_recovery { 6 | ($db:ident, $cycle:ident, $($other_inputs:ident),*) => { 7 | { 8 | std::mem::drop($db); 9 | std::mem::drop(($($other_inputs),*)); 10 | panic!("cannot recover from cycle `{:?}`", $cycle) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /components/salsa-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "salsa-macros" 3 | version = "0.18.0" 4 | authors = ["Salsa developers"] 5 | edition = "2021" 6 | license = "Apache-2.0 OR MIT" 7 | repository = "https://github.com/salsa-rs/salsa" 8 | description = "Procedural macros for the salsa crate" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | heck = "0.5.0" 15 | proc-macro2 = "1.0" 16 | quote = "1.0" 17 | syn = { version = "2.0.64", features = ["full", "visit-mut"] } 18 | synstructure = "0.13.1" 19 | -------------------------------------------------------------------------------- /components/salsa-macros/src/accumulator.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | use crate::{ 4 | hygiene::Hygiene, 5 | options::{AllowedOptions, Options}, 6 | }; 7 | 8 | // #[salsa::accumulator(jar = Jar0)] 9 | // struct Accumulator(DataType); 10 | 11 | pub(crate) fn accumulator( 12 | args: proc_macro::TokenStream, 13 | input: proc_macro::TokenStream, 14 | ) -> proc_macro::TokenStream { 15 | let hygiene = Hygiene::from1(&input); 16 | let args = syn::parse_macro_input!(args as Options); 17 | let struct_item = syn::parse_macro_input!(input as syn::ItemStruct); 18 | let ident = struct_item.ident.clone(); 19 | let m = StructMacro { 20 | hygiene, 21 | args, 22 | struct_item, 23 | }; 24 | match m.try_expand() { 25 | Ok(v) => crate::debug::dump_tokens(ident, v).into(), 26 | Err(e) => e.to_compile_error().into(), 27 | } 28 | } 29 | 30 | struct Accumulator; 31 | 32 | impl AllowedOptions for Accumulator { 33 | const RETURN_REF: bool = false; 34 | const SPECIFY: bool = false; 35 | const NO_EQ: bool = false; 36 | const NO_DEBUG: bool = true; 37 | const NO_CLONE: bool = true; 38 | const SINGLETON: bool = false; 39 | const DATA: bool = false; 40 | const DB: bool = false; 41 | const RECOVERY_FN: bool = false; 42 | const LRU: bool = false; 43 | const CONSTRUCTOR_NAME: bool = false; 44 | } 45 | 46 | struct StructMacro { 47 | hygiene: Hygiene, 48 | args: Options, 49 | struct_item: syn::ItemStruct, 50 | } 51 | 52 | #[allow(non_snake_case)] 53 | impl StructMacro { 54 | fn try_expand(self) -> syn::Result { 55 | let ident = self.struct_item.ident.clone(); 56 | 57 | let zalsa = self.hygiene.ident("zalsa"); 58 | let zalsa_struct = self.hygiene.ident("zalsa_struct"); 59 | let CACHE = self.hygiene.ident("CACHE"); 60 | let ingredient = self.hygiene.ident("ingredient"); 61 | 62 | let struct_item = self.struct_item; 63 | 64 | let mut derives = vec![]; 65 | if self.args.no_debug.is_none() { 66 | derives.push(quote!(Debug)); 67 | } 68 | if self.args.no_clone.is_none() { 69 | derives.push(quote!(Clone)); 70 | } 71 | 72 | Ok(quote! { 73 | #[derive(#(#derives),*)] 74 | #struct_item 75 | 76 | salsa::plumbing::setup_accumulator_impl! { 77 | Struct: #ident, 78 | unused_names: [ 79 | #zalsa, 80 | #zalsa_struct, 81 | #CACHE, 82 | #ingredient, 83 | ] 84 | } 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /components/salsa-macros/src/db_lifetime.rs: -------------------------------------------------------------------------------- 1 | //! Helper functions for working with fns, structs, and other generic things 2 | //! that are allowed to have a `'db` lifetime. 3 | 4 | use proc_macro2::Span; 5 | use syn::spanned::Spanned; 6 | 7 | /// Normally we try to use whatever lifetime parameter the user gave us 8 | /// to represent `'db`; but if they didn't give us one, we need to use a default 9 | /// name. We choose `'db`. 10 | pub(crate) fn default_db_lifetime(span: Span) -> syn::Lifetime { 11 | syn::Lifetime { 12 | apostrophe: span, 13 | ident: syn::Ident::new("db", span), 14 | } 15 | } 16 | 17 | /// Require that either there are no generics or exactly one lifetime parameter. 18 | pub(crate) fn require_optional_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { 19 | if generics.params.is_empty() { 20 | return Ok(()); 21 | } 22 | 23 | require_db_lifetime(generics)?; 24 | 25 | Ok(()) 26 | } 27 | 28 | /// Require that either there is exactly one lifetime parameter. 29 | pub(crate) fn require_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { 30 | if generics.params.is_empty() { 31 | return Err(syn::Error::new_spanned( 32 | generics, 33 | "this definition must have a `'db` lifetime", 34 | )); 35 | } 36 | 37 | for (param, index) in generics.params.iter().zip(0..) { 38 | let error = match param { 39 | syn::GenericParam::Lifetime(_) => index > 0, 40 | syn::GenericParam::Type(_) | syn::GenericParam::Const(_) => true, 41 | }; 42 | 43 | if error { 44 | return Err(syn::Error::new_spanned( 45 | param, 46 | "only a single lifetime parameter is accepted", 47 | )); 48 | } 49 | } 50 | 51 | Ok(()) 52 | } 53 | 54 | /// Return the `'db` lifetime given by the user, or a default. 55 | /// The generics ought to have been checked with `require_db_lifetime` already. 56 | pub(crate) fn db_lifetime(generics: &syn::Generics) -> syn::Lifetime { 57 | if let Some(lt) = generics.lifetimes().next() { 58 | lt.lifetime.clone() 59 | } else { 60 | default_db_lifetime(generics.span()) 61 | } 62 | } 63 | 64 | pub(crate) fn require_no_generics(generics: &syn::Generics) -> syn::Result<()> { 65 | if let Some(param) = generics.params.iter().next() { 66 | return Err(syn::Error::new_spanned( 67 | param, 68 | "generic parameters not allowed here", 69 | )); 70 | } 71 | 72 | Ok(()) 73 | } 74 | -------------------------------------------------------------------------------- /components/salsa-macros/src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::process::{Command, Stdio}; 3 | use std::sync::OnceLock; 4 | 5 | use proc_macro2::TokenStream; 6 | 7 | static SALSA_DEBUG_MACRO: OnceLock> = OnceLock::new(); 8 | 9 | pub(crate) fn debug_enabled(input_name: impl ToString) -> bool { 10 | let Some(env_name) = SALSA_DEBUG_MACRO.get_or_init(|| std::env::var("SALSA_DEBUG_MACRO").ok()) 11 | else { 12 | return false; 13 | }; 14 | 15 | let input_name = input_name.to_string(); 16 | env_name == "*" || env_name == &input_name[..] 17 | } 18 | 19 | pub(crate) fn dump_tokens(input_name: impl ToString, tokens: TokenStream) -> TokenStream { 20 | if debug_enabled(input_name) { 21 | let token_string = tokens.to_string(); 22 | 23 | let _: Result<(), ()> = Command::new("rustfmt") 24 | .arg("--emit=stdout") 25 | .stdin(Stdio::piped()) 26 | .spawn() 27 | .and_then(|mut rustfmt| { 28 | rustfmt 29 | .stdin 30 | .take() 31 | .unwrap() 32 | .write_all(token_string.as_bytes())?; 33 | rustfmt.wait_with_output() 34 | }) 35 | .map(|output| eprintln!("{}", String::from_utf8_lossy(&output.stdout))) 36 | .or_else(|_| Ok(eprintln!("{token_string}"))); 37 | } 38 | 39 | tokens 40 | } 41 | -------------------------------------------------------------------------------- /components/salsa-macros/src/fn_util.rs: -------------------------------------------------------------------------------- 1 | use crate::{hygiene::Hygiene, xform::ChangeLt}; 2 | 3 | /// Returns a vector of ids representing the function arguments. 4 | /// Prefers to reuse the names given by the user, if possible. 5 | pub fn input_ids(hygiene: &Hygiene, sig: &syn::Signature, skip: usize) -> Vec { 6 | sig.inputs 7 | .iter() 8 | .skip(skip) 9 | .zip(0..) 10 | .map(|(input, index)| { 11 | if let syn::FnArg::Typed(typed) = input { 12 | if let syn::Pat::Ident(ident) = &*typed.pat { 13 | return ident.ident.clone(); 14 | } 15 | } 16 | 17 | hygiene.ident(&format!("input{}", index)) 18 | }) 19 | .collect() 20 | } 21 | 22 | pub fn input_tys(sig: &syn::Signature, skip: usize) -> syn::Result> { 23 | sig.inputs 24 | .iter() 25 | .skip(skip) 26 | .map(|input| { 27 | if let syn::FnArg::Typed(typed) = input { 28 | Ok(&*typed.ty) 29 | } else { 30 | Err(syn::Error::new_spanned(input, "unexpected receiver")) 31 | } 32 | }) 33 | .collect() 34 | } 35 | 36 | pub fn output_ty(db_lt: Option<&syn::Lifetime>, sig: &syn::Signature) -> syn::Result { 37 | match &sig.output { 38 | syn::ReturnType::Default => Ok(parse_quote!(())), 39 | syn::ReturnType::Type(_, ty) => match db_lt { 40 | Some(db_lt) => Ok(ChangeLt::elided_to(db_lt).in_type(ty)), 41 | None => Ok(syn::Type::clone(ty)), 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /components/salsa-macros/src/hygiene.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use quote::ToTokens; 4 | 5 | pub struct Hygiene { 6 | user_tokens: HashSet, 7 | } 8 | 9 | impl Hygiene { 10 | pub fn from1(tokens: &proc_macro::TokenStream) -> Self { 11 | let mut user_tokens = HashSet::new(); 12 | push_idents1(tokens.clone(), &mut user_tokens); 13 | Self { user_tokens } 14 | } 15 | 16 | pub fn from2(tokens: &impl ToTokens) -> Self { 17 | let mut user_tokens = HashSet::new(); 18 | push_idents2(tokens.to_token_stream(), &mut user_tokens); 19 | Self { user_tokens } 20 | } 21 | } 22 | 23 | fn push_idents1(input: proc_macro::TokenStream, user_tokens: &mut HashSet) { 24 | input.into_iter().for_each(|token| match token { 25 | proc_macro::TokenTree::Group(g) => { 26 | push_idents1(g.stream(), user_tokens); 27 | } 28 | proc_macro::TokenTree::Ident(ident) => { 29 | user_tokens.insert(ident.to_string()); 30 | } 31 | proc_macro::TokenTree::Punct(_) => (), 32 | proc_macro::TokenTree::Literal(_) => (), 33 | }) 34 | } 35 | 36 | fn push_idents2(input: proc_macro2::TokenStream, user_tokens: &mut HashSet) { 37 | input.into_iter().for_each(|token| match token { 38 | proc_macro2::TokenTree::Group(g) => { 39 | push_idents2(g.stream(), user_tokens); 40 | } 41 | proc_macro2::TokenTree::Ident(ident) => { 42 | user_tokens.insert(ident.to_string()); 43 | } 44 | proc_macro2::TokenTree::Punct(_) => (), 45 | proc_macro2::TokenTree::Literal(_) => (), 46 | }) 47 | } 48 | 49 | impl Hygiene { 50 | /// Generates an identifier similar to `text` but 51 | /// distinct from any identifiers that appear in the user's 52 | /// code. 53 | pub(crate) fn ident(&self, text: &str) -> syn::Ident { 54 | // Make the default be `foo_` rather than `foo` -- this helps detect 55 | // cases where people wrote `foo` instead of `#foo` or `$foo` in the generated code. 56 | let mut buffer = format!("{}_", text); 57 | 58 | while self.user_tokens.contains(&buffer) { 59 | buffer.push('_'); 60 | } 61 | 62 | syn::Ident::new(&buffer, proc_macro2::Span::call_site()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /components/salsa-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides salsa's macros and attributes. 2 | 3 | #![recursion_limit = "256"] 4 | 5 | extern crate proc_macro; 6 | extern crate proc_macro2; 7 | #[macro_use] 8 | extern crate quote; 9 | 10 | use proc_macro::TokenStream; 11 | 12 | macro_rules! parse_quote { 13 | ($($inp:tt)*) => { 14 | { 15 | let tt = quote!{$($inp)*}; 16 | syn::parse2(tt.clone()).unwrap_or_else(|err| { 17 | panic!("failed to parse `{}` at {}:{}:{}: {}", tt, file!(), line!(), column!(), err) 18 | }) 19 | } 20 | } 21 | } 22 | 23 | mod accumulator; 24 | mod db; 25 | mod db_lifetime; 26 | mod debug; 27 | mod fn_util; 28 | mod hygiene; 29 | mod input; 30 | mod interned; 31 | mod options; 32 | mod salsa_struct; 33 | mod tracked; 34 | mod tracked_fn; 35 | mod tracked_impl; 36 | mod tracked_struct; 37 | mod update; 38 | mod xform; 39 | 40 | #[proc_macro_attribute] 41 | pub fn accumulator(args: TokenStream, input: TokenStream) -> TokenStream { 42 | accumulator::accumulator(args, input) 43 | } 44 | 45 | #[proc_macro_attribute] 46 | pub fn db(args: TokenStream, input: TokenStream) -> TokenStream { 47 | db::db(args, input) 48 | } 49 | 50 | #[proc_macro_attribute] 51 | pub fn interned(args: TokenStream, input: TokenStream) -> TokenStream { 52 | interned::interned(args, input) 53 | } 54 | 55 | #[proc_macro_attribute] 56 | pub fn input(args: TokenStream, input: TokenStream) -> TokenStream { 57 | input::input(args, input) 58 | } 59 | 60 | #[proc_macro_attribute] 61 | pub fn tracked(args: TokenStream, input: TokenStream) -> TokenStream { 62 | tracked::tracked(args, input) 63 | } 64 | 65 | #[proc_macro_derive(Update)] 66 | pub fn update(input: TokenStream) -> TokenStream { 67 | let item = syn::parse_macro_input!(input as syn::DeriveInput); 68 | match update::update_derive(item) { 69 | Ok(tokens) => tokens.into(), 70 | Err(err) => err.to_compile_error().into(), 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /components/salsa-macros/src/tracked.rs: -------------------------------------------------------------------------------- 1 | use syn::{spanned::Spanned, Item}; 2 | 3 | pub(crate) fn tracked( 4 | args: proc_macro::TokenStream, 5 | input: proc_macro::TokenStream, 6 | ) -> proc_macro::TokenStream { 7 | let item = syn::parse_macro_input!(input as Item); 8 | let res = match item { 9 | syn::Item::Struct(item) => crate::tracked_struct::tracked_struct(args, item), 10 | syn::Item::Fn(item) => crate::tracked_fn::tracked_fn(args, item), 11 | syn::Item::Impl(item) => crate::tracked_impl::tracked_impl(args, item), 12 | _ => Err(syn::Error::new( 13 | item.span(), 14 | "tracked can only be applied to structs, functions, and impls", 15 | )), 16 | }; 17 | match res { 18 | Ok(s) => s.into(), 19 | Err(err) => err.into_compile_error().into(), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/salsa-macros/src/xform.rs: -------------------------------------------------------------------------------- 1 | use syn::visit_mut::VisitMut; 2 | 3 | pub(crate) struct ChangeLt<'a> { 4 | from: Option<&'a str>, 5 | to: String, 6 | } 7 | 8 | impl<'a> ChangeLt<'a> { 9 | pub fn elided_to(db_lt: &syn::Lifetime) -> Self { 10 | ChangeLt { 11 | from: Some("_"), 12 | to: db_lt.ident.to_string(), 13 | } 14 | } 15 | pub fn in_type(mut self, ty: &syn::Type) -> syn::Type { 16 | let mut ty = ty.clone(); 17 | self.visit_type_mut(&mut ty); 18 | ty 19 | } 20 | } 21 | 22 | impl syn::visit_mut::VisitMut for ChangeLt<'_> { 23 | fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) { 24 | if self.from.map(|f| i.ident == f).unwrap_or(true) { 25 | i.ident = syn::Ident::new(&self.to, i.ident.span()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/calc/compile.rs: -------------------------------------------------------------------------------- 1 | use crate::{ir::SourceProgram, parser::parse_statements, type_check::type_check_program}; 2 | 3 | #[salsa::tracked] 4 | pub fn compile(db: &dyn crate::Db, source_program: SourceProgram) { 5 | let program = parse_statements(db, source_program); 6 | type_check_program(db, program); 7 | } 8 | -------------------------------------------------------------------------------- /examples/calc/db.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | // ANCHOR: db_struct 4 | #[salsa::db] 5 | #[derive(Default)] 6 | pub struct CalcDatabaseImpl { 7 | storage: salsa::Storage, 8 | 9 | // The logs are only used for testing and demonstrating reuse: 10 | logs: Arc>>>, 11 | } 12 | // ANCHOR_END: db_struct 13 | 14 | impl CalcDatabaseImpl { 15 | /// Enable logging of each salsa event. 16 | #[cfg(test)] 17 | pub fn enable_logging(&self) { 18 | let mut logs = self.logs.lock().unwrap(); 19 | if logs.is_none() { 20 | *logs = Some(vec![]); 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | pub fn take_logs(&self) -> Vec { 26 | let mut logs = self.logs.lock().unwrap(); 27 | if let Some(logs) = &mut *logs { 28 | std::mem::take(logs) 29 | } else { 30 | vec![] 31 | } 32 | } 33 | } 34 | 35 | // ANCHOR: db_impl 36 | #[salsa::db] 37 | impl salsa::Database for CalcDatabaseImpl { 38 | fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) { 39 | let event = event(); 40 | eprintln!("Event: {event:?}"); 41 | // Log interesting events, if logging is enabled 42 | if let Some(logs) = &mut *self.logs.lock().unwrap() { 43 | // only log interesting events 44 | if let salsa::EventKind::WillExecute { .. } = event.kind { 45 | logs.push(format!("Event: {event:?}")); 46 | } 47 | } 48 | } 49 | } 50 | // ANCHOR_END: db_impl 51 | -------------------------------------------------------------------------------- /examples/calc/main.rs: -------------------------------------------------------------------------------- 1 | use db::CalcDatabaseImpl; 2 | use ir::{Diagnostic, SourceProgram}; 3 | use salsa::Database as Db; 4 | 5 | mod compile; 6 | mod db; 7 | mod ir; 8 | mod parser; 9 | mod type_check; 10 | 11 | pub fn main() { 12 | let db: CalcDatabaseImpl = Default::default(); 13 | let source_program = SourceProgram::new(&db, String::new()); 14 | compile::compile(&db, source_program); 15 | let diagnostics = compile::compile::accumulated::(&db, source_program); 16 | eprintln!("{diagnostics:?}"); 17 | } 18 | -------------------------------------------------------------------------------- /examples/lazy-input/inputs/a: -------------------------------------------------------------------------------- 1 | 2 2 | ./aa 3 | -------------------------------------------------------------------------------- /examples/lazy-input/inputs/aa: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /examples/lazy-input/inputs/b: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /examples/lazy-input/inputs/start: -------------------------------------------------------------------------------- 1 | 1 2 | ./a 3 | ./b 4 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | test: 2 | cargo test --workspace --all-features --all-targets --no-fail-fast 3 | 4 | miri: 5 | cargo +nightly miri test --no-fail-fast --all-features 6 | 7 | all: test miri -------------------------------------------------------------------------------- /src/array.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | #[derive(Copy, Clone, Debug)] 5 | pub struct Array { 6 | data: [T; N], 7 | } 8 | 9 | impl Array { 10 | pub fn new(data: [T; N]) -> Self { 11 | Self { data } 12 | } 13 | } 14 | 15 | impl Deref for Array { 16 | type Target = [T]; 17 | 18 | fn deref(&self) -> &Self::Target { 19 | &self.data 20 | } 21 | } 22 | 23 | impl DerefMut for Array { 24 | fn deref_mut(&mut self) -> &mut Self::Target { 25 | &mut self.data 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/cancelled.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | panic::{self, UnwindSafe}, 4 | }; 5 | 6 | /// A panic payload indicating that execution of a salsa query was cancelled. 7 | /// 8 | /// This can occur for a few reasons: 9 | /// * 10 | /// * 11 | /// * 12 | #[derive(Debug)] 13 | #[non_exhaustive] 14 | pub enum Cancelled { 15 | /// The query was operating on revision R, but there is a pending write to move to revision R+1. 16 | #[non_exhaustive] 17 | PendingWrite, 18 | 19 | /// The query was blocked on another thread, and that thread panicked. 20 | #[non_exhaustive] 21 | PropagatedPanic, 22 | } 23 | 24 | impl Cancelled { 25 | pub(crate) fn throw(self) -> ! { 26 | // We use resume and not panic here to avoid running the panic 27 | // hook (that is, to avoid collecting and printing backtrace). 28 | std::panic::resume_unwind(Box::new(self)); 29 | } 30 | 31 | /// Runs `f`, and catches any salsa cancellation. 32 | pub fn catch(f: F) -> Result 33 | where 34 | F: FnOnce() -> T + UnwindSafe, 35 | { 36 | match panic::catch_unwind(f) { 37 | Ok(t) => Ok(t), 38 | Err(payload) => match payload.downcast() { 39 | Ok(cancelled) => Err(*cancelled), 40 | Err(payload) => panic::resume_unwind(payload), 41 | }, 42 | } 43 | } 44 | } 45 | 46 | impl std::fmt::Display for Cancelled { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | let why = match self { 49 | Cancelled::PendingWrite => "pending write", 50 | Cancelled::PropagatedPanic => "propagated panic", 51 | }; 52 | f.write_str("cancelled because of ")?; 53 | f.write_str(why) 54 | } 55 | } 56 | 57 | impl std::error::Error for Cancelled {} 58 | -------------------------------------------------------------------------------- /src/database_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::{self as salsa, Database, Event, Storage}; 2 | 3 | #[salsa::db] 4 | /// Default database implementation that you can use if you don't 5 | /// require any custom user data. 6 | #[derive(Default)] 7 | pub struct DatabaseImpl { 8 | storage: Storage, 9 | } 10 | 11 | impl DatabaseImpl { 12 | /// Create a new database; equivalent to `Self::default`. 13 | pub fn new() -> Self { 14 | Self::default() 15 | } 16 | } 17 | 18 | #[salsa::db] 19 | impl Database for DatabaseImpl { 20 | /// Default behavior: tracing debug log the event. 21 | fn salsa_event(&self, event: &dyn Fn() -> Event) { 22 | tracing::debug!("salsa_event({:?})", event()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/durability.rs: -------------------------------------------------------------------------------- 1 | /// Describes how likely a value is to change—how "durable" it is. 2 | /// 3 | /// By default, inputs have `Durability::LOW` and interned values have 4 | /// `Durability::HIGH`. But inputs can be explicitly set with other 5 | /// durabilities. 6 | /// 7 | /// We use durabilities to optimize the work of "revalidating" a query 8 | /// after some input has changed. Ordinarily, in a new revision, 9 | /// queries have to trace all their inputs back to the base inputs to 10 | /// determine if any of those inputs have changed. But if we know that 11 | /// the only changes were to inputs of low durability (the common 12 | /// case), and we know that the query only used inputs of medium 13 | /// durability or higher, then we can skip that enumeration. 14 | /// 15 | /// Typically, one assigns low durabilites to inputs that the user is 16 | /// frequently editing. Medium or high durabilities are used for 17 | /// configuration, the source from library crates, or other things 18 | /// that are unlikely to be edited. 19 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 20 | pub struct Durability(u8); 21 | 22 | impl Durability { 23 | /// Low durability: things that change frequently. 24 | /// 25 | /// Example: part of the crate being edited 26 | pub const LOW: Durability = Durability(0); 27 | 28 | /// Medium durability: things that change sometimes, but rarely. 29 | /// 30 | /// Example: a Cargo.toml file 31 | pub const MEDIUM: Durability = Durability(1); 32 | 33 | /// High durability: things that are not expected to change under 34 | /// common usage. 35 | /// 36 | /// Example: the standard library or something from crates.io 37 | pub const HIGH: Durability = Durability(2); 38 | 39 | /// The maximum possible durability; equivalent to HIGH but 40 | /// "conceptually" distinct (i.e., if we add more durability 41 | /// levels, this could change). 42 | pub(crate) const MAX: Durability = Self::HIGH; 43 | 44 | /// Number of durability levels. 45 | pub(crate) const LEN: usize = 3; 46 | 47 | pub(crate) fn index(self) -> usize { 48 | self.0 as usize 49 | } 50 | } 51 | 52 | impl Default for Durability { 53 | fn default() -> Self { 54 | Durability::LOW 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/function/accumulated.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | accumulator, hash::FxHashSet, zalsa::ZalsaDatabase, AsDynDatabase, DatabaseKeyIndex, Id, 3 | }; 4 | 5 | use super::{Configuration, IngredientImpl}; 6 | 7 | impl IngredientImpl 8 | where 9 | C: Configuration, 10 | { 11 | /// Helper used by `accumulate` functions. Computes the results accumulated by `database_key_index` 12 | /// and its inputs. 13 | pub fn accumulated_by(&self, db: &C::DbView, key: Id) -> Vec 14 | where 15 | A: accumulator::Accumulator, 16 | { 17 | let zalsa = db.zalsa(); 18 | let zalsa_local = db.zalsa_local(); 19 | let current_revision = zalsa.current_revision(); 20 | 21 | let Some(accumulator) = >::from_db(db) else { 22 | return vec![]; 23 | }; 24 | let mut output = vec![]; 25 | 26 | // First ensure the result is up to date 27 | self.fetch(db, key); 28 | 29 | let db = db.as_dyn_database(); 30 | let db_key = self.database_key_index(key); 31 | let mut visited: FxHashSet = FxHashSet::default(); 32 | let mut stack: Vec = vec![db_key]; 33 | 34 | while let Some(k) = stack.pop() { 35 | if visited.insert(k) { 36 | accumulator.produced_by(current_revision, zalsa_local, k, &mut output); 37 | 38 | let origin = zalsa 39 | .lookup_ingredient(k.ingredient_index) 40 | .origin(db, k.key_index); 41 | let inputs = origin.iter().flat_map(|origin| origin.inputs()); 42 | // Careful: we want to push in execution order, so reverse order to 43 | // ensure the first child that was executed will be the first child popped 44 | // from the stack. 45 | stack.extend( 46 | inputs 47 | .flat_map(|input| TryInto::::try_into(input).into_iter()) 48 | .rev(), 49 | ); 50 | } 51 | } 52 | 53 | output 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/function/backdate.rs: -------------------------------------------------------------------------------- 1 | use crate::zalsa_local::QueryRevisions; 2 | 3 | use super::{memo::Memo, Configuration, IngredientImpl}; 4 | 5 | impl IngredientImpl 6 | where 7 | C: Configuration, 8 | { 9 | /// If the value/durability of this memo is equal to what is found in `revisions`/`value`, 10 | /// then updates `revisions.changed_at` to match `self.revisions.changed_at`. This is invoked 11 | /// on an old memo when a new memo has been produced to check whether there have been changed. 12 | pub(super) fn backdate_if_appropriate( 13 | &self, 14 | old_memo: &Memo>, 15 | revisions: &mut QueryRevisions, 16 | value: &C::Output<'_>, 17 | ) { 18 | if let Some(old_value) = &old_memo.value { 19 | // Careful: if the value became less durable than it 20 | // used to be, that is a "breaking change" that our 21 | // consumers must be aware of. Becoming *more* durable 22 | // is not. See the test `constant_to_non_constant`. 23 | if revisions.durability >= old_memo.revisions.durability 24 | && C::should_backdate_value(old_value, value) 25 | { 26 | tracing::debug!( 27 | "value is equal, back-dating to {:?}", 28 | old_memo.revisions.changed_at, 29 | ); 30 | 31 | assert!(old_memo.revisions.changed_at <= revisions.changed_at); 32 | revisions.changed_at = old_memo.revisions.changed_at; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/function/delete.rs: -------------------------------------------------------------------------------- 1 | use crossbeam::queue::SegQueue; 2 | 3 | use super::{memo::ArcMemo, Configuration}; 4 | 5 | /// Stores the list of memos that have been deleted so they can be freed 6 | /// once the next revision starts. See the comment on the field 7 | /// `deleted_entries` of [`FunctionIngredient`][] for more details. 8 | pub(super) struct DeletedEntries { 9 | seg_queue: SegQueue>, 10 | } 11 | 12 | impl Default for DeletedEntries { 13 | fn default() -> Self { 14 | Self { 15 | seg_queue: Default::default(), 16 | } 17 | } 18 | } 19 | 20 | impl DeletedEntries { 21 | pub(super) fn push<'db>(&'db self, memo: ArcMemo<'db, C>) { 22 | let memo = unsafe { std::mem::transmute::, ArcMemo<'static, C>>(memo) }; 23 | self.seg_queue.push(memo); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/function/diff_outputs.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | hash::FxHashSet, key::DependencyIndex, zalsa_local::QueryRevisions, AsDynDatabase as _, 3 | DatabaseKeyIndex, Event, EventKind, 4 | }; 5 | 6 | use super::{memo::Memo, Configuration, IngredientImpl}; 7 | 8 | impl IngredientImpl 9 | where 10 | C: Configuration, 11 | { 12 | /// Compute the old and new outputs and invoke the `clear_stale_output` callback 13 | /// for each output that was generated before but is not generated now. 14 | pub(super) fn diff_outputs( 15 | &self, 16 | db: &C::DbView, 17 | key: DatabaseKeyIndex, 18 | old_memo: &Memo>, 19 | revisions: &QueryRevisions, 20 | ) { 21 | // Iterate over the outputs of the `old_memo` and put them into a hashset 22 | let mut old_outputs = FxHashSet::default(); 23 | old_memo.revisions.origin.outputs().for_each(|i| { 24 | old_outputs.insert(i); 25 | }); 26 | 27 | // Iterate over the outputs of the current query 28 | // and remove elements from `old_outputs` when we find them 29 | for new_output in revisions.origin.outputs() { 30 | if old_outputs.contains(&new_output) { 31 | old_outputs.remove(&new_output); 32 | } 33 | } 34 | 35 | for old_output in old_outputs { 36 | Self::report_stale_output(db, key, old_output); 37 | } 38 | } 39 | 40 | fn report_stale_output(db: &C::DbView, key: DatabaseKeyIndex, output: DependencyIndex) { 41 | let db = db.as_dyn_database(); 42 | 43 | db.salsa_event(&|| Event { 44 | thread_id: std::thread::current().id(), 45 | kind: EventKind::WillDiscardStaleOutput { 46 | execute_key: key, 47 | output_key: output, 48 | }, 49 | }); 50 | 51 | output.remove_stale_output(db, key); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/function/inputs.rs: -------------------------------------------------------------------------------- 1 | use crate::{zalsa::Zalsa, zalsa_local::QueryOrigin, Id}; 2 | 3 | use super::{Configuration, IngredientImpl}; 4 | 5 | impl IngredientImpl 6 | where 7 | C: Configuration, 8 | { 9 | pub(super) fn origin(&self, zalsa: &Zalsa, key: Id) -> Option { 10 | self.get_memo_from_table_for(zalsa, key) 11 | .map(|m| m.revisions.origin.clone()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/function/lru.rs: -------------------------------------------------------------------------------- 1 | use crate::{hash::FxLinkedHashSet, Id}; 2 | 3 | use crossbeam::atomic::AtomicCell; 4 | use parking_lot::Mutex; 5 | 6 | #[derive(Default)] 7 | pub(super) struct Lru { 8 | capacity: AtomicCell, 9 | set: Mutex>, 10 | } 11 | 12 | impl Lru { 13 | pub(super) fn record_use(&self, index: Id) -> Option { 14 | let capacity = self.capacity.load(); 15 | 16 | if capacity == 0 { 17 | // LRU is disabled 18 | return None; 19 | } 20 | 21 | let mut set = self.set.lock(); 22 | set.insert(index); 23 | if set.len() > capacity { 24 | return set.pop_front(); 25 | } 26 | 27 | None 28 | } 29 | 30 | pub(super) fn set_capacity(&self, capacity: usize) { 31 | self.capacity.store(capacity); 32 | 33 | if capacity == 0 { 34 | let mut set = self.set.lock(); 35 | *set = FxLinkedHashSet::default(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/hash.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{BuildHasher, Hash}; 2 | 3 | pub(crate) type FxHasher = std::hash::BuildHasherDefault; 4 | pub(crate) type FxIndexSet = indexmap::IndexSet; 5 | pub(crate) type FxIndexMap = indexmap::IndexMap; 6 | pub(crate) type FxDashMap = dashmap::DashMap; 7 | pub(crate) type FxLinkedHashSet = hashlink::LinkedHashSet; 8 | pub(crate) type FxHashSet = std::collections::HashSet; 9 | 10 | pub(crate) fn hash(t: &T) -> u64 { 11 | FxHasher::default().hash_one(t) 12 | } 13 | -------------------------------------------------------------------------------- /src/id.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::hash::Hash; 3 | use std::num::NonZeroU32; 4 | 5 | /// The `Id` of a salsa struct in the database [`Table`](`crate::table::Table`). 6 | /// 7 | /// The higher-order bits of an `Id` identify a [`Page`](`crate::table::Page`) 8 | /// and the low-order bits identify a slot within the page. 9 | /// 10 | /// An Id is a newtype'd u32 ranging from `0..Id::MAX_U32`. 11 | /// The maximum range is smaller than a standard u32 to leave 12 | /// room for niches; currently there is only one niche, so that 13 | /// `Option` is the same size as an `Id`. 14 | /// 15 | /// As an end-user of `Salsa` you will not use `Id` directly, 16 | /// it is wrapped in new types. 17 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 18 | pub struct Id { 19 | value: NonZeroU32, 20 | } 21 | 22 | impl Id { 23 | pub const MAX_U32: u32 = u32::MAX - 0xFF; 24 | pub const MAX_USIZE: usize = Self::MAX_U32 as usize; 25 | 26 | /// Create a `salsa::Id` from a u32 value. This value should 27 | /// be less than [`Self::MAX_U32`]. 28 | /// 29 | /// In general, you should not need to create salsa ids yourself, 30 | /// but it can be useful if you are using the type as a general 31 | /// purpose "identifier" internally. 32 | #[track_caller] 33 | pub(crate) const fn from_u32(x: u32) -> Self { 34 | Id { 35 | value: match NonZeroU32::new(x + 1) { 36 | Some(v) => v, 37 | None => panic!("given value is too large to be a `salsa::Id`"), 38 | }, 39 | } 40 | } 41 | 42 | pub const fn as_u32(self) -> u32 { 43 | self.value.get() - 1 44 | } 45 | } 46 | 47 | impl Debug for Id { 48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 49 | write!(f, "Id({:x})", self.as_u32()) 50 | } 51 | } 52 | 53 | /// Internal salsa trait for types that can be represented as a salsa id. 54 | pub trait AsId: Sized { 55 | fn as_id(&self) -> Id; 56 | } 57 | 58 | /// Internal Salsa trait for types that are just a newtype'd [`Id`][]. 59 | pub trait FromId: AsId + Copy + Eq + Hash + Debug { 60 | fn from_id(id: Id) -> Self; 61 | } 62 | 63 | impl AsId for Id { 64 | fn as_id(&self) -> Id { 65 | *self 66 | } 67 | } 68 | 69 | impl FromId for Id { 70 | fn from_id(id: Id) -> Self { 71 | id 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/input/setter.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::input::{Configuration, IngredientImpl}; 4 | use crate::{Durability, Runtime}; 5 | 6 | /// Setter for a field of an input. 7 | pub trait Setter: Sized { 8 | type FieldTy; 9 | fn with_durability(self, durability: Durability) -> Self; 10 | fn to(self, value: Self::FieldTy) -> Self::FieldTy; 11 | } 12 | 13 | #[must_use] 14 | pub struct SetterImpl<'setter, C: Configuration, S, F> { 15 | runtime: &'setter mut Runtime, 16 | id: C::Struct, 17 | ingredient: &'setter mut IngredientImpl, 18 | durability: Option, 19 | field_index: usize, 20 | setter: S, 21 | phantom: PhantomData, 22 | } 23 | 24 | impl<'setter, C, S, F> SetterImpl<'setter, C, S, F> 25 | where 26 | C: Configuration, 27 | S: FnOnce(&mut C::Fields, F) -> F, 28 | { 29 | pub fn new( 30 | runtime: &'setter mut Runtime, 31 | id: C::Struct, 32 | field_index: usize, 33 | ingredient: &'setter mut IngredientImpl, 34 | setter: S, 35 | ) -> Self { 36 | SetterImpl { 37 | runtime, 38 | id, 39 | field_index, 40 | ingredient, 41 | durability: None, 42 | setter, 43 | phantom: PhantomData, 44 | } 45 | } 46 | } 47 | 48 | impl<'setter, C, S, F> Setter for SetterImpl<'setter, C, S, F> 49 | where 50 | C: Configuration, 51 | S: FnOnce(&mut C::Fields, F) -> F, 52 | { 53 | type FieldTy = F; 54 | 55 | fn with_durability(mut self, durability: Durability) -> Self { 56 | self.durability = Some(durability); 57 | self 58 | } 59 | 60 | fn to(self, value: F) -> F { 61 | let Self { 62 | runtime, 63 | id, 64 | ingredient, 65 | durability, 66 | field_index, 67 | setter, 68 | phantom: _, 69 | } = self; 70 | 71 | ingredient.set_field(runtime, id, field_index, durability, |tuple| { 72 | setter(tuple, value) 73 | }) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/nonce.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::PhantomData, num::NonZeroU32, sync::atomic::AtomicU32}; 2 | 3 | /// A type to generate nonces. Store it in a static and each nonce it produces will be unique from other nonces. 4 | /// The type parameter `T` just serves to distinguish different kinds of nonces. 5 | pub(crate) struct NonceGenerator { 6 | value: AtomicU32, 7 | phantom: PhantomData, 8 | } 9 | 10 | /// A "nonce" is a value that gets created exactly once. 11 | /// We use it to mark the database storage so we can be sure we're seeing the same database. 12 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 13 | pub struct Nonce(NonZeroU32, PhantomData); 14 | 15 | impl NonceGenerator { 16 | pub(crate) const fn new() -> Self { 17 | Self { 18 | // start at 1 so we can detect rollover more easily 19 | value: AtomicU32::new(1), 20 | phantom: PhantomData, 21 | } 22 | } 23 | 24 | pub(crate) fn nonce(&self) -> Nonce { 25 | let value = self 26 | .value 27 | .fetch_add(1, std::sync::atomic::Ordering::Relaxed); 28 | 29 | assert!(value != 0, "nonce rolled over"); 30 | 31 | Nonce(NonZeroU32::new(value).unwrap(), self.phantom) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/revision.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroUsize; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | /// Value of the initial revision, as a u64. We don't use 0 5 | /// because we want to use a `NonZeroUsize`. 6 | const START: usize = 1; 7 | 8 | /// A unique identifier for the current version of the database. 9 | /// 10 | /// Each time an input is changed, the revision number is incremented. 11 | /// `Revision` is used internally to track which values may need to be 12 | /// recomputed, but is not something you should have to interact with 13 | /// directly as a user of salsa. 14 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 15 | pub struct Revision { 16 | generation: NonZeroUsize, 17 | } 18 | 19 | impl Revision { 20 | pub(crate) fn start() -> Self { 21 | Self::from(START) 22 | } 23 | 24 | pub(crate) fn from(g: usize) -> Self { 25 | Self { 26 | generation: NonZeroUsize::new(g).unwrap(), 27 | } 28 | } 29 | 30 | pub(crate) fn next(self) -> Revision { 31 | Self::from(self.generation.get() + 1) 32 | } 33 | 34 | fn as_usize(self) -> usize { 35 | self.generation.get() 36 | } 37 | } 38 | 39 | impl std::fmt::Debug for Revision { 40 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | write!(fmt, "R{}", self.generation) 42 | } 43 | } 44 | 45 | #[derive(Debug)] 46 | pub(crate) struct AtomicRevision { 47 | data: AtomicUsize, 48 | } 49 | 50 | impl AtomicRevision { 51 | pub(crate) fn start() -> Self { 52 | Self { 53 | data: AtomicUsize::new(START), 54 | } 55 | } 56 | 57 | pub(crate) fn load(&self) -> Revision { 58 | Revision::from(self.data.load(Ordering::SeqCst)) 59 | } 60 | 61 | pub(crate) fn store(&self, r: Revision) { 62 | self.data.store(r.as_usize(), Ordering::SeqCst); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/salsa_struct.rs: -------------------------------------------------------------------------------- 1 | pub trait SalsaStructInDb {} 2 | -------------------------------------------------------------------------------- /src/table/util.rs: -------------------------------------------------------------------------------- 1 | pub(super) fn ensure_vec_len(v: &mut Vec, len: usize) { 2 | if v.len() < len { 3 | v.resize_with(len, T::default); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/accumulate-chain.rs: -------------------------------------------------------------------------------- 1 | //! Test that when having nested tracked functions 2 | //! we don't drop any values when accumulating. 3 | 4 | mod common; 5 | 6 | use expect_test::expect; 7 | use salsa::{Accumulator, Database, DatabaseImpl}; 8 | use test_log::test; 9 | 10 | #[salsa::accumulator] 11 | struct Log(#[allow(dead_code)] String); 12 | 13 | #[salsa::tracked] 14 | fn push_logs(db: &dyn Database) { 15 | push_a_logs(db); 16 | } 17 | 18 | #[salsa::tracked] 19 | fn push_a_logs(db: &dyn Database) { 20 | Log("log a".to_string()).accumulate(db); 21 | push_b_logs(db); 22 | } 23 | 24 | #[salsa::tracked] 25 | fn push_b_logs(db: &dyn Database) { 26 | // No logs 27 | push_c_logs(db); 28 | } 29 | 30 | #[salsa::tracked] 31 | fn push_c_logs(db: &dyn Database) { 32 | // No logs 33 | push_d_logs(db); 34 | } 35 | 36 | #[salsa::tracked] 37 | fn push_d_logs(db: &dyn Database) { 38 | Log("log d".to_string()).accumulate(db); 39 | } 40 | 41 | #[test] 42 | fn accumulate_chain() { 43 | DatabaseImpl::new().attach(|db| { 44 | let logs = push_logs::accumulated::(db); 45 | // Check that we get all the logs. 46 | expect![[r#" 47 | [ 48 | Log( 49 | "log a", 50 | ), 51 | Log( 52 | "log d", 53 | ), 54 | ]"#]] 55 | .assert_eq(&format!("{:#?}", logs)); 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /tests/accumulate-custom-clone.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use expect_test::expect; 4 | use salsa::{Accumulator, Database}; 5 | use test_log::test; 6 | 7 | #[salsa::input] 8 | struct MyInput { 9 | count: u32, 10 | } 11 | 12 | #[salsa::accumulator(no_clone)] 13 | struct Log(String); 14 | 15 | impl Clone for Log { 16 | fn clone(&self) -> Self { 17 | Self(format!("{}.clone()", self.0)) 18 | } 19 | } 20 | 21 | #[salsa::tracked] 22 | fn push_logs(db: &dyn salsa::Database, input: MyInput) { 23 | for i in 0..input.count(db) { 24 | Log(format!("#{i}")).accumulate(db); 25 | } 26 | } 27 | 28 | #[test] 29 | fn accumulate_custom_clone() { 30 | salsa::DatabaseImpl::new().attach(|db| { 31 | let input = MyInput::new(db, 2); 32 | let logs = push_logs::accumulated::(db, input); 33 | expect![[r##" 34 | [ 35 | Log( 36 | "#0.clone()", 37 | ), 38 | Log( 39 | "#1.clone()", 40 | ), 41 | ] 42 | "##]] 43 | .assert_debug_eq(&logs); 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /tests/accumulate-custom-debug.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use expect_test::expect; 4 | use salsa::{Accumulator, Database}; 5 | use test_log::test; 6 | 7 | #[salsa::input] 8 | struct MyInput { 9 | count: u32, 10 | } 11 | 12 | #[salsa::accumulator(no_debug)] 13 | struct Log(String); 14 | 15 | impl std::fmt::Debug for Log { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | f.debug_tuple("CustomLog").field(&self.0).finish() 18 | } 19 | } 20 | 21 | #[salsa::tracked] 22 | fn push_logs(db: &dyn salsa::Database, input: MyInput) { 23 | for i in 0..input.count(db) { 24 | Log(format!("#{i}")).accumulate(db); 25 | } 26 | } 27 | 28 | #[test] 29 | fn accumulate_custom_debug() { 30 | salsa::DatabaseImpl::new().attach(|db| { 31 | let input = MyInput::new(db, 2); 32 | let logs = push_logs::accumulated::(db, input); 33 | expect![[r##" 34 | [ 35 | CustomLog( 36 | "#0", 37 | ), 38 | CustomLog( 39 | "#1", 40 | ), 41 | ] 42 | "##]] 43 | .assert_debug_eq(&logs); 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /tests/accumulate-dag.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use expect_test::expect; 4 | use salsa::{Accumulator, Database}; 5 | use test_log::test; 6 | 7 | #[salsa::input] 8 | struct MyInput { 9 | field_a: u32, 10 | field_b: u32, 11 | } 12 | 13 | #[salsa::accumulator] 14 | struct Log(#[allow(dead_code)] String); 15 | 16 | #[salsa::tracked] 17 | fn push_logs(db: &dyn Database, input: MyInput) { 18 | push_a_logs(db, input); 19 | push_b_logs(db, input); 20 | } 21 | 22 | #[salsa::tracked] 23 | fn push_a_logs(db: &dyn Database, input: MyInput) { 24 | let count = input.field_a(db); 25 | for i in 0..count { 26 | Log(format!("log_a({} of {})", i, count)).accumulate(db); 27 | } 28 | } 29 | 30 | #[salsa::tracked] 31 | fn push_b_logs(db: &dyn Database, input: MyInput) { 32 | // Note that b calls a 33 | push_a_logs(db, input); 34 | let count = input.field_b(db); 35 | for i in 0..count { 36 | Log(format!("log_b({} of {})", i, count)).accumulate(db); 37 | } 38 | } 39 | 40 | #[test] 41 | fn accumulate_a_called_twice() { 42 | salsa::DatabaseImpl::new().attach(|db| { 43 | let input = MyInput::new(db, 2, 3); 44 | let logs = push_logs::accumulated::(db, input); 45 | // Check that we don't see logs from `a` appearing twice in the input. 46 | expect![[r#" 47 | [ 48 | Log( 49 | "log_a(0 of 2)", 50 | ), 51 | Log( 52 | "log_a(1 of 2)", 53 | ), 54 | Log( 55 | "log_b(0 of 3)", 56 | ), 57 | Log( 58 | "log_b(1 of 3)", 59 | ), 60 | Log( 61 | "log_b(2 of 3)", 62 | ), 63 | ]"#]] 64 | .assert_eq(&format!("{:#?}", logs)); 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /tests/accumulate-execution-order.rs: -------------------------------------------------------------------------------- 1 | //! Demonstrates that accumulation is done in the order 2 | //! in which things were originally executed. 3 | 4 | mod common; 5 | 6 | use expect_test::expect; 7 | use salsa::{Accumulator, Database}; 8 | use test_log::test; 9 | 10 | #[salsa::accumulator] 11 | struct Log(#[allow(dead_code)] String); 12 | 13 | #[salsa::tracked] 14 | fn push_logs(db: &dyn Database) { 15 | push_a_logs(db); 16 | } 17 | 18 | #[salsa::tracked] 19 | fn push_a_logs(db: &dyn Database) { 20 | Log("log a".to_string()).accumulate(db); 21 | push_b_logs(db); 22 | push_c_logs(db); 23 | push_d_logs(db); 24 | } 25 | 26 | #[salsa::tracked] 27 | fn push_b_logs(db: &dyn Database) { 28 | Log("log b".to_string()).accumulate(db); 29 | push_d_logs(db); 30 | } 31 | 32 | #[salsa::tracked] 33 | fn push_c_logs(db: &dyn Database) { 34 | Log("log c".to_string()).accumulate(db); 35 | } 36 | 37 | #[salsa::tracked] 38 | fn push_d_logs(db: &dyn Database) { 39 | Log("log d".to_string()).accumulate(db); 40 | } 41 | 42 | #[test] 43 | fn accumulate_execution_order() { 44 | salsa::DatabaseImpl::new().attach(|db| { 45 | let logs = push_logs::accumulated::(db); 46 | // Check that we get logs in execution order 47 | expect![[r#" 48 | [ 49 | Log( 50 | "log a", 51 | ), 52 | Log( 53 | "log b", 54 | ), 55 | Log( 56 | "log d", 57 | ), 58 | Log( 59 | "log c", 60 | ), 61 | ]"#]] 62 | .assert_eq(&format!("{:#?}", logs)); 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /tests/accumulate-from-tracked-fn.rs: -------------------------------------------------------------------------------- 1 | //! Accumulate values from within a tracked function. 2 | //! Then mutate the values so that the tracked function re-executes. 3 | //! Check that we accumulate the appropriate, new values. 4 | 5 | use expect_test::expect; 6 | use salsa::{Accumulator, Setter}; 7 | use test_log::test; 8 | 9 | #[salsa::input] 10 | struct List { 11 | value: u32, 12 | next: Option, 13 | } 14 | 15 | #[salsa::accumulator] 16 | #[derive(Copy)] 17 | struct Integers(u32); 18 | 19 | #[salsa::tracked] 20 | fn compute(db: &dyn salsa::Database, input: List) { 21 | eprintln!( 22 | "{:?}(value={:?}, next={:?})", 23 | input, 24 | input.value(db), 25 | input.next(db) 26 | ); 27 | let result = if let Some(next) = input.next(db) { 28 | let next_integers = compute::accumulated::(db, next); 29 | eprintln!("{:?}", next_integers); 30 | let v = input.value(db) + next_integers.iter().map(|a| a.0).sum::(); 31 | eprintln!("input={:?} v={:?}", input.value(db), v); 32 | v 33 | } else { 34 | input.value(db) 35 | }; 36 | Integers(result).accumulate(db); 37 | eprintln!("pushed result {:?}", result); 38 | } 39 | 40 | #[test] 41 | fn test1() { 42 | let mut db = salsa::DatabaseImpl::new(); 43 | 44 | let l0 = List::new(&db, 1, None); 45 | let l1 = List::new(&db, 10, Some(l0)); 46 | 47 | compute(&db, l1); 48 | expect![[r#" 49 | [ 50 | Integers( 51 | 11, 52 | ), 53 | Integers( 54 | 1, 55 | ), 56 | ] 57 | "#]] 58 | .assert_debug_eq(&compute::accumulated::(&db, l1)); 59 | 60 | l0.set_value(&mut db).to(2); 61 | compute(&db, l1); 62 | expect![[r#" 63 | [ 64 | Integers( 65 | 12, 66 | ), 67 | Integers( 68 | 2, 69 | ), 70 | ] 71 | "#]] 72 | .assert_debug_eq(&compute::accumulated::(&db, l1)); 73 | } 74 | -------------------------------------------------------------------------------- /tests/accumulate-no-duplicates.rs: -------------------------------------------------------------------------------- 1 | //! Test that we don't get duplicate accumulated values 2 | 3 | mod common; 4 | 5 | use expect_test::expect; 6 | use salsa::{Accumulator, Database}; 7 | use test_log::test; 8 | 9 | // A(1) { 10 | // B 11 | // B 12 | // C { 13 | // D { 14 | // A(2) { 15 | // B 16 | // } 17 | // B 18 | // } 19 | // E 20 | // } 21 | // B 22 | // } 23 | 24 | #[salsa::accumulator] 25 | struct Log(#[allow(dead_code)] String); 26 | 27 | #[salsa::input] 28 | struct MyInput { 29 | n: u32, 30 | } 31 | 32 | #[salsa::tracked] 33 | fn push_logs(db: &dyn Database) { 34 | push_a_logs(db, MyInput::new(db, 1)); 35 | } 36 | 37 | #[salsa::tracked] 38 | fn push_a_logs(db: &dyn Database, input: MyInput) { 39 | Log("log a".to_string()).accumulate(db); 40 | if input.n(db) == 1 { 41 | push_b_logs(db); 42 | push_b_logs(db); 43 | push_c_logs(db); 44 | push_b_logs(db); 45 | } else { 46 | push_b_logs(db); 47 | } 48 | } 49 | 50 | #[salsa::tracked] 51 | fn push_b_logs(db: &dyn Database) { 52 | Log("log b".to_string()).accumulate(db); 53 | } 54 | 55 | #[salsa::tracked] 56 | fn push_c_logs(db: &dyn Database) { 57 | Log("log c".to_string()).accumulate(db); 58 | push_d_logs(db); 59 | push_e_logs(db); 60 | } 61 | 62 | // Note this isn't tracked 63 | fn push_d_logs(db: &dyn Database) { 64 | Log("log d".to_string()).accumulate(db); 65 | push_a_logs(db, MyInput::new(db, 2)); 66 | push_b_logs(db); 67 | } 68 | 69 | #[salsa::tracked] 70 | fn push_e_logs(db: &dyn Database) { 71 | Log("log e".to_string()).accumulate(db); 72 | } 73 | 74 | #[test] 75 | fn accumulate_no_duplicates() { 76 | salsa::DatabaseImpl::new().attach(|db| { 77 | let logs = push_logs::accumulated::(db); 78 | // Test that there aren't duplicate B logs. 79 | // Note that log A appears twice, because they both come 80 | // from different inputs. 81 | expect![[r#" 82 | [ 83 | Log( 84 | "log a", 85 | ), 86 | Log( 87 | "log b", 88 | ), 89 | Log( 90 | "log c", 91 | ), 92 | Log( 93 | "log d", 94 | ), 95 | Log( 96 | "log a", 97 | ), 98 | Log( 99 | "log e", 100 | ), 101 | ]"#]] 102 | .assert_eq(&format!("{:#?}", logs)); 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /tests/accumulate-reuse-workaround.rs: -------------------------------------------------------------------------------- 1 | //! Demonstrates the workaround of wrapping calls to 2 | //! `accumulated` in a tracked function to get better 3 | //! reuse. 4 | 5 | mod common; 6 | use common::{LogDatabase, LoggerDatabase}; 7 | 8 | use expect_test::expect; 9 | use salsa::{Accumulator, Setter}; 10 | use test_log::test; 11 | 12 | #[salsa::input] 13 | struct List { 14 | value: u32, 15 | next: Option, 16 | } 17 | 18 | #[salsa::accumulator] 19 | #[derive(Copy)] 20 | struct Integers(u32); 21 | 22 | #[salsa::tracked] 23 | fn compute(db: &dyn LogDatabase, input: List) -> u32 { 24 | db.push_log(format!("compute({:?})", input,)); 25 | 26 | // always pushes 0 27 | Integers(0).accumulate(db); 28 | 29 | let result = if let Some(next) = input.next(db) { 30 | let next_integers = accumulated(db, next); 31 | let v = input.value(db) + next_integers.iter().sum::(); 32 | v 33 | } else { 34 | input.value(db) 35 | }; 36 | 37 | // return value changes 38 | result 39 | } 40 | 41 | #[salsa::tracked(return_ref)] 42 | fn accumulated(db: &dyn LogDatabase, input: List) -> Vec { 43 | db.push_log(format!("accumulated({:?})", input)); 44 | compute::accumulated::(db, input) 45 | .into_iter() 46 | .map(|a| a.0) 47 | .collect() 48 | } 49 | 50 | #[test] 51 | fn test1() { 52 | let mut db = LoggerDatabase::default(); 53 | 54 | let l1 = List::new(&db, 1, None); 55 | let l2 = List::new(&db, 2, Some(l1)); 56 | 57 | assert_eq!(compute(&db, l2), 2); 58 | db.assert_logs(expect![[r#" 59 | [ 60 | "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 1, next: None }) })", 61 | "accumulated(List { [salsa id]: Id(0), value: 1, next: None })", 62 | "compute(List { [salsa id]: Id(0), value: 1, next: None })", 63 | ]"#]]); 64 | 65 | // When we mutate `l1`, we should re-execute `compute` for `l1`, 66 | // and we re-execute accumulated for `l1`, but we do NOT re-execute 67 | // `compute` for `l2`. 68 | l1.set_value(&mut db).to(2); 69 | assert_eq!(compute(&db, l2), 2); 70 | db.assert_logs(expect![[r#" 71 | [ 72 | "accumulated(List { [salsa id]: Id(0), value: 2, next: None })", 73 | "compute(List { [salsa id]: Id(0), value: 2, next: None })", 74 | ]"#]]); 75 | } 76 | -------------------------------------------------------------------------------- /tests/accumulate-reuse.rs: -------------------------------------------------------------------------------- 1 | //! Accumulator re-use test. 2 | //! 3 | //! Tests behavior when a query's only inputs 4 | //! are the accumulated values from another query. 5 | 6 | mod common; 7 | use common::{LogDatabase, LoggerDatabase}; 8 | 9 | use expect_test::expect; 10 | use salsa::{Accumulator, Setter}; 11 | use test_log::test; 12 | 13 | #[salsa::input] 14 | struct List { 15 | value: u32, 16 | next: Option, 17 | } 18 | 19 | #[salsa::accumulator] 20 | struct Integers(u32); 21 | 22 | #[salsa::tracked] 23 | fn compute(db: &dyn LogDatabase, input: List) -> u32 { 24 | db.push_log(format!("compute({:?})", input,)); 25 | 26 | // always pushes 0 27 | Integers(0).accumulate(db); 28 | 29 | let result = if let Some(next) = input.next(db) { 30 | let next_integers = compute::accumulated::(db, next); 31 | let v = input.value(db) + next_integers.iter().map(|i| i.0).sum::(); 32 | v 33 | } else { 34 | input.value(db) 35 | }; 36 | 37 | // return value changes 38 | result 39 | } 40 | 41 | #[test] 42 | fn test1() { 43 | let mut db = LoggerDatabase::default(); 44 | 45 | let l1 = List::new(&db, 1, None); 46 | let l2 = List::new(&db, 2, Some(l1)); 47 | 48 | assert_eq!(compute(&db, l2), 2); 49 | db.assert_logs(expect![[r#" 50 | [ 51 | "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 1, next: None }) })", 52 | "compute(List { [salsa id]: Id(0), value: 1, next: None })", 53 | ]"#]]); 54 | 55 | // When we mutate `l1`, we should re-execute `compute` for `l1`, 56 | // but we should not have to re-execute `compute` for `l2`. 57 | // The only input for `compute(l1)` is the accumulated values from `l1`, 58 | // which have not changed. 59 | l1.set_value(&mut db).to(2); 60 | assert_eq!(compute(&db, l2), 2); 61 | db.assert_logs(expect![[r#" 62 | [ 63 | "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 2, next: None }) })", 64 | "compute(List { [salsa id]: Id(0), value: 2, next: None })", 65 | ]"#]]); 66 | } 67 | -------------------------------------------------------------------------------- /tests/compile-fail/accumulator_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::accumulator(return_ref)] 2 | struct AccWithRetRef(u32); 3 | 4 | #[salsa::accumulator(specify)] 5 | struct AccWithSpecify(u32); 6 | 7 | #[salsa::accumulator(no_eq)] 8 | struct AccWithNoEq(u32); 9 | 10 | #[salsa::accumulator(data = MyAcc)] 11 | struct AccWithData(u32); 12 | 13 | #[salsa::accumulator(db = Db)] 14 | struct AcWithcDb(u32); 15 | 16 | #[salsa::accumulator(recover_fn = recover)] 17 | struct AccWithRecover(u32); 18 | 19 | #[salsa::accumulator(lru = 12)] 20 | struct AccWithLru(u32); 21 | 22 | #[salsa::accumulator(constructor = Constructor)] 23 | struct AccWithConstructor(u32); 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /tests/compile-fail/accumulator_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: `return_ref` option not allowed here 2 | --> tests/compile-fail/accumulator_incompatibles.rs:1:22 3 | | 4 | 1 | #[salsa::accumulator(return_ref)] 5 | | ^^^^^^^^^^ 6 | 7 | error: `specify` option not allowed here 8 | --> tests/compile-fail/accumulator_incompatibles.rs:4:22 9 | | 10 | 4 | #[salsa::accumulator(specify)] 11 | | ^^^^^^^ 12 | 13 | error: `no_eq` option not allowed here 14 | --> tests/compile-fail/accumulator_incompatibles.rs:7:22 15 | | 16 | 7 | #[salsa::accumulator(no_eq)] 17 | | ^^^^^ 18 | 19 | error: `data` option not allowed here 20 | --> tests/compile-fail/accumulator_incompatibles.rs:10:22 21 | | 22 | 10 | #[salsa::accumulator(data = MyAcc)] 23 | | ^^^^ 24 | 25 | error: `db` option not allowed here 26 | --> tests/compile-fail/accumulator_incompatibles.rs:13:22 27 | | 28 | 13 | #[salsa::accumulator(db = Db)] 29 | | ^^ 30 | 31 | error: unrecognized option `recover_fn` 32 | --> tests/compile-fail/accumulator_incompatibles.rs:16:22 33 | | 34 | 16 | #[salsa::accumulator(recover_fn = recover)] 35 | | ^^^^^^^^^^ 36 | 37 | error: `lru` option not allowed here 38 | --> tests/compile-fail/accumulator_incompatibles.rs:19:22 39 | | 40 | 19 | #[salsa::accumulator(lru = 12)] 41 | | ^^^ 42 | 43 | error: `constructor` option not allowed here 44 | --> tests/compile-fail/accumulator_incompatibles.rs:22:22 45 | | 46 | 22 | #[salsa::accumulator(constructor = Constructor)] 47 | | ^^^^^^^^^^^ 48 | -------------------------------------------------------------------------------- /tests/compile-fail/get-on-private-interned-field.rs: -------------------------------------------------------------------------------- 1 | mod a { 2 | #[salsa::interned] 3 | pub struct MyInterned<'db> { 4 | field: u32, 5 | } 6 | } 7 | 8 | fn test<'db>(db: &'db dyn salsa::Database, interned: a::MyInterned<'db>) { 9 | interned.field(db); 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/compile-fail/get-on-private-interned-field.stderr: -------------------------------------------------------------------------------- 1 | error[E0624]: method `field` is private 2 | --> tests/compile-fail/get-on-private-interned-field.rs:9:14 3 | | 4 | 2 | #[salsa::interned] 5 | | ------------------ private method defined here 6 | ... 7 | 9 | interned.field(db); 8 | | ^^^^^ private method 9 | -------------------------------------------------------------------------------- /tests/compile-fail/get-on-private-tracked-field.rs: -------------------------------------------------------------------------------- 1 | mod a { 2 | #[salsa::tracked] 3 | pub struct MyTracked<'db> { 4 | field: u32, 5 | } 6 | } 7 | 8 | fn test<'db>(db: &'db dyn salsa::Database, tracked: a::MyTracked<'db>) { 9 | tracked.field(db); 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/compile-fail/get-on-private-tracked-field.stderr: -------------------------------------------------------------------------------- 1 | error[E0624]: method `field` is private 2 | --> tests/compile-fail/get-on-private-tracked-field.rs:9:13 3 | | 4 | 2 | #[salsa::tracked] 5 | | ----------------- private method defined here 6 | ... 7 | 9 | tracked.field(db); 8 | | ^^^^^ private method 9 | -------------------------------------------------------------------------------- /tests/compile-fail/get-set-on-private-input-field.rs: -------------------------------------------------------------------------------- 1 | use salsa::prelude::*; 2 | 3 | mod a { 4 | #[salsa::input] 5 | pub struct MyInput { 6 | field: u32, 7 | } 8 | } 9 | 10 | fn main() { 11 | let mut db = salsa::DatabaseImpl::new(); 12 | let input = a::MyInput::new(&mut db, 22); 13 | 14 | input.field(&db); 15 | input.set_field(&mut db).to(23); 16 | } 17 | -------------------------------------------------------------------------------- /tests/compile-fail/get-set-on-private-input-field.stderr: -------------------------------------------------------------------------------- 1 | error[E0624]: method `field` is private 2 | --> tests/compile-fail/get-set-on-private-input-field.rs:14:11 3 | | 4 | 4 | #[salsa::input] 5 | | --------------- private method defined here 6 | ... 7 | 14 | input.field(&db); 8 | | ^^^^^ private method 9 | 10 | error[E0624]: method `set_field` is private 11 | --> tests/compile-fail/get-set-on-private-input-field.rs:15:11 12 | | 13 | 4 | #[salsa::input] 14 | | --------------- private method defined here 15 | ... 16 | 15 | input.set_field(&mut db).to(23); 17 | | ^^^^^^^^^ private method 18 | 19 | warning: unused import: `salsa::prelude` 20 | --> tests/compile-fail/get-set-on-private-input-field.rs:1:5 21 | | 22 | 1 | use salsa::prelude::*; 23 | | ^^^^^^^^^^^^^^ 24 | | 25 | = note: `#[warn(unused_imports)]` on by default 26 | -------------------------------------------------------------------------------- /tests/compile-fail/input_struct_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::input(return_ref)] 2 | struct InputWithRetRef(u32); 3 | 4 | #[salsa::input(specify)] 5 | struct InputWithSpecify(u32); 6 | 7 | #[salsa::input(no_eq)] 8 | struct InputNoWithEq(u32); 9 | 10 | #[salsa::input(db = Db)] 11 | struct InputWithDb(u32); 12 | 13 | #[salsa::input(recover_fn = recover)] 14 | struct InputWithRecover(u32); 15 | 16 | #[salsa::input(lru =12)] 17 | struct InputWithLru(u32); 18 | 19 | #[salsa::input] 20 | struct InputWithIdField { 21 | #[id] 22 | field: u32, 23 | } 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /tests/compile-fail/input_struct_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: `return_ref` option not allowed here 2 | --> tests/compile-fail/input_struct_incompatibles.rs:1:16 3 | | 4 | 1 | #[salsa::input(return_ref)] 5 | | ^^^^^^^^^^ 6 | 7 | error: `specify` option not allowed here 8 | --> tests/compile-fail/input_struct_incompatibles.rs:4:16 9 | | 10 | 4 | #[salsa::input(specify)] 11 | | ^^^^^^^ 12 | 13 | error: `no_eq` option not allowed here 14 | --> tests/compile-fail/input_struct_incompatibles.rs:7:16 15 | | 16 | 7 | #[salsa::input(no_eq)] 17 | | ^^^^^ 18 | 19 | error: `db` option not allowed here 20 | --> tests/compile-fail/input_struct_incompatibles.rs:10:16 21 | | 22 | 10 | #[salsa::input(db = Db)] 23 | | ^^ 24 | 25 | error: unrecognized option `recover_fn` 26 | --> tests/compile-fail/input_struct_incompatibles.rs:13:16 27 | | 28 | 13 | #[salsa::input(recover_fn = recover)] 29 | | ^^^^^^^^^^ 30 | 31 | error: `lru` option not allowed here 32 | --> tests/compile-fail/input_struct_incompatibles.rs:16:16 33 | | 34 | 16 | #[salsa::input(lru =12)] 35 | | ^^^ 36 | 37 | error: `#[id]` cannot be used with `#[salsa::input]` 38 | --> tests/compile-fail/input_struct_incompatibles.rs:21:5 39 | | 40 | 21 | / #[id] 41 | 22 | | field: u32, 42 | | |______________^ 43 | -------------------------------------------------------------------------------- /tests/compile-fail/interned_struct_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::interned(return_ref)] 2 | struct InternedWithRetRef { 3 | field: u32, 4 | } 5 | 6 | #[salsa::interned(specify)] 7 | struct InternedWithSpecify { 8 | field: u32, 9 | } 10 | 11 | #[salsa::interned(no_eq)] 12 | struct InternedWithNoEq { 13 | field: u32, 14 | } 15 | 16 | #[salsa::interned(db = Db)] 17 | struct InternedWithDb { 18 | field: u32, 19 | } 20 | 21 | #[salsa::interned(recover_fn = recover)] 22 | struct InternedWithRecover { 23 | field: u32, 24 | } 25 | 26 | #[salsa::interned(lru = 12)] 27 | struct InternedWithLru { 28 | field: u32, 29 | } 30 | 31 | #[salsa::interned] 32 | struct InternedWithIdField { 33 | #[id] 34 | field: u32, 35 | } 36 | 37 | fn main() {} 38 | -------------------------------------------------------------------------------- /tests/compile-fail/interned_struct_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: `return_ref` option not allowed here 2 | --> tests/compile-fail/interned_struct_incompatibles.rs:1:19 3 | | 4 | 1 | #[salsa::interned(return_ref)] 5 | | ^^^^^^^^^^ 6 | 7 | error: `specify` option not allowed here 8 | --> tests/compile-fail/interned_struct_incompatibles.rs:6:19 9 | | 10 | 6 | #[salsa::interned(specify)] 11 | | ^^^^^^^ 12 | 13 | error: `no_eq` option not allowed here 14 | --> tests/compile-fail/interned_struct_incompatibles.rs:11:19 15 | | 16 | 11 | #[salsa::interned(no_eq)] 17 | | ^^^^^ 18 | 19 | error: `db` option not allowed here 20 | --> tests/compile-fail/interned_struct_incompatibles.rs:16:19 21 | | 22 | 16 | #[salsa::interned(db = Db)] 23 | | ^^ 24 | 25 | error: unrecognized option `recover_fn` 26 | --> tests/compile-fail/interned_struct_incompatibles.rs:21:19 27 | | 28 | 21 | #[salsa::interned(recover_fn = recover)] 29 | | ^^^^^^^^^^ 30 | 31 | error: `lru` option not allowed here 32 | --> tests/compile-fail/interned_struct_incompatibles.rs:26:19 33 | | 34 | 26 | #[salsa::interned(lru = 12)] 35 | | ^^^ 36 | 37 | error: `#[id]` cannot be used with `#[salsa::interned]` 38 | --> tests/compile-fail/interned_struct_incompatibles.rs:33:5 39 | | 40 | 33 | / #[id] 41 | 34 | | field: u32, 42 | | |______________^ 43 | -------------------------------------------------------------------------------- /tests/compile-fail/lru_can_not_be_used_with_specify.rs: -------------------------------------------------------------------------------- 1 | #[salsa::input] 2 | struct MyInput { 3 | field: u32, 4 | } 5 | 6 | #[salsa::tracked(lru = 3, specify)] 7 | fn lru_can_not_be_used_with_specify(db: &dyn salsa::Database, input: MyInput) -> u32 { 8 | input.field(db) 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/compile-fail/lru_can_not_be_used_with_specify.stderr: -------------------------------------------------------------------------------- 1 | error: the `specify` and `lru` options cannot be used together 2 | --> tests/compile-fail/lru_can_not_be_used_with_specify.rs:6:27 3 | | 4 | 6 | #[salsa::tracked(lru = 3, specify)] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs: -------------------------------------------------------------------------------- 1 | use salsa::prelude::*; 2 | 3 | #[salsa::input] 4 | struct MyInput { 5 | field: u32, 6 | } 7 | 8 | #[salsa::tracked] 9 | struct MyTracked<'db> { 10 | field: u32, 11 | } 12 | 13 | #[salsa::tracked] 14 | fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { 15 | MyTracked::new(db, input.field(db) / 2) 16 | } 17 | 18 | fn main() { 19 | let mut db = salsa::DatabaseImpl::new(); 20 | let input = MyInput::new(&db, 22); 21 | let tracked = tracked_fn(&db, input); 22 | input.set_field(&mut db).to(24); 23 | tracked.field(&db); // tracked comes from prior revision 24 | } 25 | -------------------------------------------------------------------------------- /tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.stderr: -------------------------------------------------------------------------------- 1 | error[E0502]: cannot borrow `db` as mutable because it is also borrowed as immutable 2 | --> tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs:22:21 3 | | 4 | 21 | let tracked = tracked_fn(&db, input); 5 | | --- immutable borrow occurs here 6 | 22 | input.set_field(&mut db).to(24); 7 | | ^^^^^^^ mutable borrow occurs here 8 | 23 | tracked.field(&db); // tracked comes from prior revision 9 | | ------- immutable borrow later used here 10 | -------------------------------------------------------------------------------- /tests/compile-fail/salsa_fields_incompatibles.rs: -------------------------------------------------------------------------------- 1 | // Banned field name: `from` 2 | #[salsa::input] 3 | struct InputWithBannedName1 { 4 | from: u32, 5 | } 6 | 7 | // Banned field name: `new` 8 | #[salsa::input] 9 | struct InputWithBannedName2 { 10 | new: u32, 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /tests/compile-fail/salsa_fields_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: the field name `from` is disallowed in salsa structs 2 | --> tests/compile-fail/salsa_fields_incompatibles.rs:4:5 3 | | 4 | 4 | from: u32, 5 | | ^^^^ 6 | 7 | error: the field name `new` is disallowed in salsa structs 8 | --> tests/compile-fail/salsa_fields_incompatibles.rs:10:5 9 | | 10 | 10 | new: u32, 11 | | ^^^ 12 | -------------------------------------------------------------------------------- /tests/compile-fail/singleton_only_for_input.rs: -------------------------------------------------------------------------------- 1 | //! Compile Singleton struct test: 2 | //! 3 | //! Singleton flags are only allowed for input structs. If applied on any other Salsa struct compilation must fail 4 | 5 | #[salsa::input(singleton)] 6 | struct MyInput { 7 | field: u32, 8 | } 9 | 10 | #[salsa::tracked(singleton)] 11 | struct MyTracked<'db> { 12 | field: u32, 13 | } 14 | 15 | #[salsa::tracked(singleton)] 16 | fn create_tracked_structs(db: &dyn salsa::Database, input: MyInput) -> Vec { 17 | (0..input.field(db)) 18 | .map(|i| MyTracked::new(db, i)) 19 | .collect() 20 | } 21 | 22 | #[salsa::accumulator(singleton)] 23 | struct Integers(u32); 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /tests/compile-fail/singleton_only_for_input.stderr: -------------------------------------------------------------------------------- 1 | error: `singleton` option not allowed here 2 | --> tests/compile-fail/singleton_only_for_input.rs:15:18 3 | | 4 | 15 | #[salsa::tracked(singleton)] 5 | | ^^^^^^^^^ 6 | 7 | error: `singleton` option not allowed here 8 | --> tests/compile-fail/singleton_only_for_input.rs:22:22 9 | | 10 | 22 | #[salsa::accumulator(singleton)] 11 | | ^^^^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/compile-fail/span-input-setter.rs: -------------------------------------------------------------------------------- 1 | #[salsa::input] 2 | pub struct MyInput { 3 | field: u32, 4 | } 5 | 6 | fn main() { 7 | let mut db = salsa::DatabaseImpl::new(); 8 | let input = MyInput::new(&mut db, 22); 9 | input.field(&db); 10 | input.set_field(22); 11 | } 12 | -------------------------------------------------------------------------------- /tests/compile-fail/span-input-setter.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/compile-fail/span-input-setter.rs:10:21 3 | | 4 | 10 | input.set_field(22); 5 | | --------- ^^ expected `&mut _`, found integer 6 | | | 7 | | arguments to this method are incorrect 8 | | 9 | = note: expected mutable reference `&mut _` 10 | found type `{integer}` 11 | note: method defined here 12 | --> tests/compile-fail/span-input-setter.rs:3:5 13 | | 14 | 1 | #[salsa::input] 15 | | --------------- 16 | 2 | pub struct MyInput { 17 | 3 | field: u32, 18 | | ^^^^^ 19 | help: consider mutably borrowing here 20 | | 21 | 10 | input.set_field(&mut 22); 22 | | ++++ 23 | -------------------------------------------------------------------------------- /tests/compile-fail/span-tracked-getter.rs: -------------------------------------------------------------------------------- 1 | #[salsa::tracked] 2 | pub struct MyTracked<'db> { 3 | field: u32, 4 | } 5 | 6 | #[salsa::tracked] 7 | fn my_fn(db: &dyn salsa::Database) { 8 | let x = MyTracked::new(db, 22); 9 | x.field(22); 10 | } 11 | 12 | fn main() { 13 | let mut db = salsa::DatabaseImpl::new(); 14 | my_fn(&db); 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile-fail/span-tracked-getter.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/compile-fail/span-tracked-getter.rs:9:13 3 | | 4 | 9 | x.field(22); 5 | | ----- ^^ expected `&_`, found integer 6 | | | 7 | | arguments to this method are incorrect 8 | | 9 | = note: expected reference `&_` 10 | found type `{integer}` 11 | note: method defined here 12 | --> tests/compile-fail/span-tracked-getter.rs:3:5 13 | | 14 | 1 | #[salsa::tracked] 15 | | ----------------- 16 | 2 | pub struct MyTracked<'db> { 17 | 3 | field: u32, 18 | | ^^^^^ 19 | help: consider borrowing here 20 | | 21 | 9 | x.field(&22); 22 | | + 23 | 24 | warning: variable does not need to be mutable 25 | --> tests/compile-fail/span-tracked-getter.rs:13:9 26 | | 27 | 13 | let mut db = salsa::DatabaseImpl::new(); 28 | | ----^^ 29 | | | 30 | | help: remove this `mut` 31 | | 32 | = note: `#[warn(unused_mut)]` on by default 33 | -------------------------------------------------------------------------------- /tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs: -------------------------------------------------------------------------------- 1 | //! Test that `specify` does not work if the key is a `salsa::input` 2 | //! compilation fails 3 | #![allow(warnings)] 4 | 5 | #[salsa::input] 6 | struct MyInput { 7 | field: u32, 8 | } 9 | 10 | #[salsa::tracked] 11 | struct MyTracked<'db> { 12 | field: u32, 13 | } 14 | 15 | #[salsa::tracked(specify)] 16 | fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { 17 | MyTracked::new(db, input.field(db) * 2) 18 | } 19 | 20 | fn main() {} 21 | -------------------------------------------------------------------------------- /tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyInput: TrackedStructInDb` is not satisfied 2 | --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs:15:1 3 | | 4 | 15 | #[salsa::tracked(specify)] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TrackedStructInDb` is not implemented for `MyInput` 6 | | 7 | = help: the trait `TrackedStructInDb` is implemented for `MyTracked<'_>` 8 | note: required by a bound in `salsa::function::specify::>::specify_and_record` 9 | --> src/function/specify.rs 10 | | 11 | | pub fn specify_and_record<'db>(&'db self, db: &'db C::DbView, key: Id, value: C::Output<'db>) 12 | | ------------------ required by a bound in this associated function 13 | | where 14 | | C::Input<'db>: TrackedStructInDb, 15 | | ^^^^^^^^^^^^^^^^^ required by this bound in `salsa::function::specify::>::specify_and_record` 16 | = note: this error originates in the macro `salsa::plumbing::setup_tracked_fn` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) 17 | -------------------------------------------------------------------------------- /tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs: -------------------------------------------------------------------------------- 1 | //! Test that `specify` does not work if the key is a `salsa::interned` 2 | //! compilation fails 3 | #![allow(warnings)] 4 | 5 | #[salsa::interned] 6 | struct MyInterned<'db> { 7 | field: u32, 8 | } 9 | 10 | #[salsa::tracked] 11 | struct MyTracked<'db> { 12 | field: u32, 13 | } 14 | 15 | #[salsa::tracked(specify)] 16 | fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInterned<'db>) -> MyTracked<'db> { 17 | MyTracked::new(db, input.field(db) * 2) 18 | } 19 | 20 | fn main() {} 21 | -------------------------------------------------------------------------------- /tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyInterned<'_>: TrackedStructInDb` is not satisfied 2 | --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs:15:1 3 | | 4 | 15 | #[salsa::tracked(specify)] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TrackedStructInDb` is not implemented for `MyInterned<'_>` 6 | | 7 | = help: the trait `TrackedStructInDb` is implemented for `MyTracked<'_>` 8 | note: required by a bound in `salsa::function::specify::>::specify_and_record` 9 | --> src/function/specify.rs 10 | | 11 | | pub fn specify_and_record<'db>(&'db self, db: &'db C::DbView, key: Id, value: C::Output<'db>) 12 | | ------------------ required by a bound in this associated function 13 | | where 14 | | C::Input<'db>: TrackedStructInDb, 15 | | ^^^^^^^^^^^^^^^^^ required by this bound in `salsa::function::specify::>::specify_and_record` 16 | = note: this error originates in the macro `salsa::plumbing::setup_tracked_fn` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) 17 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_fn_incompatibles.rs: -------------------------------------------------------------------------------- 1 | use salsa::Database as Db; 2 | 3 | #[salsa::input] 4 | struct MyInput { 5 | field: u32, 6 | } 7 | 8 | #[salsa::tracked(data = Data)] 9 | fn tracked_fn_with_data(db: &dyn Db, input: MyInput) -> u32 { 10 | input.field(db) * 2 11 | } 12 | 13 | #[salsa::tracked(db = Db)] 14 | fn tracked_fn_with_db(db: &dyn Db, input: MyInput) -> u32 { 15 | input.field(db) * 2 16 | } 17 | 18 | #[salsa::tracked(constructor = TrackedFn3)] 19 | fn tracked_fn_with_constructor(db: &dyn Db, input: MyInput) -> u32 { 20 | input.field(db) * 2 21 | } 22 | 23 | #[salsa::tracked] 24 | fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {} 25 | 26 | #[salsa::tracked] 27 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {} 28 | 29 | #[salsa::tracked(specify)] 30 | fn tracked_fn_with_too_many_arguments_for_specify( 31 | db: &dyn Db, 32 | input: MyInput, 33 | input: MyInput, 34 | ) -> u32 { 35 | } 36 | 37 | fn main() {} 38 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_fn_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: `data` option not allowed here 2 | --> tests/compile-fail/tracked_fn_incompatibles.rs:8:18 3 | | 4 | 8 | #[salsa::tracked(data = Data)] 5 | | ^^^^ 6 | 7 | error: `db` option not allowed here 8 | --> tests/compile-fail/tracked_fn_incompatibles.rs:13:18 9 | | 10 | 13 | #[salsa::tracked(db = Db)] 11 | | ^^ 12 | 13 | error: `constructor` option not allowed here 14 | --> tests/compile-fail/tracked_fn_incompatibles.rs:18:18 15 | | 16 | 18 | #[salsa::tracked(constructor = TrackedFn3)] 17 | | ^^^^^^^^^^^ 18 | 19 | error: #[salsa::tracked] must also be applied to the impl block for tracked methods 20 | --> tests/compile-fail/tracked_fn_incompatibles.rs:27:55 21 | | 22 | 27 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {} 23 | | ^^^^^ 24 | 25 | error: only functions with a single salsa struct as their input can be specified 26 | --> tests/compile-fail/tracked_fn_incompatibles.rs:29:18 27 | | 28 | 29 | #[salsa::tracked(specify)] 29 | | ^^^^^^^ 30 | 31 | error[E0308]: mismatched types 32 | --> tests/compile-fail/tracked_fn_incompatibles.rs:24:46 33 | | 34 | 23 | #[salsa::tracked] 35 | | ----------------- implicitly returns `()` as its body has no tail or `return` expression 36 | 24 | fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {} 37 | | ^^^ expected `u32`, found `()` 38 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_impl_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::tracked] 2 | struct MyTracked<'db> { 3 | field: u32, 4 | } 5 | 6 | #[salsa::tracked(return_ref)] 7 | impl<'db> std::default::Default for MyTracked<'db> { 8 | fn default() -> Self {} 9 | } 10 | 11 | #[salsa::tracked(specify)] 12 | impl<'db> std::default::Default for MyTracked<'db> { 13 | fn default() -> Self {} 14 | } 15 | 16 | #[salsa::tracked(no_eq)] 17 | impl<'db> std::default::Default for MyTracked<'db> { 18 | fn default() -> Self {} 19 | } 20 | 21 | #[salsa::tracked(data = Data)] 22 | impl<'db> std::default::Default for MyTracked<'db> { 23 | fn default() -> Self {} 24 | } 25 | 26 | #[salsa::tracked(db = Db)] 27 | impl<'db> std::default::Default for MyTracked<'db> { 28 | fn default() -> Self {} 29 | } 30 | 31 | #[salsa::tracked(recover_fn = recover)] 32 | impl<'db> std::default::Default for MyTracked<'db> { 33 | fn default() -> Self {} 34 | } 35 | 36 | #[salsa::tracked(lru = 32)] 37 | impl<'db> std::default::Default for MyTracked<'db> { 38 | fn default() -> Self {} 39 | } 40 | 41 | #[salsa::tracked(constructor = Constructor)] 42 | impl<'db> std::default::Default for MyTracked<'db> { 43 | fn default() -> Self {} 44 | } 45 | 46 | #[salsa::tracked] 47 | impl<'db> std::default::Default for [MyTracked<'db>; 12] { 48 | fn default() -> Self {} 49 | } 50 | 51 | fn main() {} 52 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_impl_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected token 2 | --> tests/compile-fail/tracked_impl_incompatibles.rs:6:18 3 | | 4 | 6 | #[salsa::tracked(return_ref)] 5 | | ^^^^^^^^^^ 6 | 7 | error: unexpected token 8 | --> tests/compile-fail/tracked_impl_incompatibles.rs:11:18 9 | | 10 | 11 | #[salsa::tracked(specify)] 11 | | ^^^^^^^ 12 | 13 | error: unexpected token 14 | --> tests/compile-fail/tracked_impl_incompatibles.rs:16:18 15 | | 16 | 16 | #[salsa::tracked(no_eq)] 17 | | ^^^^^ 18 | 19 | error: unexpected token 20 | --> tests/compile-fail/tracked_impl_incompatibles.rs:21:18 21 | | 22 | 21 | #[salsa::tracked(data = Data)] 23 | | ^^^^ 24 | 25 | error: unexpected token 26 | --> tests/compile-fail/tracked_impl_incompatibles.rs:26:18 27 | | 28 | 26 | #[salsa::tracked(db = Db)] 29 | | ^^ 30 | 31 | error: unexpected token 32 | --> tests/compile-fail/tracked_impl_incompatibles.rs:31:18 33 | | 34 | 31 | #[salsa::tracked(recover_fn = recover)] 35 | | ^^^^^^^^^^ 36 | 37 | error: unexpected token 38 | --> tests/compile-fail/tracked_impl_incompatibles.rs:36:18 39 | | 40 | 36 | #[salsa::tracked(lru = 32)] 41 | | ^^^ 42 | 43 | error: unexpected token 44 | --> tests/compile-fail/tracked_impl_incompatibles.rs:41:18 45 | | 46 | 41 | #[salsa::tracked(constructor = Constructor)] 47 | | ^^^^^^^^^^^ 48 | 49 | error[E0117]: only traits defined in the current crate can be implemented for arbitrary types 50 | --> tests/compile-fail/tracked_impl_incompatibles.rs:47:1 51 | | 52 | 47 | impl<'db> std::default::Default for [MyTracked<'db>; 12] { 53 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------------------- 54 | | | | 55 | | | this is not defined in the current crate because arrays are always foreign 56 | | impl doesn't use only types from inside the current crate 57 | | 58 | = note: define and implement a trait or new type instead 59 | 60 | error[E0308]: mismatched types 61 | --> tests/compile-fail/tracked_impl_incompatibles.rs:48:21 62 | | 63 | 48 | fn default() -> Self {} 64 | | ------- ^^^^ expected `[MyTracked<'_>; 12]`, found `()` 65 | | | 66 | | implicitly returns `()` as its body has no tail or `return` expression 67 | | 68 | = note: expected array `[MyTracked<'db>; 12]` 69 | found unit type `()` 70 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_method_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::tracked] 2 | struct Tracked<'db> { 3 | field: u32, 4 | } 5 | 6 | #[salsa::tracked] 7 | impl<'db> Tracked<'db> { 8 | #[salsa::tracked] 9 | fn ref_self(&self, db: &dyn salsa::Database) {} 10 | } 11 | 12 | #[salsa::tracked] 13 | impl<'db> Tracked<'db> { 14 | #[salsa::tracked] 15 | fn ref_mut_self(&mut self, db: &dyn salsa::Database) {} 16 | } 17 | 18 | #[salsa::tracked] 19 | impl<'db> Tracked<'db> { 20 | #[salsa::tracked] 21 | fn multiple_lifetimes<'db1>(&mut self, db: &'db1 dyn salsa::Database) {} 22 | } 23 | 24 | #[salsa::tracked] 25 | impl<'db> Tracked<'db> { 26 | #[salsa::tracked] 27 | fn type_generics(&mut self, db: &dyn salsa::Database) -> T { 28 | panic!() 29 | } 30 | } 31 | 32 | fn main() {} 33 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_method_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: tracked methods's first argument must be declared as `self`, not `&self` or `&mut self` 2 | --> tests/compile-fail/tracked_method_incompatibles.rs:9:17 3 | | 4 | 9 | fn ref_self(&self, db: &dyn salsa::Database) {} 5 | | ^ 6 | 7 | error: tracked methods's first argument must be declared as `self`, not `&self` or `&mut self` 8 | --> tests/compile-fail/tracked_method_incompatibles.rs:15:21 9 | | 10 | 15 | fn ref_mut_self(&mut self, db: &dyn salsa::Database) {} 11 | | ^ 12 | 13 | error: tracked method already has a lifetime parameter in scope 14 | --> tests/compile-fail/tracked_method_incompatibles.rs:21:27 15 | | 16 | 21 | fn multiple_lifetimes<'db1>(&mut self, db: &'db1 dyn salsa::Database) {} 17 | | ^^^^ 18 | 19 | error: tracked methods cannot have non-lifetime generic parameters 20 | --> tests/compile-fail/tracked_method_incompatibles.rs:27:22 21 | | 22 | 27 | fn type_generics(&mut self, db: &dyn salsa::Database) -> T { 23 | | ^ 24 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_method_on_untracked_impl.rs: -------------------------------------------------------------------------------- 1 | #[salsa::input] 2 | struct MyInput { 3 | field: u32, 4 | } 5 | 6 | impl MyInput { 7 | #[salsa::tracked] 8 | fn tracked_method_on_untracked_impl(self, db: &dyn Db) -> u32 { 9 | input.field(db) 10 | } 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_method_on_untracked_impl.stderr: -------------------------------------------------------------------------------- 1 | error: #[salsa::tracked] must also be applied to the impl block for tracked methods 2 | --> tests/compile-fail/tracked_method_on_untracked_impl.rs:8:41 3 | | 4 | 8 | fn tracked_method_on_untracked_impl(self, db: &dyn Db) -> u32 { 5 | | ^^^^ 6 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_struct_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::tracked(return_ref)] 2 | struct TrackedWithRetRef { 3 | field: u32, 4 | } 5 | 6 | #[salsa::tracked(specify)] 7 | struct TrackedSructWithSpecify { 8 | field: u32, 9 | } 10 | 11 | #[salsa::tracked(no_eq)] 12 | struct TrackedStructWithNoEq { 13 | field: u32, 14 | } 15 | 16 | #[salsa::tracked(db = Db)] 17 | struct TrackedStructWithDb { 18 | field: u32, 19 | } 20 | 21 | #[salsa::tracked(recover_fn = recover)] 22 | struct TrackedStructWithRecover { 23 | field: u32, 24 | } 25 | 26 | #[salsa::tracked(lru = 12)] 27 | struct TrackedStructWithLru { 28 | field: u32, 29 | } 30 | 31 | fn main() {} 32 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_struct_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: `return_ref` option not allowed here 2 | --> tests/compile-fail/tracked_struct_incompatibles.rs:1:18 3 | | 4 | 1 | #[salsa::tracked(return_ref)] 5 | | ^^^^^^^^^^ 6 | 7 | error: `specify` option not allowed here 8 | --> tests/compile-fail/tracked_struct_incompatibles.rs:6:18 9 | | 10 | 6 | #[salsa::tracked(specify)] 11 | | ^^^^^^^ 12 | 13 | error: `no_eq` option not allowed here 14 | --> tests/compile-fail/tracked_struct_incompatibles.rs:11:18 15 | | 16 | 11 | #[salsa::tracked(no_eq)] 17 | | ^^^^^ 18 | 19 | error: `db` option not allowed here 20 | --> tests/compile-fail/tracked_struct_incompatibles.rs:16:18 21 | | 22 | 16 | #[salsa::tracked(db = Db)] 23 | | ^^ 24 | 25 | error: unrecognized option `recover_fn` 26 | --> tests/compile-fail/tracked_struct_incompatibles.rs:21:18 27 | | 28 | 21 | #[salsa::tracked(recover_fn = recover)] 29 | | ^^^^^^^^^^ 30 | 31 | error: `lru` option not allowed here 32 | --> tests/compile-fail/tracked_struct_incompatibles.rs:26:18 33 | | 34 | 26 | #[salsa::tracked(lru = 12)] 35 | | ^^^ 36 | -------------------------------------------------------------------------------- /tests/compile_fail.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::stable] 2 | #[test] 3 | fn compile_fail() { 4 | let t = trybuild::TestCases::new(); 5 | t.compile_fail("tests/compile-fail/*.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/deletion-drops.rs: -------------------------------------------------------------------------------- 1 | //! Basic deletion test: 2 | //! 3 | //! * entities not created in a revision are deleted, as is any memoized data keyed on them. 4 | 5 | mod common; 6 | 7 | use salsa::{Database, Setter}; 8 | use test_log::test; 9 | 10 | #[salsa::input] 11 | struct MyInput { 12 | identity: u32, 13 | } 14 | 15 | #[salsa::tracked] 16 | struct MyTracked<'db> { 17 | #[id] 18 | identifier: u32, 19 | 20 | #[return_ref] 21 | field: Bomb, 22 | } 23 | 24 | thread_local! { 25 | static DROPPED: std::cell::RefCell> = const { std::cell::RefCell::new(vec![]) }; 26 | } 27 | 28 | fn dropped() -> Vec { 29 | DROPPED.with(|d| d.borrow().clone()) 30 | } 31 | 32 | #[derive(Clone, Debug, PartialEq, Eq)] 33 | struct Bomb { 34 | identity: u32, 35 | } 36 | 37 | impl Drop for Bomb { 38 | fn drop(&mut self) { 39 | DROPPED.with(|d| d.borrow_mut().push(self.identity)); 40 | } 41 | } 42 | 43 | #[salsa::tracked] 44 | impl MyInput { 45 | #[salsa::tracked] 46 | fn create_tracked_struct(self, db: &dyn Database) -> MyTracked<'_> { 47 | MyTracked::new( 48 | db, 49 | self.identity(db), 50 | Bomb { 51 | identity: self.identity(db), 52 | }, 53 | ) 54 | } 55 | } 56 | 57 | #[test] 58 | fn deletion_drops() { 59 | let mut db = salsa::DatabaseImpl::new(); 60 | 61 | let input = MyInput::new(&db, 22); 62 | 63 | expect_test::expect![[r#" 64 | [] 65 | "#]] 66 | .assert_debug_eq(&dropped()); 67 | 68 | let tracked_struct = input.create_tracked_struct(&db); 69 | assert_eq!(tracked_struct.field(&db).identity, 22); 70 | 71 | expect_test::expect![[r#" 72 | [] 73 | "#]] 74 | .assert_debug_eq(&dropped()); 75 | 76 | input.set_identity(&mut db).to(44); 77 | 78 | expect_test::expect![[r#" 79 | [] 80 | "#]] 81 | .assert_debug_eq(&dropped()); 82 | 83 | // Now that we execute with rev = 44, the old id is put on the free list 84 | let tracked_struct = input.create_tracked_struct(&db); 85 | assert_eq!(tracked_struct.field(&db).identity, 44); 86 | 87 | expect_test::expect![[r#" 88 | [] 89 | "#]] 90 | .assert_debug_eq(&dropped()); 91 | 92 | // When we execute again with `input1`, that id is re-used, so the old value is deleted 93 | let input1 = MyInput::new(&db, 66); 94 | let _tracked_struct1 = input1.create_tracked_struct(&db); 95 | 96 | expect_test::expect![[r#" 97 | [ 98 | 22, 99 | ] 100 | "#]] 101 | .assert_debug_eq(&dropped()); 102 | } 103 | -------------------------------------------------------------------------------- /tests/deletion.rs: -------------------------------------------------------------------------------- 1 | //! Basic deletion test: 2 | //! 3 | //! * entities not created in a revision are deleted, as is any memoized data keyed on them. 4 | 5 | mod common; 6 | use common::LogDatabase; 7 | 8 | use expect_test::expect; 9 | use salsa::Setter; 10 | use test_log::test; 11 | 12 | #[salsa::input] 13 | struct MyInput { 14 | field: u32, 15 | } 16 | 17 | #[salsa::tracked] 18 | fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { 19 | db.push_log(format!("final_result({:?})", input)); 20 | let mut sum = 0; 21 | for tracked_struct in create_tracked_structs(db, input) { 22 | sum += contribution_from_struct(db, tracked_struct); 23 | } 24 | sum 25 | } 26 | 27 | #[salsa::tracked] 28 | struct MyTracked<'db> { 29 | field: u32, 30 | } 31 | 32 | #[salsa::tracked] 33 | fn create_tracked_structs(db: &dyn LogDatabase, input: MyInput) -> Vec> { 34 | db.push_log(format!("intermediate_result({:?})", input)); 35 | (0..input.field(db)) 36 | .map(|i| MyTracked::new(db, i)) 37 | .collect() 38 | } 39 | 40 | #[salsa::tracked] 41 | fn contribution_from_struct<'db>(db: &'db dyn LogDatabase, tracked: MyTracked<'db>) -> u32 { 42 | tracked.field(db) * 2 43 | } 44 | 45 | #[test] 46 | fn basic() { 47 | let mut db = common::DiscardLoggerDatabase::default(); 48 | 49 | // Creates 3 tracked structs 50 | let input = MyInput::new(&db, 3); 51 | assert_eq!(final_result(&db, input), 2 * 2 + 2); 52 | db.assert_logs(expect![[r#" 53 | [ 54 | "final_result(MyInput { [salsa id]: Id(0), field: 3 })", 55 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 3 })", 56 | ]"#]]); 57 | 58 | // Creates only 2 tracked structs in this revision, should delete 1 59 | // 60 | // Expect to see 3 DidDiscard events-- 61 | // 62 | // * the struct itself 63 | // * the struct's field 64 | // * the `contribution_from_struct` result 65 | input.set_field(&mut db).to(2); 66 | assert_eq!(final_result(&db, input), 2); 67 | db.assert_logs(expect![[r#" 68 | [ 69 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 2 })", 70 | "salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(Id(0)), output_key: MyTracked(Id(402)) })", 71 | "salsa_event(DidDiscard { key: MyTracked(Id(402)) })", 72 | "salsa_event(DidDiscard { key: contribution_from_struct(Id(402)) })", 73 | "final_result(MyInput { [salsa id]: Id(0), field: 2 })", 74 | ]"#]]); 75 | } 76 | -------------------------------------------------------------------------------- /tests/elided-lifetime-in-tracked-fn.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | 4 | mod common; 5 | use common::LogDatabase; 6 | 7 | use expect_test::expect; 8 | use salsa::Setter; 9 | use test_log::test; 10 | 11 | #[salsa::input] 12 | struct MyInput { 13 | field: u32, 14 | } 15 | 16 | #[salsa::tracked] 17 | fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { 18 | db.push_log(format!("final_result({:?})", input)); 19 | intermediate_result(db, input).field(db) * 2 20 | } 21 | 22 | #[salsa::tracked] 23 | struct MyTracked<'db> { 24 | field: u32, 25 | } 26 | 27 | #[salsa::tracked] 28 | fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> { 29 | db.push_log(format!("intermediate_result({:?})", input)); 30 | MyTracked::new(db, input.field(db) / 2) 31 | } 32 | 33 | #[test] 34 | fn execute() { 35 | let mut db = common::LoggerDatabase::default(); 36 | 37 | let input = MyInput::new(&db, 22); 38 | assert_eq!(final_result(&db, input), 22); 39 | db.assert_logs(expect![[r#" 40 | [ 41 | "final_result(MyInput { [salsa id]: Id(0), field: 22 })", 42 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", 43 | ]"#]]); 44 | 45 | // Intermediate result is the same, so final result does 46 | // not need to be recomputed: 47 | input.set_field(&mut db).to(23); 48 | assert_eq!(final_result(&db, input), 22); 49 | db.assert_logs(expect![[r#" 50 | [ 51 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 23 })", 52 | ]"#]]); 53 | 54 | input.set_field(&mut db).to(24); 55 | assert_eq!(final_result(&db, input), 24); 56 | db.assert_logs(expect![[r#" 57 | [ 58 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 24 })", 59 | "final_result(MyInput { [salsa id]: Id(0), field: 24 })", 60 | ]"#]]); 61 | } 62 | -------------------------------------------------------------------------------- /tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs: -------------------------------------------------------------------------------- 1 | //! Test that if field X of a tracked struct changes but not field Y, 2 | //! functions that depend on X re-execute, but those depending only on Y do not 3 | //! compiles and executes successfully. 4 | #![allow(dead_code)] 5 | 6 | mod common; 7 | use common::LogDatabase; 8 | 9 | use expect_test::expect; 10 | use salsa::Setter; 11 | 12 | #[salsa::input] 13 | struct MyInput { 14 | field: u32, 15 | } 16 | 17 | #[salsa::tracked] 18 | fn final_result_depends_on_x(db: &dyn LogDatabase, input: MyInput) -> u32 { 19 | db.push_log(format!("final_result_depends_on_x({:?})", input)); 20 | intermediate_result(db, input).x(db) * 2 21 | } 22 | 23 | #[salsa::tracked] 24 | fn final_result_depends_on_y(db: &dyn LogDatabase, input: MyInput) -> u32 { 25 | db.push_log(format!("final_result_depends_on_y({:?})", input)); 26 | intermediate_result(db, input).y(db) * 2 27 | } 28 | 29 | #[salsa::tracked] 30 | struct MyTracked<'db> { 31 | x: u32, 32 | y: u32, 33 | } 34 | 35 | #[salsa::tracked] 36 | fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> { 37 | MyTracked::new(db, (input.field(db) + 1) / 2, input.field(db) / 2) 38 | } 39 | 40 | #[test] 41 | fn execute() { 42 | // x = (input.field + 1) / 2 43 | // y = input.field / 2 44 | // final_result_depends_on_x = x * 2 = (input.field + 1) / 2 * 2 45 | // final_result_depends_on_y = y * 2 = input.field / 2 * 2 46 | let mut db = common::LoggerDatabase::default(); 47 | 48 | // intermediate results: 49 | // x = (22 + 1) / 2 = 11 50 | // y = 22 / 2 = 11 51 | let input = MyInput::new(&db, 22); 52 | assert_eq!(final_result_depends_on_x(&db, input), 22); 53 | db.assert_logs(expect![[r#" 54 | [ 55 | "final_result_depends_on_x(MyInput { [salsa id]: Id(0), field: 22 })", 56 | ]"#]]); 57 | 58 | assert_eq!(final_result_depends_on_y(&db, input), 22); 59 | db.assert_logs(expect![[r#" 60 | [ 61 | "final_result_depends_on_y(MyInput { [salsa id]: Id(0), field: 22 })", 62 | ]"#]]); 63 | 64 | input.set_field(&mut db).to(23); 65 | // x = (23 + 1) / 2 = 12 66 | // Intermediate result x changes, so final result depends on x 67 | // needs to be recomputed; 68 | assert_eq!(final_result_depends_on_x(&db, input), 24); 69 | db.assert_logs(expect![[r#" 70 | [ 71 | "final_result_depends_on_x(MyInput { [salsa id]: Id(0), field: 23 })", 72 | ]"#]]); 73 | 74 | // y = 23 / 2 = 11 75 | // Intermediate result y is the same, so final result depends on y 76 | // does not need to be recomputed; 77 | assert_eq!(final_result_depends_on_y(&db, input), 22); 78 | db.assert_logs(expect!["[]"]); 79 | } 80 | -------------------------------------------------------------------------------- /tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs: -------------------------------------------------------------------------------- 1 | //! Test that if field X of an input changes but not field Y, 2 | //! functions that depend on X re-execute, but those depending only on Y do not 3 | //! compiles and executes successfully. 4 | #![allow(dead_code)] 5 | 6 | mod common; 7 | use common::LogDatabase; 8 | 9 | use expect_test::expect; 10 | use salsa::Setter; 11 | 12 | #[salsa::input] 13 | struct MyInput { 14 | x: u32, 15 | y: u32, 16 | } 17 | 18 | #[salsa::tracked] 19 | fn result_depends_on_x(db: &dyn LogDatabase, input: MyInput) -> u32 { 20 | db.push_log(format!("result_depends_on_x({:?})", input)); 21 | input.x(db) + 1 22 | } 23 | 24 | #[salsa::tracked] 25 | fn result_depends_on_y(db: &dyn LogDatabase, input: MyInput) -> u32 { 26 | db.push_log(format!("result_depends_on_y({:?})", input)); 27 | input.y(db) - 1 28 | } 29 | #[test] 30 | fn execute() { 31 | // result_depends_on_x = x + 1 32 | // result_depends_on_y = y - 1 33 | let mut db = common::LoggerDatabase::default(); 34 | 35 | let input = MyInput::new(&db, 22, 33); 36 | assert_eq!(result_depends_on_x(&db, input), 23); 37 | db.assert_logs(expect![[r#" 38 | [ 39 | "result_depends_on_x(MyInput { [salsa id]: Id(0), x: 22, y: 33 })", 40 | ]"#]]); 41 | 42 | assert_eq!(result_depends_on_y(&db, input), 32); 43 | db.assert_logs(expect![[r#" 44 | [ 45 | "result_depends_on_y(MyInput { [salsa id]: Id(0), x: 22, y: 33 })", 46 | ]"#]]); 47 | 48 | input.set_x(&mut db).to(23); 49 | // input x changes, so result depends on x needs to be recomputed; 50 | assert_eq!(result_depends_on_x(&db, input), 24); 51 | db.assert_logs(expect![[r#" 52 | [ 53 | "result_depends_on_x(MyInput { [salsa id]: Id(0), x: 23, y: 33 })", 54 | ]"#]]); 55 | 56 | // input y is the same, so result depends on y 57 | // does not need to be recomputed; 58 | assert_eq!(result_depends_on_y(&db, input), 32); 59 | db.assert_logs(expect!["[]"]); 60 | } 61 | -------------------------------------------------------------------------------- /tests/hello_world.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | 4 | mod common; 5 | use common::LogDatabase; 6 | 7 | use expect_test::expect; 8 | use salsa::Setter; 9 | use test_log::test; 10 | 11 | #[salsa::input] 12 | struct MyInput { 13 | field: u32, 14 | } 15 | 16 | #[salsa::tracked] 17 | fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { 18 | db.push_log(format!("final_result({:?})", input)); 19 | intermediate_result(db, input).field(db) * 2 20 | } 21 | 22 | #[salsa::tracked] 23 | struct MyTracked<'db> { 24 | field: u32, 25 | } 26 | 27 | #[salsa::tracked] 28 | fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> { 29 | db.push_log(format!("intermediate_result({:?})", input)); 30 | MyTracked::new(db, input.field(db) / 2) 31 | } 32 | 33 | #[test] 34 | fn execute() { 35 | let mut db = common::LoggerDatabase::default(); 36 | 37 | let input = MyInput::new(&db, 22); 38 | assert_eq!(final_result(&db, input), 22); 39 | db.assert_logs(expect![[r#" 40 | [ 41 | "final_result(MyInput { [salsa id]: Id(0), field: 22 })", 42 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", 43 | ]"#]]); 44 | 45 | // Intermediate result is the same, so final result does 46 | // not need to be recomputed: 47 | input.set_field(&mut db).to(23); 48 | assert_eq!(final_result(&db, input), 22); 49 | db.assert_logs(expect![[r#" 50 | [ 51 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 23 })", 52 | ]"#]]); 53 | 54 | input.set_field(&mut db).to(24); 55 | assert_eq!(final_result(&db, input), 24); 56 | db.assert_logs(expect![[r#" 57 | [ 58 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 24 })", 59 | "final_result(MyInput { [salsa id]: Id(0), field: 24 })", 60 | ]"#]]); 61 | } 62 | 63 | /// Create and mutate a distinct input. No re-execution required. 64 | #[test] 65 | fn red_herring() { 66 | let mut db = common::LoggerDatabase::default(); 67 | 68 | let input = MyInput::new(&db, 22); 69 | assert_eq!(final_result(&db, input), 22); 70 | db.assert_logs(expect![[r#" 71 | [ 72 | "final_result(MyInput { [salsa id]: Id(0), field: 22 })", 73 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", 74 | ]"#]]); 75 | 76 | // Create a distinct input and mutate it. 77 | // This will trigger a new revision in the database 78 | // but shouldn't actually invalidate our existing ones. 79 | let input2 = MyInput::new(&db, 44); 80 | input2.set_field(&mut db).to(66); 81 | 82 | // Re-run the query on the original input. Nothing re-executes! 83 | assert_eq!(final_result(&db, input), 22); 84 | db.assert_logs(expect![[r#" 85 | []"#]]); 86 | } 87 | -------------------------------------------------------------------------------- /tests/input_default.rs: -------------------------------------------------------------------------------- 1 | //! Tests that fields attributed with `#[default]` are initialized with `Default::default()`. 2 | 3 | use salsa::Durability; 4 | use test_log::test; 5 | 6 | #[salsa::input] 7 | struct MyInput { 8 | required: bool, 9 | #[default] 10 | optional: usize, 11 | } 12 | 13 | #[test] 14 | fn new_constructor() { 15 | let db = salsa::DatabaseImpl::new(); 16 | 17 | let input = MyInput::new(&db, true); 18 | 19 | assert!(input.required(&db)); 20 | assert_eq!(input.optional(&db), 0); 21 | } 22 | 23 | #[test] 24 | fn builder_specify_optional() { 25 | let db = salsa::DatabaseImpl::new(); 26 | 27 | let input = MyInput::builder(true).optional(20).new(&db); 28 | 29 | assert!(input.required(&db)); 30 | assert_eq!(input.optional(&db), 20); 31 | } 32 | 33 | #[test] 34 | fn builder_default_optional_value() { 35 | let db = salsa::DatabaseImpl::new(); 36 | 37 | let input = MyInput::builder(true) 38 | .required_durability(Durability::HIGH) 39 | .new(&db); 40 | 41 | assert!(input.required(&db)); 42 | assert_eq!(input.optional(&db), 0); 43 | } 44 | -------------------------------------------------------------------------------- /tests/input_field_durability.rs: -------------------------------------------------------------------------------- 1 | //! Tests that code using the builder's durability methods compiles. 2 | 3 | use salsa::Durability; 4 | use test_log::test; 5 | 6 | #[salsa::input] 7 | struct MyInput { 8 | required_field: bool, 9 | 10 | #[default] 11 | optional_field: usize, 12 | } 13 | 14 | #[test] 15 | fn required_field_durability() { 16 | let db = salsa::DatabaseImpl::new(); 17 | 18 | let input = MyInput::builder(true) 19 | .required_field_durability(Durability::HIGH) 20 | .new(&db); 21 | 22 | assert!(input.required_field(&db)); 23 | assert_eq!(input.optional_field(&db), 0); 24 | } 25 | 26 | #[test] 27 | fn optional_field_durability() { 28 | let db = salsa::DatabaseImpl::new(); 29 | 30 | let input = MyInput::builder(true) 31 | .optional_field(20) 32 | .optional_field_durability(Durability::HIGH) 33 | .new(&db); 34 | 35 | assert!(input.required_field(&db)); 36 | assert_eq!(input.optional_field(&db), 20); 37 | } 38 | -------------------------------------------------------------------------------- /tests/input_setter_preserves_durability.rs: -------------------------------------------------------------------------------- 1 | use test_log::test; 2 | 3 | use salsa::plumbing::ZalsaDatabase; 4 | use salsa::{Durability, Setter}; 5 | 6 | #[salsa::input] 7 | struct MyInput { 8 | required_field: bool, 9 | 10 | #[default] 11 | optional_field: usize, 12 | } 13 | 14 | #[test] 15 | fn execute() { 16 | let mut db = salsa::DatabaseImpl::new(); 17 | 18 | let input = MyInput::builder(true) 19 | .required_field_durability(Durability::HIGH) 20 | .new(&db); 21 | 22 | // Change the field value. It should preserve high durability. 23 | input.set_required_field(&mut db).to(false); 24 | 25 | let last_high_revision = db.zalsa().last_changed_revision(Durability::HIGH); 26 | 27 | // Changing the value again should **again** dump the high durability revision. 28 | input.set_required_field(&mut db).to(false); 29 | 30 | assert_ne!( 31 | db.zalsa().last_changed_revision(Durability::HIGH), 32 | last_high_revision 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /tests/interned-struct-with-lifetime.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | 4 | use expect_test::expect; 5 | use test_log::test; 6 | 7 | #[salsa::interned] 8 | struct InternedString<'db> { 9 | data: String, 10 | } 11 | 12 | #[salsa::interned] 13 | struct InternedPair<'db> { 14 | data: (InternedString<'db>, InternedString<'db>), 15 | } 16 | 17 | #[salsa::tracked] 18 | fn intern_stuff(db: &dyn salsa::Database) -> String { 19 | let s1 = InternedString::new(db, "Hello, ".to_string()); 20 | let s2 = InternedString::new(db, "World, ".to_string()); 21 | let s3 = InternedPair::new(db, (s1, s2)); 22 | format!("{s3:?}") 23 | } 24 | 25 | #[test] 26 | fn execute() { 27 | let db = salsa::DatabaseImpl::new(); 28 | expect![[r#" 29 | "InternedPair { data: (InternedString { data: \"Hello, \" }, InternedString { data: \"World, \" }) }" 30 | "#]].assert_debug_eq(&intern_stuff(&db)); 31 | } 32 | -------------------------------------------------------------------------------- /tests/is_send_sync.rs: -------------------------------------------------------------------------------- 1 | //! Test that a setting a field on a `#[salsa::input]` 2 | //! overwrites and returns the old value. 3 | 4 | use salsa::Database; 5 | use test_log::test; 6 | 7 | #[salsa::input] 8 | struct MyInput { 9 | field: String, 10 | } 11 | 12 | #[salsa::tracked] 13 | struct MyTracked<'db> { 14 | field: MyInterned<'db>, 15 | } 16 | 17 | #[salsa::interned] 18 | struct MyInterned<'db> { 19 | field: String, 20 | } 21 | 22 | #[salsa::tracked] 23 | fn test(db: &dyn Database, input: MyInput) { 24 | let input = is_send_sync(input); 25 | let interned = is_send_sync(MyInterned::new(db, input.field(db).clone())); 26 | let _tracked_struct = is_send_sync(MyTracked::new(db, interned)); 27 | } 28 | 29 | fn is_send_sync(t: T) -> T { 30 | t 31 | } 32 | 33 | #[test] 34 | fn execute() { 35 | let db = salsa::DatabaseImpl::new(); 36 | let input = MyInput::new(&db, "Hello".to_string()); 37 | test(&db, input); 38 | } 39 | -------------------------------------------------------------------------------- /tests/mutate_in_place.rs: -------------------------------------------------------------------------------- 1 | //! Test that a setting a field on a `#[salsa::input]` 2 | //! overwrites and returns the old value. 3 | 4 | use salsa::Setter; 5 | use test_log::test; 6 | 7 | #[salsa::input] 8 | struct MyInput { 9 | field: String, 10 | } 11 | 12 | #[test] 13 | fn execute() { 14 | let mut db = salsa::DatabaseImpl::new(); 15 | 16 | let input = MyInput::new(&db, "Hello".to_string()); 17 | 18 | // Overwrite field with an empty String 19 | // and store the old value in my_string 20 | let mut my_string = input.set_field(&mut db).to(String::new()); 21 | my_string.push_str(" World!"); 22 | 23 | // Set the field back to out initial String, 24 | // expecting to get the empty one back 25 | assert_eq!(input.set_field(&mut db).to(my_string), ""); 26 | 27 | // Check if the stored String is the one we expected 28 | assert_eq!(input.field(&db), "Hello World!"); 29 | } 30 | -------------------------------------------------------------------------------- /tests/override_new_get_set.rs: -------------------------------------------------------------------------------- 1 | //! Test that the `constructor` macro overrides 2 | //! the `new` method's name and `get` and `set` 3 | //! change the name of the getter and setter of the fields. 4 | #![allow(warnings)] 5 | 6 | use std::fmt::Display; 7 | 8 | use salsa::Setter; 9 | 10 | #[salsa::db] 11 | trait Db: salsa::Database {} 12 | 13 | #[salsa::input(constructor = from_string)] 14 | struct MyInput { 15 | #[get(text)] 16 | #[set(set_text)] 17 | field: String, 18 | } 19 | 20 | impl MyInput { 21 | pub fn new(db: &mut dyn Db, s: impl Display) -> MyInput { 22 | MyInput::from_string(db, s.to_string()) 23 | } 24 | 25 | pub fn field(self, db: &dyn Db) -> String { 26 | self.text(db) 27 | } 28 | 29 | pub fn set_field(self, db: &mut dyn Db, id: String) { 30 | self.set_text(db).to(id); 31 | } 32 | } 33 | 34 | #[salsa::interned(constructor = from_string)] 35 | struct MyInterned<'db> { 36 | #[get(text)] 37 | #[return_ref] 38 | field: String, 39 | } 40 | 41 | impl<'db> MyInterned<'db> { 42 | pub fn new(db: &'db dyn Db, s: impl Display) -> MyInterned<'db> { 43 | MyInterned::from_string(db, s.to_string()) 44 | } 45 | 46 | pub fn field(self, db: &'db dyn Db) -> &str { 47 | &self.text(db) 48 | } 49 | } 50 | 51 | #[salsa::tracked(constructor = from_string)] 52 | struct MyTracked<'db> { 53 | #[get(text)] 54 | field: String, 55 | } 56 | 57 | impl<'db> MyTracked<'db> { 58 | pub fn new(db: &'db dyn Db, s: impl Display) -> MyTracked<'db> { 59 | MyTracked::from_string(db, s.to_string()) 60 | } 61 | 62 | pub fn field(self, db: &'db dyn Db) -> String { 63 | self.text(db) 64 | } 65 | } 66 | 67 | #[test] 68 | fn execute() { 69 | salsa::DatabaseImpl::new(); 70 | } 71 | -------------------------------------------------------------------------------- /tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs: -------------------------------------------------------------------------------- 1 | //! Test that creating a tracked struct outside of a 2 | //! tracked function panics with an assert message. 3 | 4 | #[salsa::tracked] 5 | struct MyTracked<'db> { 6 | field: u32, 7 | } 8 | 9 | #[test] 10 | #[should_panic( 11 | expected = "cannot create a tracked struct disambiguator outside of a tracked function" 12 | )] 13 | fn execute() { 14 | let db = salsa::DatabaseImpl::new(); 15 | MyTracked::new(&db, 0); 16 | } 17 | -------------------------------------------------------------------------------- /tests/parallel/main.rs: -------------------------------------------------------------------------------- 1 | mod setup; 2 | 3 | mod parallel_cancellation; 4 | mod parallel_cycle_all_recover; 5 | mod parallel_cycle_mid_recover; 6 | mod parallel_cycle_none_recover; 7 | mod parallel_cycle_one_recover; 8 | mod signal; 9 | -------------------------------------------------------------------------------- /tests/parallel/parallel_cancellation.rs: -------------------------------------------------------------------------------- 1 | //! Test for cycle recover spread across two threads. 2 | //! See `../cycles.rs` for a complete listing of cycle tests, 3 | //! both intra and cross thread. 4 | 5 | use salsa::Cancelled; 6 | use salsa::Setter; 7 | 8 | use crate::setup::Knobs; 9 | use crate::setup::KnobsDatabase; 10 | 11 | #[salsa::input] 12 | struct MyInput { 13 | field: i32, 14 | } 15 | 16 | #[salsa::tracked] 17 | fn a1(db: &dyn KnobsDatabase, input: MyInput) -> MyInput { 18 | db.signal(1); 19 | db.wait_for(2); 20 | dummy(db, input) 21 | } 22 | 23 | #[salsa::tracked] 24 | fn dummy(_db: &dyn KnobsDatabase, _input: MyInput) -> MyInput { 25 | panic!("should never get here!") 26 | } 27 | 28 | // Cancellation signalling test 29 | // 30 | // The pattern is as follows. 31 | // 32 | // Thread A Thread B 33 | // -------- -------- 34 | // a1 35 | // | wait for stage 1 36 | // signal stage 1 set input, triggers cancellation 37 | // wait for stage 2 (blocks) triggering cancellation sends stage 2 38 | // | 39 | // (unblocked) 40 | // dummy 41 | // panics 42 | 43 | #[test] 44 | fn execute() { 45 | let mut db = Knobs::default(); 46 | 47 | let input = MyInput::new(&db, 1); 48 | 49 | let thread_a = std::thread::spawn({ 50 | let db = db.clone(); 51 | move || a1(&db, input) 52 | }); 53 | 54 | db.signal_on_did_cancel.store(2); 55 | input.set_field(&mut db).to(2); 56 | 57 | // Assert thread A *should* was cancelled 58 | let cancelled = thread_a 59 | .join() 60 | .unwrap_err() 61 | .downcast::() 62 | .unwrap(); 63 | 64 | // and inspect the output 65 | expect_test::expect![[r#" 66 | PendingWrite 67 | "#]] 68 | .assert_debug_eq(&cancelled); 69 | } 70 | -------------------------------------------------------------------------------- /tests/parallel/parallel_cycle_none_recover.rs: -------------------------------------------------------------------------------- 1 | //! Test a cycle where no queries recover that occurs across threads. 2 | //! See the `../cycles.rs` for a complete listing of cycle tests, 3 | //! both intra and cross thread. 4 | 5 | use crate::setup::Knobs; 6 | use crate::setup::KnobsDatabase; 7 | use expect_test::expect; 8 | use salsa::Database; 9 | 10 | #[salsa::input] 11 | pub(crate) struct MyInput { 12 | field: i32, 13 | } 14 | 15 | #[salsa::tracked] 16 | pub(crate) fn a(db: &dyn KnobsDatabase, input: MyInput) -> i32 { 17 | // Wait to create the cycle until both threads have entered 18 | db.signal(1); 19 | db.wait_for(2); 20 | 21 | b(db, input) 22 | } 23 | 24 | #[salsa::tracked] 25 | pub(crate) fn b(db: &dyn KnobsDatabase, input: MyInput) -> i32 { 26 | // Wait to create the cycle until both threads have entered 27 | db.wait_for(1); 28 | db.signal(2); 29 | 30 | // Wait for thread A to block on this thread 31 | db.wait_for(3); 32 | 33 | // Now try to execute A 34 | a(db, input) 35 | } 36 | 37 | #[test] 38 | fn execute() { 39 | let db = Knobs::default(); 40 | 41 | let input = MyInput::new(&db, -1); 42 | 43 | let thread_a = std::thread::spawn({ 44 | let db = db.clone(); 45 | db.knobs().signal_on_will_block.store(3); 46 | move || a(&db, input) 47 | }); 48 | 49 | let thread_b = std::thread::spawn({ 50 | let db = db.clone(); 51 | move || b(&db, input) 52 | }); 53 | 54 | // We expect B to panic because it detects a cycle (it is the one that calls A, ultimately). 55 | // Right now, it panics with a string. 56 | let err_b = thread_b.join().unwrap_err(); 57 | db.attach(|_| { 58 | if let Some(c) = err_b.downcast_ref::() { 59 | let expected = expect![[r#" 60 | [ 61 | a(Id(0)), 62 | b(Id(0)), 63 | ] 64 | "#]]; 65 | expected.assert_debug_eq(&c.all_participants(&db)); 66 | } else { 67 | panic!("b failed in an unexpected way: {:?}", err_b); 68 | } 69 | }); 70 | 71 | // We expect A to propagate a panic, which causes us to use the sentinel 72 | // type `Canceled`. 73 | assert!(thread_a 74 | .join() 75 | .unwrap_err() 76 | .downcast_ref::() 77 | .is_some()); 78 | } 79 | -------------------------------------------------------------------------------- /tests/parallel/setup.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crossbeam::atomic::AtomicCell; 4 | use salsa::Database; 5 | 6 | use crate::signal::Signal; 7 | 8 | /// Various "knobs" and utilities used by tests to force 9 | /// a certain behavior. 10 | #[salsa::db] 11 | pub(crate) trait KnobsDatabase: Database { 12 | fn knobs(&self) -> &Knobs; 13 | 14 | fn signal(&self, stage: usize); 15 | 16 | fn wait_for(&self, stage: usize); 17 | } 18 | 19 | /// A database containing various "knobs" that can be used to customize how the queries 20 | /// behave on one specific thread. Note that this state is 21 | /// intentionally thread-local (apart from `signal`). 22 | #[salsa::db] 23 | #[derive(Default)] 24 | pub(crate) struct Knobs { 25 | storage: salsa::Storage, 26 | 27 | /// A kind of flexible barrier used to coordinate execution across 28 | /// threads to ensure we reach various weird states. 29 | pub(crate) signal: Arc, 30 | 31 | /// When this database is about to block, send this signal. 32 | pub(crate) signal_on_will_block: AtomicCell, 33 | 34 | /// When this database has set the cancellation flag, send this signal. 35 | pub(crate) signal_on_did_cancel: AtomicCell, 36 | } 37 | 38 | impl Clone for Knobs { 39 | #[track_caller] 40 | fn clone(&self) -> Self { 41 | // To avoid mistakes, check that when we clone, we haven't customized this behavior yet 42 | assert_eq!(self.signal_on_will_block.load(), 0); 43 | assert_eq!(self.signal_on_did_cancel.load(), 0); 44 | Self { 45 | storage: self.storage.clone(), 46 | signal: self.signal.clone(), 47 | signal_on_will_block: AtomicCell::new(0), 48 | signal_on_did_cancel: AtomicCell::new(0), 49 | } 50 | } 51 | } 52 | 53 | #[salsa::db] 54 | impl salsa::Database for Knobs { 55 | fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) { 56 | let event = event(); 57 | match event.kind { 58 | salsa::EventKind::WillBlockOn { .. } => { 59 | self.signal(self.signal_on_will_block.load()); 60 | } 61 | salsa::EventKind::DidSetCancellationFlag => { 62 | self.signal(self.signal_on_did_cancel.load()); 63 | } 64 | _ => {} 65 | } 66 | } 67 | } 68 | 69 | #[salsa::db] 70 | impl KnobsDatabase for Knobs { 71 | fn knobs(&self) -> &Knobs { 72 | self 73 | } 74 | 75 | fn signal(&self, stage: usize) { 76 | self.signal.signal(stage); 77 | } 78 | 79 | fn wait_for(&self, stage: usize) { 80 | self.signal.wait_for(stage); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/parallel/signal.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::{Condvar, Mutex}; 2 | 3 | #[derive(Default)] 4 | pub(crate) struct Signal { 5 | value: Mutex, 6 | cond_var: Condvar, 7 | } 8 | 9 | impl Signal { 10 | pub(crate) fn signal(&self, stage: usize) { 11 | dbg!(format!("signal({})", stage)); 12 | 13 | // This check avoids acquiring the lock for things that will 14 | // clearly be a no-op. Not *necessary* but helps to ensure we 15 | // are more likely to encounter weird race conditions; 16 | // otherwise calls to `sum` will tend to be unnecessarily 17 | // synchronous. 18 | if stage > 0 { 19 | let mut v = self.value.lock(); 20 | if stage > *v { 21 | *v = stage; 22 | self.cond_var.notify_all(); 23 | } 24 | } 25 | } 26 | 27 | /// Waits until the given condition is true; the fn is invoked 28 | /// with the current stage. 29 | pub(crate) fn wait_for(&self, stage: usize) { 30 | dbg!(format!("wait_for({})", stage)); 31 | 32 | // As above, avoid lock if clearly a no-op. 33 | if stage > 0 { 34 | let mut v = self.value.lock(); 35 | while *v < stage { 36 | self.cond_var.wait(&mut v); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/singleton.rs: -------------------------------------------------------------------------------- 1 | //! Basic Singleton struct test: 2 | //! 3 | //! Singleton structs are created only once. Subsequent `get`s and `new`s after creation return the same `Id`. 4 | 5 | use expect_test::expect; 6 | 7 | use salsa::Database as _; 8 | use test_log::test; 9 | 10 | #[salsa::input(singleton)] 11 | struct MyInput { 12 | field: u32, 13 | id_field: u16, 14 | } 15 | 16 | #[test] 17 | fn basic() { 18 | let db = salsa::DatabaseImpl::new(); 19 | let input1 = MyInput::new(&db, 3, 4); 20 | let input2 = MyInput::get(&db); 21 | 22 | assert_eq!(input1, input2); 23 | 24 | let input3 = MyInput::try_get(&db); 25 | assert_eq!(Some(input1), input3); 26 | } 27 | 28 | #[test] 29 | #[should_panic] 30 | fn twice() { 31 | let db = salsa::DatabaseImpl::new(); 32 | let input1 = MyInput::new(&db, 3, 4); 33 | let input2 = MyInput::get(&db); 34 | 35 | assert_eq!(input1, input2); 36 | 37 | // should panic here 38 | _ = MyInput::new(&db, 3, 5); 39 | } 40 | 41 | #[test] 42 | fn debug() { 43 | salsa::DatabaseImpl::new().attach(|db| { 44 | let input = MyInput::new(db, 3, 4); 45 | let actual = format!("{:?}", input); 46 | let expected = expect!["MyInput { [salsa id]: Id(0), field: 3, id_field: 4 }"]; 47 | expected.assert_eq(&actual); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs: -------------------------------------------------------------------------------- 1 | //! Test that `specify` only works if the key is a tracked struct created in the current query. 2 | //! compilation succeeds but execution panics 3 | #![allow(warnings)] 4 | 5 | #[salsa::input] 6 | struct MyInput { 7 | field: u32, 8 | } 9 | 10 | #[salsa::tracked] 11 | struct MyTracked<'db> { 12 | field: u32, 13 | } 14 | 15 | #[salsa::tracked] 16 | fn tracked_struct_created_in_another_query<'db>( 17 | db: &'db dyn salsa::Database, 18 | input: MyInput, 19 | ) -> MyTracked<'db> { 20 | MyTracked::new(db, input.field(db) * 2) 21 | } 22 | 23 | #[salsa::tracked] 24 | fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { 25 | let t = tracked_struct_created_in_another_query(db, input); 26 | if input.field(db) != 0 { 27 | tracked_fn_extra::specify(db, t, 2222); 28 | } 29 | t 30 | } 31 | 32 | #[salsa::tracked(specify)] 33 | fn tracked_fn_extra<'db>(_db: &'db dyn salsa::Database, _input: MyTracked<'db>) -> u32 { 34 | 0 35 | } 36 | 37 | #[test] 38 | #[should_panic( 39 | expected = "can only use `specify` on salsa structs created during the current tracked fn" 40 | )] 41 | fn execute_when_specified() { 42 | let mut db = salsa::DatabaseImpl::new(); 43 | let input = MyInput::new(&db, 22); 44 | let tracked = tracked_fn(&db, input); 45 | } 46 | -------------------------------------------------------------------------------- /tests/synthetic_write.rs: -------------------------------------------------------------------------------- 1 | //! Test that a constant `tracked` fn (has no inputs) 2 | //! compiles and executes successfully. 3 | #![allow(warnings)] 4 | 5 | mod common; 6 | 7 | use common::{LogDatabase, Logger}; 8 | use expect_test::expect; 9 | use salsa::{Database, DatabaseImpl, Durability, Event, EventKind}; 10 | 11 | #[salsa::input] 12 | struct MyInput { 13 | field: u32, 14 | } 15 | 16 | #[salsa::tracked] 17 | fn tracked_fn(db: &dyn Database, input: MyInput) -> u32 { 18 | input.field(db) * 2 19 | } 20 | 21 | #[test] 22 | fn execute() { 23 | let mut db = common::ExecuteValidateLoggerDatabase::default(); 24 | 25 | let input = MyInput::new(&db, 22); 26 | assert_eq!(tracked_fn(&db, input), 44); 27 | 28 | db.assert_logs(expect![[r#" 29 | [ 30 | "salsa_event(WillExecute { database_key: tracked_fn(Id(0)) })", 31 | ]"#]]); 32 | 33 | // Bumps the revision 34 | db.synthetic_write(Durability::LOW); 35 | 36 | // Query should re-run 37 | assert_eq!(tracked_fn(&db, input), 44); 38 | 39 | db.assert_logs(expect![[r#" 40 | [ 41 | "salsa_event(DidValidateMemoizedValue { database_key: tracked_fn(Id(0)) })", 42 | ]"#]]); 43 | } 44 | -------------------------------------------------------------------------------- /tests/tracked-struct-id-field-bad-eq.rs: -------------------------------------------------------------------------------- 1 | //! Test an id field whose `PartialEq` impl is always true. 2 | 3 | use salsa::{Database, Setter}; 4 | use test_log::test; 5 | 6 | #[salsa::input] 7 | struct MyInput { 8 | field: bool, 9 | } 10 | 11 | #[allow(clippy::derived_hash_with_manual_eq)] 12 | #[derive(Eq, Hash, Debug, Clone)] 13 | struct BadEq { 14 | field: bool, 15 | } 16 | 17 | impl PartialEq for BadEq { 18 | fn eq(&self, _other: &Self) -> bool { 19 | true 20 | } 21 | } 22 | 23 | impl From for BadEq { 24 | fn from(value: bool) -> Self { 25 | Self { field: value } 26 | } 27 | } 28 | 29 | #[salsa::tracked] 30 | struct MyTracked<'db> { 31 | #[id] 32 | field: BadEq, 33 | } 34 | 35 | #[salsa::tracked] 36 | fn the_fn(db: &dyn Database, input: MyInput) { 37 | let tracked0 = MyTracked::new(db, BadEq::from(input.field(db))); 38 | assert_eq!(tracked0.field(db).field, input.field(db)); 39 | } 40 | 41 | #[test] 42 | fn execute() { 43 | let mut db = salsa::DatabaseImpl::new(); 44 | let input = MyInput::new(&db, true); 45 | the_fn(&db, input); 46 | input.set_field(&mut db).to(false); 47 | the_fn(&db, input); 48 | } 49 | -------------------------------------------------------------------------------- /tests/tracked-struct-id-field-bad-hash.rs: -------------------------------------------------------------------------------- 1 | //! Test for a tracked struct where the id field has a 2 | //! very poorly chosen hash impl (always returns 0). 3 | //! This demonstrates that the `#[id]` fields on a struct 4 | //! can change values and yet the struct can have the same 5 | //! id (because struct ids are based on the *hash* of the 6 | //! `#[id]` fields). 7 | 8 | use salsa::{Database as Db, Setter}; 9 | use test_log::test; 10 | 11 | #[salsa::input] 12 | struct MyInput { 13 | field: bool, 14 | } 15 | 16 | #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] 17 | struct BadHash { 18 | field: bool, 19 | } 20 | 21 | impl From for BadHash { 22 | fn from(value: bool) -> Self { 23 | Self { field: value } 24 | } 25 | } 26 | 27 | impl std::hash::Hash for BadHash { 28 | fn hash(&self, state: &mut H) { 29 | state.write_i16(0); 30 | } 31 | } 32 | 33 | #[salsa::tracked] 34 | struct MyTracked<'db> { 35 | #[id] 36 | field: BadHash, 37 | } 38 | 39 | #[salsa::tracked] 40 | fn the_fn(db: &dyn Db, input: MyInput) { 41 | let tracked0 = MyTracked::new(db, BadHash::from(input.field(db))); 42 | assert_eq!(tracked0.field(db).field, input.field(db)); 43 | } 44 | 45 | #[test] 46 | fn execute() { 47 | let mut db = salsa::DatabaseImpl::new(); 48 | 49 | let input = MyInput::new(&db, true); 50 | the_fn(&db, input); 51 | input.set_field(&mut db).to(false); 52 | the_fn(&db, input); 53 | } 54 | -------------------------------------------------------------------------------- /tests/tracked-struct-unchanged-in-new-rev.rs: -------------------------------------------------------------------------------- 1 | use salsa::{Database as Db, Setter}; 2 | use test_log::test; 3 | 4 | #[salsa::input] 5 | struct MyInput { 6 | field: u32, 7 | } 8 | 9 | #[salsa::tracked] 10 | struct MyTracked<'db> { 11 | field: u32, 12 | } 13 | 14 | #[salsa::tracked] 15 | fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked<'_> { 16 | MyTracked::new(db, input.field(db) / 2) 17 | } 18 | 19 | #[test] 20 | fn execute() { 21 | let mut db = salsa::DatabaseImpl::new(); 22 | 23 | let input1 = MyInput::new(&db, 22); 24 | let input2 = MyInput::new(&db, 44); 25 | let _tracked1 = tracked_fn(&db, input1); 26 | let _tracked2 = tracked_fn(&db, input2); 27 | 28 | // modify the input and change the revision 29 | input1.set_field(&mut db).to(24); 30 | let tracked2 = tracked_fn(&db, input2); 31 | 32 | // this should not panic 33 | tracked2.field(&db); 34 | } 35 | -------------------------------------------------------------------------------- /tests/tracked-struct-value-field-bad-eq.rs: -------------------------------------------------------------------------------- 1 | //! Test a field whose `PartialEq` impl is always true. 2 | //! This can result in us getting different results than 3 | //! if we were to execute from scratch. 4 | 5 | use expect_test::expect; 6 | use salsa::{Database, Setter}; 7 | mod common; 8 | use common::LogDatabase; 9 | use test_log::test; 10 | 11 | #[salsa::input] 12 | struct MyInput { 13 | field: bool, 14 | } 15 | 16 | #[allow(clippy::derived_hash_with_manual_eq)] 17 | #[derive(Eq, Hash, Debug, Clone)] 18 | struct BadEq { 19 | field: bool, 20 | } 21 | 22 | impl PartialEq for BadEq { 23 | fn eq(&self, _other: &Self) -> bool { 24 | true 25 | } 26 | } 27 | 28 | impl From for BadEq { 29 | fn from(value: bool) -> Self { 30 | Self { field: value } 31 | } 32 | } 33 | 34 | #[salsa::tracked] 35 | struct MyTracked<'db> { 36 | field: BadEq, 37 | } 38 | 39 | #[salsa::tracked] 40 | fn the_fn(db: &dyn Database, input: MyInput) -> bool { 41 | let tracked = make_tracked_struct(db, input); 42 | read_tracked_struct(db, tracked) 43 | } 44 | 45 | #[salsa::tracked] 46 | fn make_tracked_struct(db: &dyn Database, input: MyInput) -> MyTracked<'_> { 47 | MyTracked::new(db, BadEq::from(input.field(db))) 48 | } 49 | 50 | #[salsa::tracked] 51 | fn read_tracked_struct<'db>(db: &'db dyn Database, tracked: MyTracked<'db>) -> bool { 52 | tracked.field(db).field 53 | } 54 | 55 | #[test] 56 | fn execute() { 57 | let mut db = common::ExecuteValidateLoggerDatabase::default(); 58 | 59 | let input = MyInput::new(&db, true); 60 | let result = the_fn(&db, input); 61 | assert!(result); 62 | 63 | db.assert_logs(expect![[r#" 64 | [ 65 | "salsa_event(WillExecute { database_key: the_fn(Id(0)) })", 66 | "salsa_event(WillExecute { database_key: make_tracked_struct(Id(0)) })", 67 | "salsa_event(WillExecute { database_key: read_tracked_struct(Id(400)) })", 68 | ]"#]]); 69 | 70 | // Update the input to `false` and re-execute. 71 | input.set_field(&mut db).to(false); 72 | let result = the_fn(&db, input); 73 | 74 | // If the `Eq` impl were working properly, we would 75 | // now return `false`. But because the `Eq` is considered 76 | // equal we re-use memoized results and so we get true. 77 | assert!(result); 78 | 79 | db.assert_logs(expect![[r#" 80 | [ 81 | "salsa_event(WillExecute { database_key: make_tracked_struct(Id(0)) })", 82 | "salsa_event(DidValidateMemoizedValue { database_key: read_tracked_struct(Id(400)) })", 83 | "salsa_event(DidValidateMemoizedValue { database_key: the_fn(Id(0)) })", 84 | ]"#]]); 85 | } 86 | -------------------------------------------------------------------------------- /tests/tracked-struct-value-field-not-eq.rs: -------------------------------------------------------------------------------- 1 | //! Test a field whose `PartialEq` impl is always true. 2 | //! This can our "last changed" data to be wrong 3 | //! but we *should* always reflect the final values. 4 | 5 | use salsa::{Database, Setter}; 6 | use test_log::test; 7 | 8 | #[salsa::input] 9 | struct MyInput { 10 | field: bool, 11 | } 12 | 13 | #[derive(Hash, Debug, Clone)] 14 | struct NotEq { 15 | field: bool, 16 | } 17 | 18 | impl From for NotEq { 19 | fn from(value: bool) -> Self { 20 | Self { field: value } 21 | } 22 | } 23 | 24 | #[salsa::tracked] 25 | struct MyTracked<'db> { 26 | #[no_eq] 27 | field: NotEq, 28 | } 29 | 30 | #[salsa::tracked] 31 | fn the_fn(db: &dyn Database, input: MyInput) { 32 | let tracked0 = MyTracked::new(db, NotEq::from(input.field(db))); 33 | assert_eq!(tracked0.field(db).field, input.field(db)); 34 | } 35 | 36 | #[test] 37 | fn execute() { 38 | let mut db = salsa::DatabaseImpl::new(); 39 | 40 | let input = MyInput::new(&db, true); 41 | the_fn(&db, input); 42 | input.set_field(&mut db).to(false); 43 | the_fn(&db, input); 44 | } 45 | -------------------------------------------------------------------------------- /tests/tracked_fn_constant.rs: -------------------------------------------------------------------------------- 1 | //! Test that a constant `tracked` fn (has no inputs) 2 | //! compiles and executes successfully. 3 | #![allow(warnings)] 4 | 5 | use crate::common::LogDatabase; 6 | 7 | mod common; 8 | 9 | #[salsa::tracked] 10 | fn tracked_fn(db: &dyn salsa::Database) -> u32 { 11 | 44 12 | } 13 | 14 | #[salsa::tracked] 15 | fn tracked_custom_db(db: &dyn LogDatabase) -> u32 { 16 | 44 17 | } 18 | 19 | #[test] 20 | fn execute() { 21 | let mut db = salsa::DatabaseImpl::new(); 22 | assert_eq!(tracked_fn(&db), 44); 23 | } 24 | 25 | #[test] 26 | fn execute_custom() { 27 | let mut db = common::LoggerDatabase::default(); 28 | assert_eq!(tracked_custom_db(&db), 44); 29 | } 30 | -------------------------------------------------------------------------------- /tests/tracked_fn_high_durability_dependency.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | 3 | use salsa::plumbing::HasStorage; 4 | use salsa::{Database, Durability, Setter}; 5 | 6 | mod common; 7 | #[salsa::input] 8 | struct MyInput { 9 | field: u32, 10 | } 11 | 12 | #[salsa::tracked] 13 | fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> u32 { 14 | input.field(db) * 2 15 | } 16 | 17 | #[test] 18 | fn execute() { 19 | let mut db = salsa::DatabaseImpl::default(); 20 | 21 | let input_high = MyInput::new(&mut db, 0); 22 | input_high 23 | .set_field(&mut db) 24 | .with_durability(Durability::HIGH) 25 | .to(2200); 26 | 27 | assert_eq!(tracked_fn(&db, input_high), 4400); 28 | 29 | // Changing the value should re-execute the query 30 | input_high 31 | .set_field(&mut db) 32 | .with_durability(Durability::HIGH) 33 | .to(2201); 34 | 35 | assert_eq!(tracked_fn(&db, input_high), 4402); 36 | } 37 | -------------------------------------------------------------------------------- /tests/tracked_fn_no_eq.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use common::LogDatabase; 4 | use expect_test::expect; 5 | use salsa::Setter as _; 6 | 7 | #[salsa::input] 8 | struct Input { 9 | number: i16, 10 | } 11 | 12 | #[salsa::tracked(no_eq)] 13 | fn abs_float(db: &dyn LogDatabase, input: Input) -> f32 { 14 | let number = input.number(db); 15 | 16 | db.push_log(format!("abs_float({number})")); 17 | number.abs() as f32 18 | } 19 | 20 | #[salsa::tracked] 21 | fn derived(db: &dyn LogDatabase, input: Input) -> u32 { 22 | let x = abs_float(db, input); 23 | db.push_log("derived".to_string()); 24 | 25 | x as u32 26 | } 27 | #[test] 28 | fn invoke() { 29 | let mut db = common::LoggerDatabase::default(); 30 | 31 | let input = Input::new(&db, 5); 32 | let x = derived(&db, input); 33 | 34 | assert_eq!(x, 5); 35 | 36 | input.set_number(&mut db).to(-5); 37 | 38 | // Derived should re-execute even the result of `abs_float` is the same. 39 | let x = derived(&db, input); 40 | assert_eq!(x, 5); 41 | 42 | db.assert_logs(expect![[r#" 43 | [ 44 | "abs_float(5)", 45 | "derived", 46 | "abs_float(-5)", 47 | "derived", 48 | ]"#]]); 49 | } 50 | -------------------------------------------------------------------------------- /tests/tracked_fn_on_input.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | #![allow(warnings)] 4 | 5 | #[salsa::input] 6 | struct MyInput { 7 | field: u32, 8 | } 9 | 10 | #[salsa::tracked] 11 | fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> u32 { 12 | input.field(db) * 2 13 | } 14 | 15 | #[test] 16 | fn execute() { 17 | let mut db = salsa::DatabaseImpl::new(); 18 | let input = MyInput::new(&db, 22); 19 | assert_eq!(tracked_fn(&db, input), 44); 20 | } 21 | -------------------------------------------------------------------------------- /tests/tracked_fn_on_input_with_high_durability.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | 3 | use expect_test::expect; 4 | 5 | use common::{EventLoggerDatabase, HasLogger, LogDatabase, Logger}; 6 | use salsa::plumbing::HasStorage; 7 | use salsa::{Database, Durability, Event, EventKind, Setter}; 8 | 9 | mod common; 10 | #[salsa::input] 11 | struct MyInput { 12 | field: u32, 13 | } 14 | 15 | #[salsa::tracked] 16 | fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> u32 { 17 | input.field(db) * 2 18 | } 19 | 20 | #[test] 21 | fn execute() { 22 | let mut db = EventLoggerDatabase::default(); 23 | let input_low = MyInput::new(&db, 22); 24 | let input_high = MyInput::builder(2200).durability(Durability::HIGH).new(&db); 25 | 26 | assert_eq!(tracked_fn(&db, input_low), 44); 27 | assert_eq!(tracked_fn(&db, input_high), 4400); 28 | 29 | db.assert_logs(expect![[r#" 30 | [ 31 | "Event { thread_id: ThreadId(2), kind: WillCheckCancellation }", 32 | "Event { thread_id: ThreadId(2), kind: WillExecute { database_key: tracked_fn(Id(0)) } }", 33 | "Event { thread_id: ThreadId(2), kind: WillCheckCancellation }", 34 | "Event { thread_id: ThreadId(2), kind: WillExecute { database_key: tracked_fn(Id(1)) } }", 35 | ]"#]]); 36 | 37 | db.synthetic_write(Durability::LOW); 38 | 39 | assert_eq!(tracked_fn(&db, input_low), 44); 40 | assert_eq!(tracked_fn(&db, input_high), 4400); 41 | 42 | // FIXME: There's currently no good way to verify whether an input was validated using shallow or deep comparison. 43 | // All we can do for now is verify that the values were validated. 44 | // Note: It maybe confusing why it validates `input_high` when the write has `Durability::LOW`. 45 | // This is because all values must be validated whenever a write occurs. It doesn't mean that it 46 | // executed the query. 47 | db.assert_logs(expect![[r#" 48 | [ 49 | "Event { thread_id: ThreadId(2), kind: DidSetCancellationFlag }", 50 | "Event { thread_id: ThreadId(2), kind: WillCheckCancellation }", 51 | "Event { thread_id: ThreadId(2), kind: DidValidateMemoizedValue { database_key: tracked_fn(Id(0)) } }", 52 | "Event { thread_id: ThreadId(2), kind: WillCheckCancellation }", 53 | "Event { thread_id: ThreadId(2), kind: DidValidateMemoizedValue { database_key: tracked_fn(Id(1)) } }", 54 | ]"#]]); 55 | } 56 | -------------------------------------------------------------------------------- /tests/tracked_fn_on_interned.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::interned` 2 | //! compiles and executes successfully. 3 | 4 | #[salsa::interned] 5 | struct Name<'db> { 6 | name: String, 7 | } 8 | 9 | #[salsa::tracked] 10 | fn tracked_fn<'db>(db: &'db dyn salsa::Database, name: Name<'db>) -> String { 11 | name.name(db).clone() 12 | } 13 | 14 | #[test] 15 | fn execute() { 16 | let db = salsa::DatabaseImpl::new(); 17 | let name = Name::new(&db, "Salsa".to_string()); 18 | 19 | assert_eq!(tracked_fn(&db, name), "Salsa"); 20 | } 21 | -------------------------------------------------------------------------------- /tests/tracked_fn_on_tracked.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | 4 | #[salsa::input] 5 | struct MyInput { 6 | field: u32, 7 | } 8 | 9 | #[salsa::tracked] 10 | struct MyTracked<'db> { 11 | field: u32, 12 | } 13 | 14 | #[salsa::tracked] 15 | fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> MyTracked<'_> { 16 | MyTracked::new(db, input.field(db) * 2) 17 | } 18 | 19 | #[test] 20 | fn execute() { 21 | let db = salsa::DatabaseImpl::new(); 22 | let input = MyInput::new(&db, 22); 23 | assert_eq!(tracked_fn(&db, input).field(&db), 44); 24 | } 25 | -------------------------------------------------------------------------------- /tests/tracked_fn_on_tracked_specify.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | #![allow(warnings)] 4 | 5 | #[salsa::input] 6 | struct MyInput { 7 | field: u32, 8 | } 9 | 10 | #[salsa::tracked] 11 | struct MyTracked<'db> { 12 | field: u32, 13 | } 14 | 15 | #[salsa::tracked] 16 | fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { 17 | let t = MyTracked::new(db, input.field(db) * 2); 18 | if input.field(db) != 0 { 19 | tracked_fn_extra::specify(db, t, 2222); 20 | } 21 | t 22 | } 23 | 24 | #[salsa::tracked(specify)] 25 | fn tracked_fn_extra<'db>(_db: &'db dyn salsa::Database, _input: MyTracked<'db>) -> u32 { 26 | 0 27 | } 28 | 29 | #[test] 30 | fn execute_when_specified() { 31 | let mut db = salsa::DatabaseImpl::new(); 32 | let input = MyInput::new(&db, 22); 33 | let tracked = tracked_fn(&db, input); 34 | assert_eq!(tracked.field(&db), 44); 35 | assert_eq!(tracked_fn_extra(&db, tracked), 2222); 36 | } 37 | 38 | #[test] 39 | fn execute_when_not_specified() { 40 | let mut db = salsa::DatabaseImpl::new(); 41 | let input = MyInput::new(&db, 0); 42 | let tracked = tracked_fn(&db, input); 43 | assert_eq!(tracked.field(&db), 0); 44 | assert_eq!(tracked_fn_extra(&db, tracked), 0); 45 | } 46 | -------------------------------------------------------------------------------- /tests/tracked_fn_read_own_specify.rs: -------------------------------------------------------------------------------- 1 | use expect_test::expect; 2 | mod common; 3 | use common::LogDatabase; 4 | use salsa::Database; 5 | 6 | #[salsa::input] 7 | struct MyInput { 8 | field: u32, 9 | } 10 | 11 | #[salsa::tracked] 12 | struct MyTracked<'db> { 13 | field: u32, 14 | } 15 | 16 | #[salsa::tracked] 17 | fn tracked_fn(db: &dyn LogDatabase, input: MyInput) -> u32 { 18 | db.push_log(format!("tracked_fn({input:?})")); 19 | let t = MyTracked::new(db, input.field(db) * 2); 20 | tracked_fn_extra::specify(db, t, 2222); 21 | tracked_fn_extra(db, t) 22 | } 23 | 24 | #[salsa::tracked(specify)] 25 | fn tracked_fn_extra<'db>(db: &dyn LogDatabase, input: MyTracked<'db>) -> u32 { 26 | db.push_log(format!("tracked_fn_extra({input:?})")); 27 | 0 28 | } 29 | 30 | #[test] 31 | fn execute() { 32 | let mut db = common::LoggerDatabase::default(); 33 | let input = MyInput::new(&db, 22); 34 | assert_eq!(tracked_fn(&db, input), 2222); 35 | db.assert_logs(expect![[r#" 36 | [ 37 | "tracked_fn(MyInput { [salsa id]: Id(0), field: 22 })", 38 | ]"#]]); 39 | 40 | // A "synthetic write" causes the system to act *as though* some 41 | // input of durability `durability` has changed. 42 | db.synthetic_write(salsa::Durability::LOW); 43 | 44 | // Re-run the query on the original input. Nothing re-executes! 45 | assert_eq!(tracked_fn(&db, input), 2222); 46 | db.assert_logs(expect!["[]"]); 47 | } 48 | -------------------------------------------------------------------------------- /tests/tracked_fn_return_ref.rs: -------------------------------------------------------------------------------- 1 | use salsa::Database; 2 | 3 | #[salsa::input] 4 | struct Input { 5 | number: usize, 6 | } 7 | 8 | #[salsa::tracked(return_ref)] 9 | fn test(db: &dyn salsa::Database, input: Input) -> Vec { 10 | (0..input.number(db)) 11 | .map(|i| format!("test {}", i)) 12 | .collect() 13 | } 14 | 15 | #[test] 16 | fn invoke() { 17 | salsa::DatabaseImpl::new().attach(|db| { 18 | let input = Input::new(db, 3); 19 | let x: &Vec = test(db, input); 20 | expect_test::expect![[r#" 21 | [ 22 | "test 0", 23 | "test 1", 24 | "test 2", 25 | ] 26 | "#]] 27 | .assert_debug_eq(x); 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /tests/tracked_method.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | #![allow(warnings)] 4 | 5 | trait TrackedTrait { 6 | fn tracked_trait_fn(self, db: &dyn salsa::Database) -> u32; 7 | } 8 | 9 | #[salsa::input] 10 | struct MyInput { 11 | field: u32, 12 | } 13 | 14 | #[salsa::tracked] 15 | impl MyInput { 16 | #[salsa::tracked] 17 | fn tracked_fn(self, db: &dyn salsa::Database) -> u32 { 18 | self.field(db) * 2 19 | } 20 | 21 | #[salsa::tracked(return_ref)] 22 | fn tracked_fn_ref(self, db: &dyn salsa::Database) -> u32 { 23 | self.field(db) * 3 24 | } 25 | } 26 | 27 | #[salsa::tracked] 28 | impl TrackedTrait for MyInput { 29 | #[salsa::tracked] 30 | fn tracked_trait_fn(self, db: &dyn salsa::Database) -> u32 { 31 | self.field(db) * 4 32 | } 33 | } 34 | 35 | #[test] 36 | fn execute() { 37 | let mut db = salsa::DatabaseImpl::new(); 38 | let object = MyInput::new(&mut db, 22); 39 | // assert_eq!(object.tracked_fn(&db), 44); 40 | // assert_eq!(*object.tracked_fn_ref(&db), 66); 41 | assert_eq!(object.tracked_trait_fn(&db), 88); 42 | } 43 | -------------------------------------------------------------------------------- /tests/tracked_method_inherent_return_ref.rs: -------------------------------------------------------------------------------- 1 | use salsa::Database; 2 | 3 | #[salsa::input] 4 | struct Input { 5 | number: usize, 6 | } 7 | 8 | #[salsa::tracked] 9 | impl Input { 10 | #[salsa::tracked(return_ref)] 11 | fn test(self, db: &dyn salsa::Database) -> Vec { 12 | (0..self.number(db)) 13 | .map(|i| format!("test {}", i)) 14 | .collect() 15 | } 16 | } 17 | 18 | #[test] 19 | fn invoke() { 20 | salsa::DatabaseImpl::new().attach(|db| { 21 | let input = Input::new(db, 3); 22 | let x: &Vec = input.test(db); 23 | expect_test::expect![[r#" 24 | [ 25 | "test 0", 26 | "test 1", 27 | "test 2", 28 | ] 29 | "#]] 30 | .assert_debug_eq(x); 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /tests/tracked_method_on_tracked_struct.rs: -------------------------------------------------------------------------------- 1 | use salsa::Database; 2 | 3 | #[derive(Debug, PartialEq, Eq, Hash)] 4 | pub struct Item {} 5 | 6 | #[salsa::input] 7 | pub struct Input { 8 | name: String, 9 | } 10 | 11 | #[salsa::tracked] 12 | impl Input { 13 | #[salsa::tracked] 14 | pub fn source_tree(self, db: &dyn Database) -> SourceTree<'_> { 15 | SourceTree::new(db, self.name(db).clone()) 16 | } 17 | } 18 | 19 | #[salsa::tracked] 20 | pub struct SourceTree<'db> { 21 | name: String, 22 | } 23 | 24 | #[salsa::tracked] 25 | impl<'db1> SourceTree<'db1> { 26 | #[salsa::tracked(return_ref)] 27 | pub fn inherent_item_name(self, db: &'db1 dyn Database) -> String { 28 | self.name(db) 29 | } 30 | } 31 | 32 | trait ItemName<'db1> { 33 | fn trait_item_name(self, db: &'db1 dyn Database) -> &'db1 String; 34 | } 35 | 36 | #[salsa::tracked] 37 | impl<'db1> ItemName<'db1> for SourceTree<'db1> { 38 | #[salsa::tracked(return_ref)] 39 | fn trait_item_name(self, db: &'db1 dyn Database) -> String { 40 | self.name(db) 41 | } 42 | } 43 | 44 | #[test] 45 | fn test_inherent() { 46 | salsa::DatabaseImpl::new().attach(|db| { 47 | let input = Input::new(db, "foo".to_string()); 48 | let source_tree = input.source_tree(db); 49 | expect_test::expect![[r#" 50 | "foo" 51 | "#]] 52 | .assert_debug_eq(source_tree.inherent_item_name(db)); 53 | }) 54 | } 55 | 56 | #[test] 57 | fn test_trait() { 58 | salsa::DatabaseImpl::new().attach(|db| { 59 | let input = Input::new(db, "foo".to_string()); 60 | let source_tree = input.source_tree(db); 61 | expect_test::expect![[r#" 62 | "foo" 63 | "#]] 64 | .assert_debug_eq(source_tree.trait_item_name(db)); 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /tests/tracked_method_trait_return_ref.rs: -------------------------------------------------------------------------------- 1 | use salsa::Database; 2 | 3 | #[salsa::input] 4 | struct Input { 5 | number: usize, 6 | } 7 | 8 | trait Trait { 9 | fn test(self, db: &dyn salsa::Database) -> &Vec; 10 | } 11 | 12 | #[salsa::tracked] 13 | impl Trait for Input { 14 | #[salsa::tracked(return_ref)] 15 | fn test(self, db: &dyn salsa::Database) -> Vec { 16 | (0..self.number(db)) 17 | .map(|i| format!("test {}", i)) 18 | .collect() 19 | } 20 | } 21 | 22 | #[test] 23 | fn invoke() { 24 | salsa::DatabaseImpl::new().attach(|db| { 25 | let input = Input::new(db, 3); 26 | let x: &Vec = input.test(db); 27 | expect_test::expect![[r#" 28 | [ 29 | "test 0", 30 | "test 1", 31 | "test 2", 32 | ] 33 | "#]] 34 | .assert_debug_eq(x); 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /tests/tracked_struct_db1_lt.rs: -------------------------------------------------------------------------------- 1 | //! Test that tracked structs with lifetimes not named `'db` 2 | //! compile successfully. 3 | 4 | mod common; 5 | 6 | use test_log::test; 7 | 8 | #[salsa::input] 9 | struct MyInput { 10 | field: u32, 11 | } 12 | 13 | #[salsa::tracked] 14 | struct MyTracked1<'db1> { 15 | field: MyTracked2<'db1>, 16 | } 17 | 18 | #[salsa::tracked] 19 | struct MyTracked2<'db2> { 20 | field: u32, 21 | } 22 | 23 | #[test] 24 | fn create_db() {} 25 | -------------------------------------------------------------------------------- /tests/tracked_with_intern.rs: -------------------------------------------------------------------------------- 1 | //! Test that a setting a field on a `#[salsa::input]` 2 | //! overwrites and returns the old value. 3 | 4 | use test_log::test; 5 | 6 | #[salsa::input] 7 | struct MyInput { 8 | field: String, 9 | } 10 | 11 | #[salsa::tracked] 12 | struct MyTracked<'db> { 13 | field: MyInterned<'db>, 14 | } 15 | 16 | #[salsa::interned] 17 | struct MyInterned<'db> { 18 | field: String, 19 | } 20 | 21 | #[test] 22 | fn execute() {} 23 | -------------------------------------------------------------------------------- /tests/tracked_with_struct_db.rs: -------------------------------------------------------------------------------- 1 | //! Test that a setting a field on a `#[salsa::input]` 2 | //! overwrites and returns the old value. 3 | 4 | use salsa::{Database, DatabaseImpl}; 5 | use test_log::test; 6 | 7 | #[salsa::input] 8 | struct MyInput { 9 | field: String, 10 | } 11 | 12 | #[salsa::tracked] 13 | struct MyTracked<'db> { 14 | data: MyInput, 15 | next: MyList<'db>, 16 | } 17 | 18 | #[derive(PartialEq, Eq, Clone, Debug, salsa::Update)] 19 | enum MyList<'db> { 20 | None, 21 | Next(MyTracked<'db>), 22 | } 23 | 24 | #[salsa::tracked] 25 | fn create_tracked_list(db: &dyn Database, input: MyInput) -> MyTracked<'_> { 26 | let t0 = MyTracked::new(db, input, MyList::None); 27 | let t1 = MyTracked::new(db, input, MyList::Next(t0)); 28 | t1 29 | } 30 | 31 | #[test] 32 | fn execute() { 33 | DatabaseImpl::new().attach(|db| { 34 | let input = MyInput::new(db, "foo".to_string()); 35 | let t0: MyTracked = create_tracked_list(db, input); 36 | let t1 = create_tracked_list(db, input); 37 | expect_test::expect![[r#" 38 | MyTracked { 39 | [salsa id]: Id(401), 40 | data: MyInput { 41 | [salsa id]: Id(0), 42 | field: "foo", 43 | }, 44 | next: Next( 45 | MyTracked { 46 | [salsa id]: Id(400), 47 | data: MyInput { 48 | [salsa id]: Id(0), 49 | field: "foo", 50 | }, 51 | next: None, 52 | }, 53 | ), 54 | } 55 | "#]] 56 | .assert_debug_eq(&t0); 57 | assert_eq!(t0, t1); 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /tests/warnings/main.rs: -------------------------------------------------------------------------------- 1 | //! Test that macros don't generate code with warnings 2 | 3 | #![deny(warnings)] 4 | 5 | mod needless_borrow; 6 | mod needless_lifetimes; 7 | mod unused_variable_db; 8 | -------------------------------------------------------------------------------- /tests/warnings/needless_borrow.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 2 | enum Token {} 3 | 4 | #[salsa::tracked] 5 | struct TokenTree<'db> { 6 | #[return_ref] 7 | tokens: Vec, 8 | } 9 | -------------------------------------------------------------------------------- /tests/warnings/needless_lifetimes.rs: -------------------------------------------------------------------------------- 1 | #[salsa::db] 2 | pub trait Db: salsa::Database {} 3 | 4 | #[derive(Debug, PartialEq, Eq, Hash)] 5 | pub struct Item {} 6 | 7 | #[salsa::tracked] 8 | pub struct SourceTree<'db> {} 9 | 10 | #[salsa::tracked] 11 | impl<'db> SourceTree<'db> { 12 | #[salsa::tracked(return_ref)] 13 | pub fn all_items(self, _db: &'db dyn Db) -> Vec { 14 | todo!() 15 | } 16 | } 17 | 18 | #[salsa::tracked(return_ref)] 19 | fn use_tree<'db>(_db: &'db dyn Db, _tree: SourceTree<'db>) {} 20 | 21 | #[allow(unused)] 22 | fn use_it(db: &dyn Db, tree: SourceTree) { 23 | tree.all_items(db); 24 | use_tree(db, tree); 25 | } 26 | -------------------------------------------------------------------------------- /tests/warnings/unused_variable_db.rs: -------------------------------------------------------------------------------- 1 | #[salsa::interned] 2 | struct Keywords<'db> {} 3 | --------------------------------------------------------------------------------