├── .devcontainer └── devcontainer.json ├── .dir-locals.el ├── .github ├── dependabot.yml └── workflows │ ├── book.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── FAQ.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASES.md ├── benches ├── accumulator.rs ├── compare.rs ├── dataflow.rs ├── incremental.rs └── shims │ └── global_alloc_overwrite.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 │ ├── 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 │ ├── 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 │ ├── parser.md │ └── structure.md │ └── videos.md ├── components ├── salsa-macro-rules │ ├── CHANGELOG.md │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── macro_if.rs │ │ ├── maybe_backdate.rs │ │ ├── maybe_default.rs │ │ ├── return_mode.rs │ │ ├── setup_accumulator_impl.rs │ │ ├── setup_input_struct.rs │ │ ├── setup_interned_struct.rs │ │ ├── setup_tracked_assoc_fn_body.rs │ │ ├── setup_tracked_fn.rs │ │ ├── setup_tracked_method_body.rs │ │ ├── setup_tracked_struct.rs │ │ └── unexpected_cycle_recovery.rs └── salsa-macros │ ├── CHANGELOG.md │ ├── 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 │ ├── supertype.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 ├── release-plz.toml ├── src ├── accumulator.rs ├── accumulator │ ├── accumulated.rs │ └── accumulated_map.rs ├── active_query.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 │ └── sync.rs ├── hash.rs ├── id.rs ├── ingredient.rs ├── input.rs ├── input │ ├── input_field.rs │ ├── setter.rs │ └── singleton.rs ├── interned.rs ├── key.rs ├── lib.rs ├── memo_ingredient_indices.rs ├── nonce.rs ├── parallel.rs ├── return_mode.rs ├── revision.rs ├── runtime.rs ├── runtime │ └── dependency_graph.rs ├── salsa_struct.rs ├── storage.rs ├── sync.rs ├── table.rs ├── table │ └── memo.rs ├── tracked_struct.rs ├── tracked_struct │ └── tracked_field.rs ├── update.rs ├── views.rs ├── zalsa.rs └── zalsa_local.rs └── tests ├── accumulate-chain.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 ├── accumulated_backdate.rs ├── backtrace.rs ├── check_auto_traits.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 ├── invalid_return_mode.rs ├── invalid_return_mode.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_fn_return_not_update.rs ├── tracked_fn_return_not_update.stderr ├── tracked_fn_return_ref.rs ├── tracked_fn_return_ref.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 ├── tracked_struct_not_update.rs └── tracked_struct_not_update.stderr ├── compile_fail.rs ├── cycle.rs ├── cycle_accumulate.rs ├── cycle_fallback_immediate.rs ├── cycle_initial_call_back_into_cycle.rs ├── cycle_initial_call_query.rs ├── cycle_maybe_changed_after.rs ├── cycle_output.rs ├── cycle_recovery_call_back_into_cycle.rs ├── cycle_recovery_call_query.rs ├── cycle_regression_455.rs ├── cycle_result_dependencies.rs ├── cycle_tracked.rs ├── cycle_tracked_own_input.rs ├── dataflow.rs ├── debug.rs ├── debug_db_contents.rs ├── deletion-cascade.rs ├── deletion-drops.rs ├── deletion.rs ├── durability.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 ├── hash_collision.rs ├── hello_world.rs ├── input_default.rs ├── input_field_durability.rs ├── input_setter_preserves_durability.rs ├── intern_access_in_different_revision.rs ├── interned-revisions.rs ├── interned-structs.rs ├── interned-structs_self_ref.rs ├── lru.rs ├── mutate_in_place.rs ├── override_new_get_set.rs ├── panic-when-creating-tracked-struct-outside-of-tracked-fn.rs ├── parallel ├── cycle_a_t1_b_t2.rs ├── cycle_a_t1_b_t2_fallback.rs ├── cycle_ab_peeping_c.rs ├── cycle_nested_deep.rs ├── cycle_nested_deep_conditional.rs ├── cycle_nested_three_threads.rs ├── cycle_panic.rs ├── cycle_provisional_depending_on_itself.rs ├── main.rs ├── parallel_cancellation.rs ├── parallel_join.rs ├── parallel_map.rs ├── setup.rs └── signal.rs ├── preverify-struct-with-leaked-data-2.rs ├── preverify-struct-with-leaked-data.rs ├── return_mode.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_assoc_fn.rs ├── tracked_fn_constant.rs ├── tracked_fn_high_durability_dependency.rs ├── tracked_fn_interned_lifetime.rs ├── tracked_fn_multiple_args.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_interned_enum.rs ├── tracked_fn_on_tracked.rs ├── tracked_fn_on_tracked_specify.rs ├── tracked_fn_orphan_escape_hatch.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_deref.rs ├── tracked_method_inherent_return_ref.rs ├── tracked_method_on_tracked_struct.rs ├── tracked_method_trait_return_ref.rs ├── tracked_method_with_self_ty.rs ├── tracked_struct.rs ├── tracked_struct_db1_lt.rs ├── tracked_struct_disambiguates.rs ├── tracked_struct_durability.rs ├── tracked_struct_mixed_tracked_fields.rs ├── tracked_struct_recreate_new_revision.rs ├── tracked_struct_with_interned_query.rs ├── tracked_with_intern.rs ├── tracked_with_struct_db.rs ├── tracked_with_struct_ord.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 | 12 | jobs: 13 | book: 14 | name: Book 15 | runs-on: ubuntu-latest 16 | env: 17 | MDBOOK_VERSION: "0.4.40" 18 | MDBOOK_LINKCHECK_VERSION: "0.7.7" 19 | MDBOOK_MERMAID_VERSION: "0.13.0" 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Install mdbook 23 | run: | 24 | 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 25 | 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 26 | 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 27 | unzip mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -d ~/.cargo/bin 28 | chmod +x ~/.cargo/bin/mdbook-linkcheck 29 | - name: Setup Pages 30 | id: pages 31 | uses: actions/configure-pages@v5 32 | - name: Build 33 | run: mdbook build 34 | working-directory: book 35 | - name: Upload static files as artifact 36 | id: deployment 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | path: ./book/book/html 40 | deploy: 41 | name: Deploy 42 | runs-on: ubuntu-latest 43 | needs: book 44 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 45 | concurrency: 46 | group: github-pages 47 | cancel-in-progress: true 48 | permissions: 49 | contents: read 50 | pages: write 51 | id-token: write 52 | environment: 53 | name: github-pages 54 | url: ${{ steps.deployment.outputs.page_url }} 55 | steps: 56 | - name: Deploy to GitHub Pages 57 | id: deployment 58 | uses: actions/deploy-pages@v4 59 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | # Release unpublished packages. 14 | release-plz-release: 15 | if: ${{ github.repository_owner == 'salsa-rs' }} 16 | name: Release-plz release 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: Install Rust toolchain 26 | uses: dtolnay/rust-toolchain@stable 27 | - name: Run release-plz 28 | uses: release-plz/action@v0.5 29 | with: 30 | command: release 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 34 | 35 | # Create a PR with the new versions and changelog, preparing the next release. 36 | release-plz-pr: 37 | if: ${{ github.repository_owner == 'salsa-rs' }} 38 | name: Release-plz PR 39 | runs-on: ubuntu-latest 40 | permissions: 41 | contents: write 42 | pull-requests: write 43 | concurrency: 44 | group: release-plz-${{ github.ref }} 45 | cancel-in-progress: false 46 | steps: 47 | - name: Checkout repository 48 | uses: actions/checkout@v4 49 | with: 50 | fetch-depth: 0 51 | - name: Install Rust toolchain 52 | uses: dtolnay/rust-toolchain@stable 53 | - name: Run release-plz 54 | uses: release-plz/action@v0.5 55 | with: 56 | command: release-pr 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | TAGS 5 | nikom 6 | .idea 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/accumulator.rs: -------------------------------------------------------------------------------- 1 | use std::hint::black_box; 2 | 3 | use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Criterion}; 4 | use salsa::Accumulator; 5 | 6 | include!("shims/global_alloc_overwrite.rs"); 7 | 8 | #[salsa::input] 9 | struct Input { 10 | expressions: usize, 11 | } 12 | 13 | #[allow(dead_code)] 14 | #[salsa::accumulator] 15 | struct Diagnostic(String); 16 | 17 | #[salsa::interned] 18 | struct Expression<'db> { 19 | number: usize, 20 | } 21 | 22 | #[salsa::tracked] 23 | #[inline(never)] 24 | fn root<'db>(db: &'db dyn salsa::Database, input: Input) -> Vec { 25 | (0..input.expressions(db)) 26 | .map(|i| infer_expression(db, Expression::new(db, i))) 27 | .collect() 28 | } 29 | 30 | #[salsa::tracked] 31 | #[inline(never)] 32 | fn infer_expression<'db>(db: &'db dyn salsa::Database, expression: Expression<'db>) -> usize { 33 | let number = expression.number(db); 34 | 35 | if number % 10 == 0 { 36 | Diagnostic(format!("Number is {number}")).accumulate(db); 37 | } 38 | 39 | if number != 0 && number % 2 == 0 { 40 | let sub_expression = Expression::new(db, number / 2); 41 | let _ = infer_expression(db, sub_expression); 42 | } 43 | 44 | number 45 | } 46 | 47 | fn accumulator(criterion: &mut Criterion) { 48 | criterion.bench_function("accumulator", |b| { 49 | b.iter_batched_ref( 50 | || { 51 | let db = salsa::DatabaseImpl::new(); 52 | 53 | let input = Input::new(black_box(&db), black_box(10_000)); 54 | 55 | // Pre-warm 56 | let result = root(black_box(&db), black_box(input)); 57 | assert!(!black_box(result).is_empty()); 58 | 59 | (db, input) 60 | }, 61 | |(db, input)| { 62 | // Measure the cost of collecting accumulators ignoring the cost of running the 63 | // query itself. 64 | let diagnostics = root::accumulated::(black_box(db), *black_box(input)); 65 | 66 | assert_eq!(black_box(diagnostics).len(), 1000); 67 | }, 68 | BatchSize::SmallInput, 69 | ); 70 | }); 71 | } 72 | 73 | criterion_group!(benches, accumulator); 74 | criterion_main!(benches); 75 | -------------------------------------------------------------------------------- /benches/incremental.rs: -------------------------------------------------------------------------------- 1 | use std::hint::black_box; 2 | 3 | use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Criterion}; 4 | use salsa::Setter; 5 | 6 | include!("shims/global_alloc_overwrite.rs"); 7 | 8 | #[salsa::input] 9 | struct Input { 10 | field: usize, 11 | } 12 | 13 | #[salsa::tracked] 14 | struct Tracked<'db> { 15 | number: usize, 16 | } 17 | 18 | #[salsa::tracked(returns(ref))] 19 | #[inline(never)] 20 | fn index<'db>(db: &'db dyn salsa::Database, input: Input) -> Vec> { 21 | (0..input.field(db)).map(|i| Tracked::new(db, i)).collect() 22 | } 23 | 24 | #[salsa::tracked] 25 | #[inline(never)] 26 | fn root(db: &dyn salsa::Database, input: Input) -> usize { 27 | let index = index(db, input); 28 | index.len() 29 | } 30 | 31 | fn many_tracked_structs(criterion: &mut Criterion) { 32 | criterion.bench_function("many_tracked_structs", |b| { 33 | b.iter_batched_ref( 34 | || { 35 | let db = salsa::DatabaseImpl::new(); 36 | 37 | let input = Input::new(black_box(&db), black_box(1_000)); 38 | let input2 = Input::new(black_box(&db), black_box(1)); 39 | 40 | // prewarm cache 41 | let root1 = root(black_box(&db), black_box(input)); 42 | assert_eq!(black_box(root1), 1_000); 43 | let root2 = root(black_box(&db), black_box(input2)); 44 | assert_eq!(black_box(root2), 1); 45 | 46 | (db, input, input2) 47 | }, 48 | |(db, input, input2)| { 49 | // Make a change, but fetch the result for the other input 50 | input2.set_field(black_box(db)).to(black_box(2)); 51 | 52 | let result = root(black_box(db), *black_box(input)); 53 | 54 | assert_eq!(black_box(result), 1_000); 55 | }, 56 | BatchSize::LargeInput, 57 | ); 58 | }); 59 | } 60 | 61 | criterion_group!(benches, many_tracked_structs); 62 | criterion_main!(benches); 63 | -------------------------------------------------------------------------------- /benches/shims/global_alloc_overwrite.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all( 2 | not(target_os = "windows"), 3 | not(target_os = "openbsd"), 4 | any( 5 | target_arch = "x86_64", 6 | target_arch = "aarch64", 7 | target_arch = "powerpc64" 8 | ) 9 | ))] 10 | #[global_allocator] 11 | static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; 12 | 13 | // Disable decay after 10s because it can show up as *random* slow allocations 14 | // in benchmarks. We don't need purging in benchmarks because it isn't important 15 | // to give unallocated pages back to the OS. 16 | // https://jemalloc.net/jemalloc.3.html#opt.dirty_decay_ms 17 | #[cfg(all( 18 | not(target_os = "windows"), 19 | not(target_os = "openbsd"), 20 | any( 21 | target_arch = "x86_64", 22 | target_arch = "aarch64", 23 | target_arch = "powerpc64" 24 | ) 25 | ))] 26 | #[allow(non_upper_case_globals)] 27 | #[export_name = "_rjem_malloc_conf"] 28 | #[allow(unsafe_code)] 29 | pub static _rjem_malloc_conf: &[u8] = b"dirty_decay_ms:-1,muzzy_decay_ms:-1\0"; 30 | -------------------------------------------------------------------------------- /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 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/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 [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. 11 | - 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: 12 | - The [maybe changed after](./plumbing/maybe_changed_after.md) operation determines when a memoized value for a tracked function is out of date. 13 | - The [fetch](./plumbing/fetch.md) operation computes the most recent value. 14 | - The [derived queries flowchart](./plumbing/derived_flowchart.md) depicts the logic in flowchart form. 15 | - The [cycle handling](./plumbing/cycles.md) handling chapter describes what happens when cycles occur. 16 | - The [terminology](./plumbing/terminology.md) section describes various words that appear throughout. 17 | -------------------------------------------------------------------------------- /book/src/plumbing/cycles.md: -------------------------------------------------------------------------------- 1 | # Cycles 2 | 3 | ## Cross-thread blocking 4 | 5 | The interface for blocking across threads now works as follows: 6 | 7 | * When one thread `T1` wishes to block on a query `Q` being executed by another thread `T2`, it invokes `Runtime::try_block_on`. This will check for cycles. Assuming no cycle is detected, it will block `T1` until `T2` has completed with `Q`. At that point, `T1` reawakens. However, we don't know the result of executing `Q`, so `T1` now has to "retry". Typically, this will result in successfully reading the cached value. 8 | * While `T1` is blocking, the runtime moves its query stack (a `Vec`) into the shared dependency graph data structure. When `T1` reawakens, it recovers ownership of its query stack before returning from `try_block_on`. 9 | 10 | ## Cycle detection 11 | 12 | When a thread `T1` attempts to execute a query `Q`, it will try to load the value for `Q` from the memoization tables. If it finds an `InProgress` marker, that indicates that `Q` is currently being computed. This indicates a potential cycle. `T1` will then try to block on the query `Q`: 13 | 14 | * If `Q` is also being computed by `T1`, then there is a cycle. 15 | * Otherwise, if `Q` is being computed by some other thread `T2`, we have to check whether `T2` is (transitively) blocked on `T1`. If so, there is a cycle. 16 | 17 | These two cases are handled internally by the `Runtime::try_block_on` function. Detecting the intra-thread cycle case is easy; to detect cross-thread cycles, the runtime maintains a dependency DAG between threads (identified by `RuntimeId`). Before adding an edge `T1 -> T2` (i.e., `T1` is blocked waiting for `T2`) into the DAG, it checks whether a path exists from `T2` to `T1`. If so, we have a cycle and the edge cannot be added (then the DAG would not longer be acyclic). 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 | The most important basic operations that all queries support are: 4 | 5 | * [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. 6 | * [Fetch](./fetch.md): Returns the up-to-date value for the given K (or an error in the case of an "unrecovered" cycle). 7 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | First, we need to create the **database struct**. 4 | Typically it is only used by the "driver" of your application; 5 | the one which starts up the program, supplies the inputs, and relays the outputs. 6 | 7 | In `calc`, the database struct is in the [`db`] module, and it looks like this: 8 | 9 | [`db`]: https://github.com/salsa-rs/salsa/blob/master/examples/calc/db.rs 10 | 11 | ```rust 12 | {{#include ../../../examples/calc/db.rs:db_struct}} 13 | ``` 14 | 15 | The `#[salsa::db]` attribute marks the struct as a database. 16 | It must have a field named `storage` whose type is `salsa::Storage`, but it can also contain whatever other fields you want. 17 | 18 | ## Implementing the `salsa::Database` trait 19 | 20 | In addition to the struct itself, we must add an impl of `salsa::Database`: 21 | 22 | ```rust 23 | {{#include ../../../examples/calc/db.rs:db_impl}} 24 | ``` 25 | 26 | ## Implementing the `salsa::ParallelDatabase` trait 27 | 28 | If you want to permit accessing your database from multiple threads at once, then you also need to implement the `ParallelDatabase` trait: 29 | 30 | ```rust 31 | {{#include ../../../examples/calc/db.rs:par_db_impl}} 32 | ``` 33 | -------------------------------------------------------------------------------- /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/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.22.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | rust-version.workspace = true 9 | description = "Declarative macros for the salsa crate" 10 | 11 | [dependencies] 12 | -------------------------------------------------------------------------------- /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_default; 18 | mod return_mode; 19 | mod setup_accumulator_impl; 20 | mod setup_input_struct; 21 | mod setup_interned_struct; 22 | mod setup_tracked_assoc_fn_body; 23 | mod setup_tracked_fn; 24 | mod setup_tracked_method_body; 25 | mod setup_tracked_struct; 26 | mod unexpected_cycle_recovery; 27 | -------------------------------------------------------------------------------- /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 | ($return_mode: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 | ($return_mode: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_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 | ($return_mode:ident, $maybe_backdate:ident, default), 8 | $field_ty:ty, 9 | $field_ref_expr:expr, 10 | ) => { 11 | <$field_ty>::default() 12 | }; 13 | 14 | ( 15 | ($return_mode: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 | (($return_mode:ident, $maybe_backdate:ident, default) => $($t:tt)*) => { 26 | $($t)* 27 | }; 28 | 29 | (($return_mode: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 | #[allow(clippy::all)] 19 | #[allow(dead_code)] 20 | const _: () = { 21 | use salsa::plumbing as $zalsa; 22 | use salsa::plumbing::accumulator as $zalsa_struct; 23 | 24 | fn $ingredient(zalsa: &$zalsa::Zalsa) -> &$zalsa_struct::IngredientImpl<$Struct> { 25 | static $CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Struct>> = 26 | $zalsa::IngredientCache::new(); 27 | 28 | $CACHE.get_or_create(zalsa, || { 29 | zalsa.add_or_lookup_jar_by_type::<$zalsa_struct::JarImpl<$Struct>>() 30 | }) 31 | } 32 | 33 | impl $zalsa::Accumulator for $Struct { 34 | const DEBUG_NAME: &'static str = stringify!($Struct); 35 | 36 | fn accumulate(self, db: &Db) 37 | where 38 | Db: ?Sized + $zalsa::Database, 39 | { 40 | let (zalsa, zalsa_local) = db.zalsas(); 41 | $ingredient(zalsa).push(zalsa_local, self); 42 | } 43 | } 44 | }; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/setup_tracked_assoc_fn_body.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! setup_tracked_assoc_fn_body { 3 | ( 4 | salsa_tracked_attr: #[$salsa_tracked_attr:meta], 5 | self_ty: $self_ty:ty, 6 | db_lt: $($db_lt:lifetime)?, 7 | db: $db:ident, 8 | db_ty: ($($db_ty:tt)*), 9 | input_ids: [$($input_id:ident),*], 10 | input_tys: [$($input_ty:ty),*], 11 | output_ty: $output_ty:ty, 12 | inner_fn_name: $inner_fn_name:ident, 13 | inner_fn: $inner_fn:item, 14 | 15 | // Annoyingly macro-rules hygiene does not extend to items defined in the macro. 16 | // We have the procedural macro generate names for those items that are 17 | // not used elsewhere in the user's code. 18 | unused_names: [ 19 | $InnerTrait:ident, 20 | ] 21 | ) => { 22 | { 23 | trait $InnerTrait<$($db_lt)?> { 24 | fn $inner_fn_name(db: $($db_ty)*, $($input_id: $input_ty),*) -> $output_ty; 25 | } 26 | 27 | impl<$($db_lt)?> $InnerTrait<$($db_lt)?> for $self_ty { 28 | $inner_fn 29 | } 30 | 31 | #[$salsa_tracked_attr] 32 | fn $inner_fn_name<$($db_lt)?>(db: $($db_ty)*, $($input_id: $input_ty),*) -> $output_ty { 33 | <$self_ty as $InnerTrait>::$inner_fn_name(db, $($input_id),*) 34 | } 35 | 36 | $inner_fn_name($db, $($input_id),*) 37 | } 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /components/salsa-macro-rules/src/setup_tracked_method_body.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! setup_tracked_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, $value:ident, $count:ident, $($other_inputs:ident),*) => {{ 7 | std::mem::drop($db); 8 | std::mem::drop(($($other_inputs),*)); 9 | panic!("cannot recover from cycle") 10 | }}; 11 | } 12 | 13 | #[macro_export] 14 | macro_rules! unexpected_cycle_initial { 15 | ($db:ident, $($other_inputs:ident),*) => {{ 16 | std::mem::drop($db); 17 | std::mem::drop(($($other_inputs),*)); 18 | panic!("no cycle initial value") 19 | }}; 20 | } 21 | -------------------------------------------------------------------------------- /components/salsa-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "salsa-macros" 3 | version = "0.22.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | rust-version.workspace = true 9 | description = "Procedural macros for the salsa crate" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = "1.0" 16 | quote = "1.0" 17 | syn = { version = "2.0.101", features = ["full", "visit-mut"] } 18 | synstructure = "0.13.2" 19 | -------------------------------------------------------------------------------- /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(|_| { 37 | eprintln!("{token_string}"); 38 | Ok(()) 39 | }); 40 | } 41 | 42 | tokens 43 | } 44 | -------------------------------------------------------------------------------- /components/salsa-macros/src/fn_util.rs: -------------------------------------------------------------------------------- 1 | use crate::hygiene::Hygiene; 2 | use crate::xform::ChangeLt; 3 | 4 | /// Returns a vector of ids representing the function arguments. 5 | /// Prefers to reuse the names given by the user, if possible. 6 | pub fn input_ids(hygiene: &Hygiene, sig: &syn::Signature, skip: usize) -> Vec { 7 | sig.inputs 8 | .iter() 9 | .skip(skip) 10 | .zip(0..) 11 | .map(|(input, index)| { 12 | if let syn::FnArg::Typed(typed) = input { 13 | if let syn::Pat::Ident(ident) = &*typed.pat { 14 | return ident.ident.clone(); 15 | } 16 | } 17 | 18 | hygiene.ident(&format!("input{index}")) 19 | }) 20 | .collect() 21 | } 22 | 23 | pub fn input_tys(sig: &syn::Signature, skip: usize) -> syn::Result> { 24 | sig.inputs 25 | .iter() 26 | .skip(skip) 27 | .map(|input| { 28 | if let syn::FnArg::Typed(typed) = input { 29 | Ok(&*typed.ty) 30 | } else { 31 | Err(syn::Error::new_spanned(input, "unexpected receiver")) 32 | } 33 | }) 34 | .collect() 35 | } 36 | 37 | pub fn output_ty(db_lt: Option<&syn::Lifetime>, sig: &syn::Signature) -> syn::Result { 38 | match &sig.output { 39 | syn::ReturnType::Default => Ok(parse_quote!(())), 40 | syn::ReturnType::Type(_, ty) => match db_lt { 41 | Some(db_lt) => Ok(ChangeLt::elided_to(db_lt).in_type(ty)), 42 | None => Ok(syn::Type::clone(ty)), 43 | }, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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/tracked.rs: -------------------------------------------------------------------------------- 1 | use syn::spanned::Spanned; 2 | use syn::Item; 3 | 4 | use crate::token_stream_with_error; 5 | 6 | pub(crate) fn tracked( 7 | args: proc_macro::TokenStream, 8 | input: proc_macro::TokenStream, 9 | ) -> proc_macro::TokenStream { 10 | let item = parse_macro_input!(input as Item); 11 | let res = match item { 12 | syn::Item::Struct(item) => crate::tracked_struct::tracked_struct(args, item), 13 | syn::Item::Fn(item) => crate::tracked_fn::tracked_fn(args, item), 14 | syn::Item::Impl(item) => crate::tracked_impl::tracked_impl(args, item), 15 | _ => Err(syn::Error::new( 16 | item.span(), 17 | "tracked can only be applied to structs, functions, and impls", 18 | )), 19 | }; 20 | match res { 21 | Ok(s) => s.into(), 22 | Err(err) => token_stream_with_error(input, err), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/calc/compile.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::SourceProgram; 2 | use crate::parser::parse_statements; 3 | use crate::type_check::type_check_program; 4 | 5 | #[salsa::tracked] 6 | pub fn compile(db: &dyn crate::Db, source_program: SourceProgram) { 7 | let program = parse_statements(db, source_program); 8 | type_check_program(db, program); 9 | } 10 | -------------------------------------------------------------------------------- /examples/calc/db.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use std::sync::{Arc, Mutex}; 3 | 4 | // ANCHOR: db_struct 5 | #[salsa::db] 6 | #[derive(Clone)] 7 | #[cfg_attr(not(test), derive(Default))] 8 | pub struct CalcDatabaseImpl { 9 | storage: salsa::Storage, 10 | 11 | // The logs are only used for testing and demonstrating reuse: 12 | #[cfg(test)] 13 | logs: Arc>>>, 14 | } 15 | 16 | #[cfg(test)] 17 | impl Default for CalcDatabaseImpl { 18 | fn default() -> Self { 19 | let logs = >>>>::default(); 20 | Self { 21 | storage: salsa::Storage::new(Some(Box::new({ 22 | let logs = logs.clone(); 23 | move |event| { 24 | eprintln!("Event: {event:?}"); 25 | // Log interesting events, if logging is enabled 26 | if let Some(logs) = &mut *logs.lock().unwrap() { 27 | // only log interesting events 28 | if let salsa::EventKind::WillExecute { .. } = event.kind { 29 | logs.push(format!("Event: {event:?}")); 30 | } 31 | } 32 | } 33 | }))), 34 | logs, 35 | } 36 | } 37 | } 38 | // ANCHOR_END: db_struct 39 | 40 | impl CalcDatabaseImpl { 41 | /// Enable logging of each salsa event. 42 | #[cfg(test)] 43 | pub fn enable_logging(&self) { 44 | let mut logs = self.logs.lock().unwrap(); 45 | if logs.is_none() { 46 | *logs = Some(vec![]); 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | pub fn take_logs(&self) -> Vec { 52 | let mut logs = self.logs.lock().unwrap(); 53 | if let Some(logs) = &mut *logs { 54 | std::mem::take(logs) 55 | } else { 56 | vec![] 57 | } 58 | } 59 | } 60 | 61 | // ANCHOR: db_impl 62 | #[salsa::db] 63 | impl salsa::Database for CalcDatabaseImpl {} 64 | // ANCHOR_END: db_impl 65 | -------------------------------------------------------------------------------- /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-targets --no-fail-fast 3 | 4 | miri: 5 | cargo +nightly miri test --no-fail-fast 6 | 7 | shuttle: 8 | cargo nextest run --features shuttle --test parallel 9 | 10 | all: test miri 11 | -------------------------------------------------------------------------------- /release-plz.toml: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "salsa" 3 | version_group = "salsa" 4 | 5 | [[package]] 6 | name = "salsa-macros" 7 | version_group = "salsa" 8 | 9 | [[package]] 10 | name = "salsa-macro-rules" 11 | version_group = "salsa" 12 | -------------------------------------------------------------------------------- /src/accumulator/accumulated.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::fmt::Debug; 3 | 4 | use crate::accumulator::Accumulator; 5 | 6 | #[derive(Clone, Debug)] 7 | pub(crate) struct Accumulated { 8 | values: Vec, 9 | } 10 | 11 | pub(crate) trait AnyAccumulated: Any + Send + Sync { 12 | fn as_dyn_any(&self) -> &dyn Any; 13 | fn as_dyn_any_mut(&mut self) -> &mut dyn Any; 14 | } 15 | 16 | impl Accumulated { 17 | pub fn push(&mut self, value: A) { 18 | self.values.push(value); 19 | } 20 | 21 | pub fn extend_with_accumulated<'slf>(&'slf self, values: &mut Vec<&'slf A>) { 22 | values.extend(&self.values); 23 | } 24 | } 25 | 26 | impl Default for Accumulated { 27 | fn default() -> Self { 28 | Self { 29 | values: Default::default(), 30 | } 31 | } 32 | } 33 | 34 | impl AnyAccumulated for Accumulated 35 | where 36 | A: Accumulator, 37 | { 38 | fn as_dyn_any(&self) -> &dyn Any { 39 | self 40 | } 41 | 42 | fn as_dyn_any_mut(&mut self) -> &mut dyn Any { 43 | self 44 | } 45 | } 46 | 47 | impl dyn AnyAccumulated { 48 | pub fn accumulate(&mut self, value: A) { 49 | self.as_dyn_any_mut() 50 | .downcast_mut::>() 51 | .unwrap() 52 | .push(value); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/cancelled.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::panic::{self, UnwindSafe}; 3 | 4 | /// A panic payload indicating that execution of a salsa query was cancelled. 5 | /// 6 | /// This can occur for a few reasons: 7 | /// * 8 | /// * 9 | /// * 10 | #[derive(Debug)] 11 | #[non_exhaustive] 12 | pub enum Cancelled { 13 | /// The query was operating on revision R, but there is a pending write to move to revision R+1. 14 | #[non_exhaustive] 15 | PendingWrite, 16 | 17 | /// The query was blocked on another thread, and that thread panicked. 18 | #[non_exhaustive] 19 | PropagatedPanic, 20 | } 21 | 22 | impl Cancelled { 23 | pub(crate) fn throw(self) -> ! { 24 | // We use resume and not panic here to avoid running the panic 25 | // hook (that is, to avoid collecting and printing backtrace). 26 | panic::resume_unwind(Box::new(self)); 27 | } 28 | 29 | /// Runs `f`, and catches any salsa cancellation. 30 | pub fn catch(f: F) -> Result 31 | where 32 | F: FnOnce() -> T + UnwindSafe, 33 | { 34 | match panic::catch_unwind(f) { 35 | Ok(t) => Ok(t), 36 | Err(payload) => match payload.downcast() { 37 | Ok(cancelled) => Err(*cancelled), 38 | Err(payload) => panic::resume_unwind(payload), 39 | }, 40 | } 41 | } 42 | } 43 | 44 | impl std::fmt::Display for Cancelled { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | let why = match self { 47 | Cancelled::PendingWrite => "pending write", 48 | Cancelled::PropagatedPanic => "propagated panic", 49 | }; 50 | f.write_str("cancelled because of ")?; 51 | f.write_str(why) 52 | } 53 | } 54 | 55 | impl std::error::Error for Cancelled {} 56 | -------------------------------------------------------------------------------- /src/database_impl.rs: -------------------------------------------------------------------------------- 1 | use tracing::Level; 2 | 3 | use crate::storage::HasStorage; 4 | use crate::{Database, Storage}; 5 | 6 | /// Default database implementation that you can use if you don't 7 | /// require any custom user data. 8 | #[derive(Clone)] 9 | pub struct DatabaseImpl { 10 | storage: Storage, 11 | } 12 | 13 | impl Default for DatabaseImpl { 14 | fn default() -> Self { 15 | Self { 16 | // Default behavior: tracing debug log the event. 17 | storage: Storage::new(if tracing::enabled!(Level::DEBUG) { 18 | Some(Box::new(|event| { 19 | tracing::debug!("salsa_event({:?})", event) 20 | })) 21 | } else { 22 | None 23 | }), 24 | } 25 | } 26 | } 27 | 28 | impl DatabaseImpl { 29 | /// Create a new database; equivalent to `Self::default`. 30 | pub fn new() -> Self { 31 | Self::default() 32 | } 33 | 34 | pub fn storage(&self) -> &Storage { 35 | &self.storage 36 | } 37 | } 38 | 39 | impl Database for DatabaseImpl {} 40 | 41 | // SAFETY: The `storage` and `storage_mut` fields return a reference to the same storage field owned by `self`. 42 | unsafe impl HasStorage for DatabaseImpl { 43 | #[inline(always)] 44 | fn storage(&self) -> &Storage { 45 | &self.storage 46 | } 47 | 48 | #[inline(always)] 49 | fn storage_mut(&mut self) -> &mut Storage { 50 | &mut self.storage 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/function/backdate.rs: -------------------------------------------------------------------------------- 1 | use crate::function::memo::Memo; 2 | use crate::function::{Configuration, IngredientImpl}; 3 | use crate::zalsa_local::QueryRevisions; 4 | use crate::DatabaseKeyIndex; 5 | 6 | impl IngredientImpl 7 | where 8 | C: Configuration, 9 | { 10 | /// If the value/durability of this memo is equal to what is found in `revisions`/`value`, 11 | /// then update `revisions.changed_at` to match `self.revisions.changed_at`. This is invoked 12 | /// on an old memo when a new memo has been produced to check whether there have been changed. 13 | pub(super) fn backdate_if_appropriate<'db>( 14 | &self, 15 | old_memo: &Memo>, 16 | index: DatabaseKeyIndex, 17 | revisions: &mut QueryRevisions, 18 | value: &C::Output<'db>, 19 | ) { 20 | // We've seen issues where queries weren't re-validated when backdating provisional values 21 | // in ty. This is more of a bandaid because we're close to a release and don't have the time to prove 22 | // right now whether backdating could be made safe for queries participating in queries. 23 | // TODO: Write a test that demonstrates that backdating queries participating in a cycle isn't safe 24 | // OR write many tests showing that it is (and fixing the case where it didn't correctly account for today). 25 | if !revisions.cycle_heads().is_empty() { 26 | return; 27 | } 28 | 29 | if let Some(old_value) = &old_memo.value { 30 | // Careful: if the value became less durable than it 31 | // used to be, that is a "breaking change" that our 32 | // consumers must be aware of. Becoming *more* durable 33 | // is not. See the test `durable_to_less_durable`. 34 | if revisions.durability >= old_memo.revisions.durability 35 | && C::values_equal(old_value, value) 36 | { 37 | tracing::debug!( 38 | "{index:?} value is equal, back-dating to {:?}", 39 | old_memo.revisions.changed_at, 40 | ); 41 | 42 | assert!(old_memo.revisions.changed_at <= revisions.changed_at); 43 | revisions.changed_at = old_memo.revisions.changed_at; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/function/delete.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::NonNull; 2 | 3 | use crate::function::memo::Memo; 4 | use crate::function::Configuration; 5 | 6 | /// Stores the list of memos that have been deleted so they can be freed 7 | /// once the next revision starts. See the comment on the field 8 | /// `deleted_entries` of [`FunctionIngredient`][] for more details. 9 | pub(super) struct DeletedEntries { 10 | memos: boxcar::Vec>>>, 11 | } 12 | 13 | #[allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety 14 | unsafe impl Send for SharedBox {} 15 | #[allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety 16 | unsafe impl Sync for SharedBox {} 17 | 18 | impl Default for DeletedEntries { 19 | fn default() -> Self { 20 | Self { 21 | memos: Default::default(), 22 | } 23 | } 24 | } 25 | 26 | impl DeletedEntries { 27 | /// # Safety 28 | /// 29 | /// The memo must be valid and safe to free when the `DeletedEntries` list is cleared or dropped. 30 | pub(super) unsafe fn push(&self, memo: NonNull>>) { 31 | // Safety: The memo must be valid and safe to free when the `DeletedEntries` list is cleared or dropped. 32 | let memo = unsafe { 33 | std::mem::transmute::>>, NonNull>>>( 34 | memo, 35 | ) 36 | }; 37 | 38 | self.memos.push(SharedBox(memo)); 39 | } 40 | 41 | /// Free all deleted memos, keeping the list available for reuse. 42 | pub(super) fn clear(&mut self) { 43 | self.memos.clear(); 44 | } 45 | } 46 | 47 | /// A wrapper around `NonNull` that frees the allocation when it is dropped. 48 | struct SharedBox(NonNull); 49 | 50 | impl Drop for SharedBox { 51 | fn drop(&mut self) { 52 | // SAFETY: Guaranteed by the caller of `DeletedEntries::push`. 53 | unsafe { drop(Box::from_raw(self.0.as_ptr())) }; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/function/inputs.rs: -------------------------------------------------------------------------------- 1 | use crate::function::{Configuration, IngredientImpl}; 2 | use crate::zalsa::Zalsa; 3 | use crate::zalsa_local::QueryOriginRef; 4 | use crate::Id; 5 | 6 | impl IngredientImpl 7 | where 8 | C: Configuration, 9 | { 10 | pub(super) fn origin<'db>(&self, zalsa: &'db Zalsa, key: Id) -> Option> { 11 | let memo_ingredient_index = self.memo_ingredient_index(zalsa, key); 12 | self.get_memo_from_table_for(zalsa, key, memo_ingredient_index) 13 | .map(|m| m.revisions.origin.as_ref()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/function/lru.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroUsize; 2 | 3 | use crate::hash::FxLinkedHashSet; 4 | use crate::sync::Mutex; 5 | use crate::Id; 6 | 7 | pub(super) struct Lru { 8 | capacity: Option, 9 | set: Mutex>, 10 | } 11 | 12 | impl Lru { 13 | pub fn new(cap: usize) -> Self { 14 | Self { 15 | capacity: NonZeroUsize::new(cap), 16 | set: Mutex::default(), 17 | } 18 | } 19 | 20 | #[inline(always)] 21 | pub(super) fn record_use(&self, index: Id) { 22 | if self.capacity.is_some() { 23 | self.insert(index); 24 | } 25 | } 26 | 27 | #[inline(never)] 28 | fn insert(&self, index: Id) { 29 | let mut set = self.set.lock(); 30 | set.insert(index); 31 | } 32 | 33 | pub(super) fn set_capacity(&mut self, capacity: usize) { 34 | self.capacity = NonZeroUsize::new(capacity); 35 | if self.capacity.is_none() { 36 | self.set.get_mut().clear(); 37 | } 38 | } 39 | 40 | pub(super) fn for_each_evicted(&mut self, mut cb: impl FnMut(Id)) { 41 | let Some(cap) = self.capacity else { 42 | return; 43 | }; 44 | let set = self.set.get_mut(); 45 | while set.len() > cap.get() { 46 | if let Some(id) = set.pop_front() { 47 | cb(id); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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 FxLinkedHashSet = hashlink::LinkedHashSet; 6 | pub(crate) type FxHashSet = std::collections::HashSet; 7 | 8 | pub(crate) fn hash(t: &T) -> u64 { 9 | FxHasher::default().hash_one(t) 10 | } 11 | -------------------------------------------------------------------------------- /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 for SetterImpl<'_, 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/input/singleton.rs: -------------------------------------------------------------------------------- 1 | use crate::sync::atomic::{AtomicU64, Ordering}; 2 | use crate::Id; 3 | 4 | mod sealed { 5 | pub trait Sealed {} 6 | } 7 | 8 | pub trait SingletonChoice: sealed::Sealed + Default { 9 | fn with_scope(&self, cb: impl FnOnce() -> Id) -> Id; 10 | fn index(&self) -> Option; 11 | } 12 | 13 | pub struct Singleton { 14 | index: AtomicU64, 15 | } 16 | impl sealed::Sealed for Singleton {} 17 | impl SingletonChoice for Singleton { 18 | fn with_scope(&self, cb: impl FnOnce() -> Id) -> Id { 19 | if self.index.load(Ordering::Acquire) != 0 { 20 | panic!("singleton struct may not be duplicated"); 21 | } 22 | let id = cb(); 23 | if self 24 | .index 25 | .compare_exchange(0, id.as_bits(), Ordering::AcqRel, Ordering::Acquire) 26 | .is_err() 27 | { 28 | panic!("singleton struct may not be duplicated"); 29 | } 30 | id 31 | } 32 | 33 | fn index(&self) -> Option { 34 | match self.index.load(Ordering::Acquire) { 35 | 0 => None, 36 | 37 | // SAFETY: Our u64 is derived from an ID and thus safe to convert back. 38 | id => Some(unsafe { Id::from_bits(id) }), 39 | } 40 | } 41 | } 42 | 43 | impl Default for Singleton { 44 | fn default() -> Self { 45 | Self { 46 | index: AtomicU64::new(0), 47 | } 48 | } 49 | } 50 | #[derive(Default)] 51 | pub struct NotSingleton; 52 | impl sealed::Sealed for NotSingleton {} 53 | impl SingletonChoice for NotSingleton { 54 | fn with_scope(&self, cb: impl FnOnce() -> Id) -> Id { 55 | cb() 56 | } 57 | fn index(&self) -> Option { 58 | None 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/nonce.rs: -------------------------------------------------------------------------------- 1 | use crate::sync::atomic::{AtomicU32, Ordering}; 2 | use std::marker::PhantomData; 3 | use std::num::NonZeroU32; 4 | 5 | /// A type to generate nonces. Store it in a static and each nonce it produces will be unique from other nonces. 6 | /// The type parameter `T` just serves to distinguish different kinds of nonces. 7 | pub(crate) struct NonceGenerator { 8 | value: AtomicU32, 9 | phantom: PhantomData, 10 | } 11 | 12 | /// A "nonce" is a value that gets created exactly once. 13 | /// We use it to mark the database storage so we can be sure we're seeing the same database. 14 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 15 | pub struct Nonce(NonZeroU32, PhantomData); 16 | 17 | impl NonceGenerator { 18 | pub(crate) const fn new() -> Self { 19 | Self { 20 | // start at 1 so we can detect rollover more easily 21 | value: AtomicU32::new(1), 22 | phantom: PhantomData, 23 | } 24 | } 25 | 26 | pub(crate) fn nonce(&self) -> Nonce { 27 | let value = self.value.fetch_add(1, Ordering::Relaxed); 28 | 29 | assert!(value != 0, "nonce rolled over"); 30 | 31 | Nonce(NonZeroU32::new(value).unwrap(), self.phantom) 32 | } 33 | } 34 | 35 | impl Nonce { 36 | pub(crate) fn into_u32(self) -> NonZeroU32 { 37 | self.0 38 | } 39 | 40 | pub(crate) fn from_u32(u32: NonZeroU32) -> Self { 41 | Self(u32, PhantomData) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/parallel.rs: -------------------------------------------------------------------------------- 1 | use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelIterator}; 2 | 3 | use crate::Database; 4 | 5 | pub fn par_map(db: &Db, inputs: impl IntoParallelIterator, op: F) -> C 6 | where 7 | Db: Database + ?Sized, 8 | F: Fn(&Db, T) -> R + Sync + Send, 9 | T: Send, 10 | R: Send + Sync, 11 | C: FromParallelIterator, 12 | { 13 | inputs 14 | .into_par_iter() 15 | .map_with(DbForkOnClone(db.fork_db()), |db, element| { 16 | op(db.0.as_view(), element) 17 | }) 18 | .collect() 19 | } 20 | 21 | struct DbForkOnClone(Box); 22 | 23 | impl Clone for DbForkOnClone { 24 | fn clone(&self) -> Self { 25 | DbForkOnClone(self.0.fork_db()) 26 | } 27 | } 28 | 29 | pub fn join(db: &Db, a: A, b: B) -> (RA, RB) 30 | where 31 | A: FnOnce(&Db) -> RA + Send, 32 | B: FnOnce(&Db) -> RB + Send, 33 | RA: Send, 34 | RB: Send, 35 | { 36 | // we need to fork eagerly, as `rayon::join_context` gives us no option to tell whether we get 37 | // moved to another thread before the closure is executed 38 | let db_a = db.fork_db(); 39 | let db_b = db.fork_db(); 40 | rayon::join( 41 | move || a(db_a.as_view::()), 42 | move || b(db_b.as_view::()), 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/return_mode.rs: -------------------------------------------------------------------------------- 1 | //! User-implementable salsa traits for refining the return type via `returns(as_ref)` and `returns(as_deref)`. 2 | 3 | use std::ops::Deref; 4 | 5 | /// Used to determine the return type and value for tracked fields and functions annotated with `returns(as_ref)`. 6 | pub trait SalsaAsRef { 7 | // The type returned by tracked fields and functions annotated with `returns(as_ref)`. 8 | type AsRef<'a> 9 | where 10 | Self: 'a; 11 | 12 | // The value returned by tracked fields and functions annotated with `returns(as_ref)`. 13 | fn as_ref(&self) -> Self::AsRef<'_>; 14 | } 15 | 16 | impl SalsaAsRef for Option { 17 | type AsRef<'a> 18 | = Option<&'a T> 19 | where 20 | Self: 'a; 21 | 22 | fn as_ref(&self) -> Self::AsRef<'_> { 23 | self.as_ref() 24 | } 25 | } 26 | 27 | impl SalsaAsRef for Result { 28 | type AsRef<'a> 29 | = Result<&'a T, &'a E> 30 | where 31 | Self: 'a; 32 | 33 | fn as_ref(&self) -> Self::AsRef<'_> { 34 | self.as_ref() 35 | } 36 | } 37 | 38 | /// Used to determine the return type and value for tracked fields and functions annotated with `returns(as_deref)`. 39 | pub trait SalsaAsDeref { 40 | // The type returned by tracked fields and functions annotated with `returns(as_deref)`. 41 | type AsDeref<'a> 42 | where 43 | Self: 'a; 44 | 45 | // The value returned by tracked fields and functions annotated with `returns(as_deref)`. 46 | fn as_deref(&self) -> Self::AsDeref<'_>; 47 | } 48 | 49 | impl SalsaAsDeref for Option { 50 | type AsDeref<'a> 51 | = Option<&'a T::Target> 52 | where 53 | Self: 'a; 54 | 55 | fn as_deref(&self) -> Self::AsDeref<'_> { 56 | self.as_deref() 57 | } 58 | } 59 | 60 | impl SalsaAsDeref for Result { 61 | type AsDeref<'a> 62 | = Result<&'a T::Target, &'a E> 63 | where 64 | Self: 'a; 65 | 66 | fn as_deref(&self) -> Self::AsDeref<'_> { 67 | self.as_deref() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /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 | #[derive(Debug)] 12 | struct Log(#[allow(dead_code)] String); 13 | 14 | #[salsa::tracked] 15 | fn push_logs(db: &dyn Database) { 16 | push_a_logs(db); 17 | } 18 | 19 | #[salsa::tracked] 20 | fn push_a_logs(db: &dyn Database) { 21 | Log("log a".to_string()).accumulate(db); 22 | push_b_logs(db); 23 | } 24 | 25 | #[salsa::tracked] 26 | fn push_b_logs(db: &dyn Database) { 27 | // No logs 28 | push_c_logs(db); 29 | } 30 | 31 | #[salsa::tracked] 32 | fn push_c_logs(db: &dyn Database) { 33 | // No logs 34 | push_d_logs(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_chain() { 44 | DatabaseImpl::new().attach(|db| { 45 | let logs = push_logs::accumulated::(db); 46 | // Check that we get all the logs. 47 | expect![[r#" 48 | [ 49 | Log( 50 | "log a", 51 | ), 52 | Log( 53 | "log d", 54 | ), 55 | ]"#]] 56 | .assert_eq(&format!("{logs:#?}")); 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /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(debug)] 8 | struct MyInput { 9 | count: u32, 10 | } 11 | 12 | #[salsa::accumulator] 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(debug)] 8 | struct MyInput { 9 | field_a: u32, 10 | field_b: u32, 11 | } 12 | 13 | #[salsa::accumulator] 14 | #[derive(Debug)] 15 | struct Log(#[allow(dead_code)] String); 16 | 17 | #[salsa::tracked] 18 | fn push_logs(db: &dyn Database, input: MyInput) { 19 | push_a_logs(db, input); 20 | push_b_logs(db, input); 21 | } 22 | 23 | #[salsa::tracked] 24 | fn push_a_logs(db: &dyn Database, input: MyInput) { 25 | let count = input.field_a(db); 26 | for i in 0..count { 27 | Log(format!("log_a({i} of {count})")).accumulate(db); 28 | } 29 | } 30 | 31 | #[salsa::tracked] 32 | fn push_b_logs(db: &dyn Database, input: MyInput) { 33 | // Note that b calls a 34 | push_a_logs(db, input); 35 | let count = input.field_b(db); 36 | for i in 0..count { 37 | Log(format!("log_b({i} of {count})")).accumulate(db); 38 | } 39 | } 40 | 41 | #[test] 42 | fn accumulate_a_called_twice() { 43 | salsa::DatabaseImpl::new().attach(|db| { 44 | let input = MyInput::new(db, 2, 3); 45 | let logs = push_logs::accumulated::(db, input); 46 | // Check that we don't see logs from `a` appearing twice in the input. 47 | expect![[r#" 48 | [ 49 | Log( 50 | "log_a(0 of 2)", 51 | ), 52 | Log( 53 | "log_a(1 of 2)", 54 | ), 55 | Log( 56 | "log_b(0 of 3)", 57 | ), 58 | Log( 59 | "log_b(1 of 3)", 60 | ), 61 | Log( 62 | "log_b(2 of 3)", 63 | ), 64 | ]"#]] 65 | .assert_eq(&format!("{logs:#?}")); 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /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 | #[derive(Debug)] 12 | struct Log(#[allow(dead_code)] String); 13 | 14 | #[salsa::tracked] 15 | fn push_logs(db: &dyn Database) { 16 | push_a_logs(db); 17 | } 18 | 19 | #[salsa::tracked] 20 | fn push_a_logs(db: &dyn Database) { 21 | Log("log a".to_string()).accumulate(db); 22 | push_b_logs(db); 23 | push_c_logs(db); 24 | push_d_logs(db); 25 | } 26 | 27 | #[salsa::tracked] 28 | fn push_b_logs(db: &dyn Database) { 29 | Log("log b".to_string()).accumulate(db); 30 | push_d_logs(db); 31 | } 32 | 33 | #[salsa::tracked] 34 | fn push_c_logs(db: &dyn Database) { 35 | Log("log c".to_string()).accumulate(db); 36 | } 37 | 38 | #[salsa::tracked] 39 | fn push_d_logs(db: &dyn Database) { 40 | Log("log d".to_string()).accumulate(db); 41 | } 42 | 43 | #[test] 44 | fn accumulate_execution_order() { 45 | salsa::DatabaseImpl::new().attach(|db| { 46 | let logs = push_logs::accumulated::(db); 47 | // Check that we get logs in execution order 48 | expect![[r#" 49 | [ 50 | Log( 51 | "log a", 52 | ), 53 | Log( 54 | "log b", 55 | ), 56 | Log( 57 | "log d", 58 | ), 59 | Log( 60 | "log c", 61 | ), 62 | ]"#]] 63 | .assert_eq(&format!("{logs:#?}")); 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /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(debug)] 10 | struct List { 11 | value: u32, 12 | next: Option, 13 | } 14 | 15 | #[salsa::accumulator] 16 | #[derive(Copy, Clone, Debug)] 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-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 | use expect_test::expect; 8 | use salsa::{Accumulator, Setter}; 9 | use test_log::test; 10 | 11 | #[salsa::input(debug)] 12 | struct List { 13 | value: u32, 14 | next: Option, 15 | } 16 | 17 | #[salsa::accumulator] 18 | #[derive(Copy, Clone, Debug)] 19 | struct Integers(u32); 20 | 21 | #[salsa::tracked] 22 | fn compute(db: &dyn LogDatabase, input: List) -> u32 { 23 | db.push_log(format!("compute({input:?})",)); 24 | 25 | // always pushes 0 26 | Integers(0).accumulate(db); 27 | 28 | let result = if let Some(next) = input.next(db) { 29 | let next_integers = accumulated(db, next); 30 | let v = input.value(db) + next_integers.iter().sum::(); 31 | v 32 | } else { 33 | input.value(db) 34 | }; 35 | 36 | // return value changes 37 | result 38 | } 39 | 40 | #[salsa::tracked(returns(ref))] 41 | fn accumulated(db: &dyn LogDatabase, input: List) -> Vec { 42 | db.push_log(format!("accumulated({input:?})")); 43 | compute::accumulated::(db, input) 44 | .into_iter() 45 | .map(|a| a.0) 46 | .collect() 47 | } 48 | 49 | #[test] 50 | fn test1() { 51 | let mut db = LoggerDatabase::default(); 52 | 53 | let l1 = List::new(&db, 1, None); 54 | let l2 = List::new(&db, 2, Some(l1)); 55 | 56 | assert_eq!(compute(&db, l2), 2); 57 | db.assert_logs(expect![[r#" 58 | [ 59 | "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 1, next: None }) })", 60 | "accumulated(List { [salsa id]: Id(0), value: 1, next: None })", 61 | "compute(List { [salsa id]: Id(0), value: 1, next: None })", 62 | ]"#]]); 63 | 64 | // When we mutate `l1`, we should re-execute `compute` for `l1`, 65 | // and we re-execute accumulated for `l1`, but we do NOT re-execute 66 | // `compute` for `l2`. 67 | l1.set_value(&mut db).to(2); 68 | assert_eq!(compute(&db, l2), 2); 69 | db.assert_logs(expect![[r#" 70 | [ 71 | "accumulated(List { [salsa id]: Id(0), value: 2, next: None })", 72 | "compute(List { [salsa id]: Id(0), value: 2, next: None })", 73 | ]"#]]); 74 | } 75 | -------------------------------------------------------------------------------- /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 | use expect_test::expect; 9 | use salsa::{Accumulator, Setter}; 10 | use test_log::test; 11 | 12 | #[salsa::input(debug)] 13 | struct List { 14 | value: u32, 15 | next: Option, 16 | } 17 | 18 | #[salsa::accumulator] 19 | struct Integers(u32); 20 | 21 | #[salsa::tracked] 22 | fn compute(db: &dyn LogDatabase, input: List) -> u32 { 23 | db.push_log(format!("compute({input:?})",)); 24 | 25 | // always pushes 0 26 | Integers(0).accumulate(db); 27 | 28 | let result = if let Some(next) = input.next(db) { 29 | let next_integers = compute::accumulated::(db, next); 30 | let v = input.value(db) + next_integers.iter().map(|i| i.0).sum::(); 31 | v 32 | } else { 33 | input.value(db) 34 | }; 35 | 36 | // return value changes 37 | result 38 | } 39 | 40 | #[test] 41 | fn test1() { 42 | let mut db = LoggerDatabase::default(); 43 | 44 | let l1 = List::new(&db, 1, None); 45 | let l2 = List::new(&db, 2, Some(l1)); 46 | 47 | assert_eq!(compute(&db, l2), 2); 48 | db.assert_logs(expect![[r#" 49 | [ 50 | "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 1, next: None }) })", 51 | "compute(List { [salsa id]: Id(0), value: 1, next: None })", 52 | ]"#]]); 53 | 54 | // When we mutate `l1`, we should re-execute `compute` for `l1`, 55 | // but we should not have to re-execute `compute` for `l2`. 56 | // The only input for `compute(l1)` is the accumulated values from `l1`, 57 | // which have not changed. 58 | l1.set_value(&mut db).to(2); 59 | assert_eq!(compute(&db, l2), 2); 60 | db.assert_logs(expect![[r#" 61 | [ 62 | "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 2, next: None }) })", 63 | "compute(List { [salsa id]: Id(0), value: 2, next: None })", 64 | ]"#]]); 65 | } 66 | -------------------------------------------------------------------------------- /tests/accumulated_backdate.rs: -------------------------------------------------------------------------------- 1 | //! Tests that accumulated values are correctly accounted for 2 | //! when backdating a value. 3 | 4 | mod common; 5 | use common::LogDatabase; 6 | use expect_test::expect; 7 | use salsa::{Accumulator, Setter}; 8 | use test_log::test; 9 | 10 | #[salsa::input(debug)] 11 | struct File { 12 | content: String, 13 | } 14 | 15 | #[salsa::accumulator] 16 | #[derive(Debug)] 17 | struct Log(#[allow(dead_code)] String); 18 | 19 | #[salsa::tracked] 20 | fn compile(db: &dyn LogDatabase, input: File) -> u32 { 21 | parse(db, input) 22 | } 23 | 24 | #[salsa::tracked] 25 | fn parse(db: &dyn LogDatabase, input: File) -> u32 { 26 | let value: Result = input.content(db).parse(); 27 | 28 | match value { 29 | Ok(value) => value, 30 | Err(error) => { 31 | Log(error.to_string()).accumulate(db); 32 | 0 33 | } 34 | } 35 | } 36 | 37 | #[test] 38 | fn backdate() { 39 | let mut db = common::LoggerDatabase::default(); 40 | 41 | let input = File::new(&db, "0".to_string()); 42 | 43 | let logs = compile::accumulated::(&db, input); 44 | expect![[r#"[]"#]].assert_eq(&format!("{logs:#?}")); 45 | 46 | input.set_content(&mut db).to("a".to_string()); 47 | let logs = compile::accumulated::(&db, input); 48 | 49 | expect![[r#" 50 | [ 51 | Log( 52 | "invalid digit found in string", 53 | ), 54 | ]"#]] 55 | .assert_eq(&format!("{logs:#?}")); 56 | } 57 | 58 | #[test] 59 | fn backdate_no_diagnostics() { 60 | let mut db = common::LoggerDatabase::default(); 61 | 62 | let input = File::new(&db, "a".to_string()); 63 | 64 | let logs = compile::accumulated::(&db, input); 65 | expect![[r#" 66 | [ 67 | Log( 68 | "invalid digit found in string", 69 | ), 70 | ]"#]] 71 | .assert_eq(&format!("{logs:#?}")); 72 | 73 | input.set_content(&mut db).to("0".to_string()); 74 | let logs = compile::accumulated::(&db, input); 75 | 76 | expect![[r#"[]"#]].assert_eq(&format!("{logs:#?}")); 77 | } 78 | -------------------------------------------------------------------------------- /tests/check_auto_traits.rs: -------------------------------------------------------------------------------- 1 | //! Test that auto trait impls exist as expected. 2 | 3 | use std::panic::UnwindSafe; 4 | 5 | use salsa::Database; 6 | use test_log::test; 7 | 8 | #[salsa::input] 9 | struct MyInput { 10 | field: String, 11 | } 12 | 13 | #[salsa::tracked] 14 | struct MyTracked<'db> { 15 | field: MyInterned<'db>, 16 | } 17 | 18 | #[salsa::interned] 19 | struct MyInterned<'db> { 20 | field: String, 21 | } 22 | 23 | #[salsa::tracked] 24 | fn test(db: &dyn Database, input: MyInput) { 25 | let input = is_send(is_sync(input)); 26 | let interned = is_send(is_sync(MyInterned::new(db, input.field(db).clone()))); 27 | let _tracked_struct = is_send(is_sync(MyTracked::new(db, interned))); 28 | } 29 | 30 | fn is_send(t: T) -> T { 31 | t 32 | } 33 | 34 | fn is_sync(t: T) -> T { 35 | t 36 | } 37 | 38 | fn is_unwind_safe(t: T) -> T { 39 | t 40 | } 41 | 42 | #[test] 43 | fn execute() { 44 | let db = is_send(salsa::DatabaseImpl::new()); 45 | let _handle = is_send(is_sync(is_unwind_safe( 46 | db.storage().clone().into_zalsa_handle(), 47 | ))); 48 | let input = MyInput::new(&db, "Hello".to_string()); 49 | test(&db, input); 50 | } 51 | -------------------------------------------------------------------------------- /tests/compile-fail/accumulator_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::accumulator(returns(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: `returns` option not allowed here 2 | --> tests/compile-fail/accumulator_incompatibles.rs:1:22 3 | | 4 | 1 | #[salsa::accumulator(returns(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 | mod a { 2 | #[salsa::input] 3 | pub struct MyInput { 4 | field: u32, 5 | } 6 | } 7 | 8 | fn main() { 9 | let mut db = salsa::DatabaseImpl::new(); 10 | let input = a::MyInput::new(&mut db, 22); 11 | 12 | input.field(&db); 13 | input.set_field(&mut db).to(23); 14 | } 15 | -------------------------------------------------------------------------------- /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:12:11 3 | | 4 | 2 | #[salsa::input] 5 | | --------------- private method defined here 6 | ... 7 | 12 | 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:13:11 12 | | 13 | 2 | #[salsa::input] 14 | | --------------- private method defined here 15 | ... 16 | 13 | input.set_field(&mut db).to(23); 17 | | ^^^^^^^^^ private method 18 | -------------------------------------------------------------------------------- /tests/compile-fail/input_struct_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::input(returns(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 InputWithTrackedField { 21 | #[tracked] 22 | field: u32, 23 | } 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /tests/compile-fail/input_struct_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: `returns` option not allowed here 2 | --> tests/compile-fail/input_struct_incompatibles.rs:1:16 3 | | 4 | 1 | #[salsa::input(returns(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: `#[tracked]` cannot be used with `#[salsa::input]` 38 | --> tests/compile-fail/input_struct_incompatibles.rs:21:5 39 | | 40 | 21 | / #[tracked] 41 | 22 | | field: u32, 42 | | |______________^ 43 | 44 | error: cannot find attribute `tracked` in this scope 45 | --> tests/compile-fail/input_struct_incompatibles.rs:21:7 46 | | 47 | 21 | #[tracked] 48 | | ^^^^^^^ 49 | | 50 | help: consider importing one of these attribute macros 51 | | 52 | 1 + use salsa::tracked; 53 | | 54 | 1 + use salsa_macros::tracked; 55 | | 56 | -------------------------------------------------------------------------------- /tests/compile-fail/interned_struct_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::interned(returns(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 InternedWithTrackedField { 33 | #[tracked] 34 | field: u32, 35 | } 36 | 37 | fn main() {} 38 | -------------------------------------------------------------------------------- /tests/compile-fail/interned_struct_incompatibles.stderr: -------------------------------------------------------------------------------- 1 | error: `returns` option not allowed here 2 | --> tests/compile-fail/interned_struct_incompatibles.rs:1:19 3 | | 4 | 1 | #[salsa::interned(returns(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: `#[tracked]` cannot be used with `#[salsa::interned]` 38 | --> tests/compile-fail/interned_struct_incompatibles.rs:33:5 39 | | 40 | 33 | / #[tracked] 41 | 34 | | field: u32, 42 | | |______________^ 43 | 44 | error: cannot find attribute `tracked` in this scope 45 | --> tests/compile-fail/interned_struct_incompatibles.rs:33:7 46 | | 47 | 33 | #[tracked] 48 | | ^^^^^^^ 49 | | 50 | help: consider importing one of these attribute macros 51 | | 52 | 1 + use salsa::tracked; 53 | | 54 | 1 + use salsa_macros::tracked; 55 | | 56 | -------------------------------------------------------------------------------- /tests/compile-fail/invalid_return_mode.rs: -------------------------------------------------------------------------------- 1 | use salsa::Database as Db; 2 | 3 | #[salsa::input] 4 | struct MyInput { 5 | #[returns(clone)] 6 | text: String, 7 | } 8 | 9 | #[salsa::tracked(returns(not_a_return_mode))] 10 | fn tracked_fn_invalid_return_mode(db: &dyn Db, input: MyInput) -> String { 11 | input.text(db) 12 | } 13 | 14 | #[salsa::input] 15 | struct MyInvalidInput { 16 | #[returns(not_a_return_mode)] 17 | text: String, 18 | } 19 | 20 | fn main() { } -------------------------------------------------------------------------------- /tests/compile-fail/invalid_return_mode.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid return mode. Allowed modes are: ["copy", "clone", "ref", "deref", "as_ref", "as_deref"] 2 | --> tests/compile-fail/invalid_return_mode.rs:9:26 3 | | 4 | 9 | #[salsa::tracked(returns(not_a_return_mode))] 5 | | ^^^^^^^^^^^^^^^^^ 6 | 7 | error: Invalid return mode. Allowed modes are: ["copy", "clone", "ref", "deref", "as_ref", "as_deref"] 8 | --> tests/compile-fail/invalid_return_mode.rs:16:15 9 | | 10 | 16 | #[returns(not_a_return_mode)] 11 | | ^^^^^^^^^^^^^^^^^ 12 | 13 | error: cannot find attribute `returns` in this scope 14 | --> tests/compile-fail/invalid_return_mode.rs:16:7 15 | | 16 | 16 | #[returns(not_a_return_mode)] 17 | | ^^^^^^^ 18 | -------------------------------------------------------------------------------- /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 | #[salsa::interned] 38 | struct MyInterned<'db> { 39 | field: u32, 40 | } 41 | 42 | #[salsa::tracked] 43 | fn tracked_fn_with_lt_param_and_elided_lt_on_db_arg1<'db>( 44 | db: &dyn Db, 45 | interned: MyInterned<'db>, 46 | ) -> u32 { 47 | interned.field(db) * 2 48 | } 49 | 50 | #[salsa::tracked] 51 | fn tracked_fn_with_lt_param_and_elided_lt_on_db_arg2<'db_lifetime>( 52 | db: &dyn Db, 53 | interned: MyInterned<'db_lifetime>, 54 | ) -> u32 { 55 | interned.field(db) * 2 56 | } 57 | 58 | #[salsa::tracked] 59 | fn tracked_fn_with_lt_param_and_elided_lt_on_input<'db>( 60 | db: &'db dyn Db, 61 | interned: MyInterned, 62 | ) -> u32 { 63 | interned.field(db) * 2 64 | } 65 | 66 | #[salsa::tracked] 67 | fn tracked_fn_with_multiple_lts<'db1, 'db2>(db: &'db1 dyn Db, interned: MyInterned<'db2>) -> u32 { 68 | interned.field(db) * 2 69 | } 70 | 71 | fn main() {} 72 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_fn_return_not_update.rs: -------------------------------------------------------------------------------- 1 | use salsa::Database as Db; 2 | 3 | #[salsa::input] 4 | struct MyInput {} 5 | 6 | #[derive(Clone, Debug)] 7 | struct NotUpdate; 8 | 9 | #[salsa::tracked] 10 | fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> NotUpdate { 11 | _ = (db, input); 12 | NotUpdate 13 | } 14 | 15 | fn main() {} 16 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_fn_return_not_update.stderr: -------------------------------------------------------------------------------- 1 | error[E0369]: binary operation `==` cannot be applied to type `&NotUpdate` 2 | --> tests/compile-fail/tracked_fn_return_not_update.rs:10:56 3 | | 4 | 10 | fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> NotUpdate { 5 | | ^^^^^^^^^ 6 | | 7 | note: an implementation of `PartialEq` might be missing for `NotUpdate` 8 | --> tests/compile-fail/tracked_fn_return_not_update.rs:7:1 9 | | 10 | 7 | struct NotUpdate; 11 | | ^^^^^^^^^^^^^^^^ must implement `PartialEq` 12 | help: consider annotating `NotUpdate` with `#[derive(PartialEq)]` 13 | | 14 | 7 + #[derive(PartialEq)] 15 | 8 | struct NotUpdate; 16 | | 17 | 18 | error[E0599]: the function or associated item `maybe_update` exists for struct `UpdateDispatch`, but its trait bounds were not satisfied 19 | --> tests/compile-fail/tracked_fn_return_not_update.rs:10:56 20 | | 21 | 7 | struct NotUpdate; 22 | | ---------------- doesn't satisfy `NotUpdate: PartialEq` or `NotUpdate: Update` 23 | ... 24 | 10 | fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> NotUpdate { 25 | | ^^^^^^^^^ function or associated item cannot be called on `UpdateDispatch` due to unsatisfied trait bounds 26 | | 27 | ::: src/update.rs 28 | | 29 | | pub struct Dispatch(PhantomData); 30 | | ---------------------- doesn't satisfy `_: UpdateFallback` 31 | | 32 | = note: the following trait bounds were not satisfied: 33 | `NotUpdate: Update` 34 | `NotUpdate: PartialEq` 35 | which is required by `UpdateDispatch: UpdateFallback` 36 | note: the trait `Update` must be implemented 37 | --> src/update.rs 38 | | 39 | | pub unsafe trait Update { 40 | | ^^^^^^^^^^^^^^^^^^^^^^^ 41 | help: consider annotating `NotUpdate` with `#[derive(PartialEq)]` 42 | | 43 | 7 + #[derive(PartialEq)] 44 | 8 | struct NotUpdate; 45 | | 46 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_fn_return_ref.rs: -------------------------------------------------------------------------------- 1 | use salsa::Database as Db; 2 | 3 | #[salsa::input] 4 | struct MyInput { 5 | #[returns(ref)] 6 | text: String, 7 | } 8 | 9 | #[derive(Clone, Debug, PartialEq, Eq)] 10 | struct ContainsRef<'db> { 11 | text: &'db str, 12 | } 13 | 14 | #[salsa::tracked] 15 | fn tracked_fn_return_ref<'db>(db: &'db dyn Db, input: MyInput) -> &'db str { 16 | input.text(db) 17 | } 18 | 19 | #[salsa::tracked] 20 | fn tracked_fn_return_struct_containing_ref<'db>( 21 | db: &'db dyn Db, 22 | input: MyInput, 23 | ) -> ContainsRef<'db> { 24 | ContainsRef { 25 | text: input.text(db), 26 | } 27 | } 28 | 29 | #[salsa::tracked] 30 | fn tracked_fn_return_struct_containing_ref_elided_implicit<'db>( 31 | db: &'db dyn Db, 32 | input: MyInput, 33 | ) -> ContainsRef { 34 | ContainsRef { 35 | text: input.text(db), 36 | } 37 | } 38 | 39 | #[salsa::tracked] 40 | fn tracked_fn_return_struct_containing_ref_elided_explicit<'db>( 41 | db: &'db dyn Db, 42 | input: MyInput, 43 | ) -> ContainsRef<'_> { 44 | ContainsRef { 45 | text: input.text(db), 46 | } 47 | } 48 | 49 | fn main() {} 50 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_fn_return_ref.stderr: -------------------------------------------------------------------------------- 1 | error[E0106]: missing lifetime specifier 2 | --> tests/compile-fail/tracked_fn_return_ref.rs:33:6 3 | | 4 | 33 | ) -> ContainsRef { 5 | | ^^^^^^^^^^^ expected named lifetime parameter 6 | | 7 | help: consider using the `'db` lifetime 8 | | 9 | 33 | ) -> ContainsRef<'db> { 10 | | +++++ 11 | 12 | warning: elided lifetime has a name 13 | --> tests/compile-fail/tracked_fn_return_ref.rs:33:6 14 | | 15 | 30 | fn tracked_fn_return_struct_containing_ref_elided_implicit<'db>( 16 | | --- lifetime `'db` declared here 17 | ... 18 | 33 | ) -> ContainsRef { 19 | | ^^^^^^^^^^^ this elided lifetime gets resolved as `'db` 20 | | 21 | = note: `#[warn(elided_named_lifetimes)]` on by default 22 | 23 | warning: elided lifetime has a name 24 | --> tests/compile-fail/tracked_fn_return_ref.rs:43:18 25 | | 26 | 40 | fn tracked_fn_return_struct_containing_ref_elided_explicit<'db>( 27 | | --- lifetime `'db` declared here 28 | ... 29 | 43 | ) -> ContainsRef<'_> { 30 | | ^^ this elided lifetime gets resolved as `'db` 31 | 32 | error: lifetime may not live long enough 33 | --> tests/compile-fail/tracked_fn_return_ref.rs:15:67 34 | | 35 | 15 | fn tracked_fn_return_ref<'db>(db: &'db dyn Db, input: MyInput) -> &'db str { 36 | | --- lifetime `'db` defined here ^ requires that `'db` must outlive `'static` 37 | 38 | error: lifetime may not live long enough 39 | --> tests/compile-fail/tracked_fn_return_ref.rs:23:6 40 | | 41 | 20 | fn tracked_fn_return_struct_containing_ref<'db>( 42 | | --- lifetime `'db` defined here 43 | ... 44 | 23 | ) -> ContainsRef<'db> { 45 | | ^^^^^^^^^^^ requires that `'db` must outlive `'static` 46 | 47 | error: lifetime may not live long enough 48 | --> tests/compile-fail/tracked_fn_return_ref.rs:43:6 49 | | 50 | 40 | fn tracked_fn_return_struct_containing_ref_elided_explicit<'db>( 51 | | --- lifetime `'db` defined here 52 | ... 53 | 43 | ) -> ContainsRef<'_> { 54 | | ^^^^^^^^^^^ requires that `'db` must outlive `'static` 55 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_impl_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::tracked] 2 | struct MyTracked<'db> { 3 | field: u32, 4 | } 5 | 6 | #[salsa::tracked(returns(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_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_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 | 7 | error[E0405]: cannot find trait `Db` in this scope 8 | --> tests/compile-fail/tracked_method_on_untracked_impl.rs:8:56 9 | | 10 | 8 | fn tracked_method_on_untracked_impl(self, db: &dyn Db) -> u32 { 11 | | ^^ not found in this scope 12 | 13 | error[E0425]: cannot find value `input` in this scope 14 | --> tests/compile-fail/tracked_method_on_untracked_impl.rs:9:9 15 | | 16 | 9 | input.field(db) 17 | | ^^^^^ not found in this scope 18 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_struct_incompatibles.rs: -------------------------------------------------------------------------------- 1 | #[salsa::tracked(returns(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: `returns` option not allowed here 2 | --> tests/compile-fail/tracked_struct_incompatibles.rs:1:18 3 | | 4 | 1 | #[salsa::tracked(returns(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/tracked_struct_not_update.rs: -------------------------------------------------------------------------------- 1 | #[salsa::tracked] 2 | struct MyInput<'db> { 3 | field: NotUpdate, 4 | } 5 | 6 | #[derive(Clone, Debug, Hash)] 7 | struct NotUpdate; 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/compile-fail/tracked_struct_not_update.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the function or associated item `maybe_update` exists for struct `UpdateDispatch`, but its trait bounds were not satisfied 2 | --> tests/compile-fail/tracked_struct_not_update.rs:1:1 3 | | 4 | 1 | #[salsa::tracked] 5 | | ^^^^^^^^^^^^^^^^^ function or associated item cannot be called on `UpdateDispatch` due to unsatisfied trait bounds 6 | ... 7 | 7 | struct NotUpdate; 8 | | ---------------- doesn't satisfy `NotUpdate: PartialEq` or `NotUpdate: Update` 9 | | 10 | ::: src/update.rs 11 | | 12 | | pub struct Dispatch(PhantomData); 13 | | ---------------------- doesn't satisfy `_: UpdateFallback` 14 | | 15 | note: if you're trying to build a new `UpdateDispatch`, consider using `UpdateDispatch::::new` which returns `UpdateDispatch<_>` 16 | --> src/update.rs 17 | | 18 | | pub fn new() -> Self { 19 | | ^^^^^^^^^^^^^^^^^^^^ 20 | = note: the following trait bounds were not satisfied: 21 | `NotUpdate: Update` 22 | `NotUpdate: PartialEq` 23 | which is required by `UpdateDispatch: UpdateFallback` 24 | note: the trait `Update` must be implemented 25 | --> src/update.rs 26 | | 27 | | pub unsafe trait Update { 28 | | ^^^^^^^^^^^^^^^^^^^^^^^ 29 | = note: this error originates in the macro `salsa::plumbing::setup_tracked_struct` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) 30 | help: consider annotating `NotUpdate` with `#[derive(PartialEq)]` 31 | | 32 | 7 + #[derive(PartialEq)] 33 | 8 | struct NotUpdate; 34 | | 35 | -------------------------------------------------------------------------------- /tests/compile_fail.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::all(stable, since(1.84))] 2 | #[test] 3 | fn compile_fail() { 4 | let t = trybuild::TestCases::new(); 5 | t.compile_fail("tests/compile-fail/*.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/cycle_fallback_immediate.rs: -------------------------------------------------------------------------------- 1 | //! It is possible to omit the `cycle_fn`, only specifying `cycle_result` in which case 2 | //! an immediate fallback value is used as the cycle handling opposed to doing a fixpoint resolution. 3 | 4 | use std::sync::atomic::{AtomicI32, Ordering}; 5 | 6 | #[salsa::tracked(cycle_result=cycle_result)] 7 | fn one_o_one(db: &dyn salsa::Database) -> u32 { 8 | let val = one_o_one(db); 9 | val + 1 10 | } 11 | 12 | fn cycle_result(_db: &dyn salsa::Database) -> u32 { 13 | 100 14 | } 15 | 16 | #[test_log::test] 17 | fn simple() { 18 | let db = salsa::DatabaseImpl::default(); 19 | 20 | assert_eq!(one_o_one(&db), 100); 21 | } 22 | 23 | #[salsa::tracked(cycle_result=two_queries_cycle_result)] 24 | fn two_queries1(db: &dyn salsa::Database) -> i32 { 25 | two_queries2(db); 26 | 0 27 | } 28 | 29 | #[salsa::tracked] 30 | fn two_queries2(db: &dyn salsa::Database) -> i32 { 31 | two_queries1(db); 32 | // This is horribly against Salsa's rules, but we want to test that 33 | // the value from within the cycle is not considered, and this is 34 | // the only way I found. 35 | static CALLS_COUNT: AtomicI32 = AtomicI32::new(0); 36 | CALLS_COUNT.fetch_add(1, Ordering::Relaxed) 37 | } 38 | 39 | fn two_queries_cycle_result(_db: &dyn salsa::Database) -> i32 { 40 | 1 41 | } 42 | 43 | #[test] 44 | fn two_queries() { 45 | let db = salsa::DatabaseImpl::default(); 46 | 47 | assert_eq!(two_queries1(&db), 1); 48 | assert_eq!(two_queries2(&db), 1); 49 | } 50 | -------------------------------------------------------------------------------- /tests/cycle_initial_call_back_into_cycle.rs: -------------------------------------------------------------------------------- 1 | //! Calling back into the same cycle from your cycle initial function will trigger another cycle. 2 | 3 | use salsa::UnexpectedCycle; 4 | 5 | #[salsa::tracked] 6 | fn initial_value(db: &dyn salsa::Database) -> u32 { 7 | query(db) 8 | } 9 | 10 | #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=cycle_initial)] 11 | fn query(db: &dyn salsa::Database) -> u32 { 12 | let val = query(db); 13 | if val < 5 { 14 | val + 1 15 | } else { 16 | val 17 | } 18 | } 19 | 20 | fn cycle_initial(db: &dyn salsa::Database) -> u32 { 21 | initial_value(db) 22 | } 23 | 24 | fn cycle_fn( 25 | _db: &dyn salsa::Database, 26 | _value: &u32, 27 | _count: u32, 28 | ) -> salsa::CycleRecoveryAction { 29 | salsa::CycleRecoveryAction::Iterate 30 | } 31 | 32 | #[test_log::test] 33 | fn the_test() { 34 | let db = salsa::DatabaseImpl::default(); 35 | 36 | UnexpectedCycle::catch(|| query(&db)).unwrap_err(); 37 | } 38 | -------------------------------------------------------------------------------- /tests/cycle_initial_call_query.rs: -------------------------------------------------------------------------------- 1 | //! It's possible to call a Salsa query from within a cycle initial fn. 2 | 3 | #[salsa::tracked] 4 | fn initial_value(_db: &dyn salsa::Database) -> u32 { 5 | 0 6 | } 7 | 8 | #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=cycle_initial)] 9 | fn query(db: &dyn salsa::Database) -> u32 { 10 | let val = query(db); 11 | if val < 5 { 12 | val + 1 13 | } else { 14 | val 15 | } 16 | } 17 | 18 | fn cycle_initial(db: &dyn salsa::Database) -> u32 { 19 | initial_value(db) 20 | } 21 | 22 | fn cycle_fn( 23 | _db: &dyn salsa::Database, 24 | _value: &u32, 25 | _count: u32, 26 | ) -> salsa::CycleRecoveryAction { 27 | salsa::CycleRecoveryAction::Iterate 28 | } 29 | 30 | #[test_log::test] 31 | fn the_test() { 32 | let db = salsa::DatabaseImpl::default(); 33 | 34 | assert_eq!(query(&db), 5); 35 | } 36 | -------------------------------------------------------------------------------- /tests/cycle_recovery_call_back_into_cycle.rs: -------------------------------------------------------------------------------- 1 | //! Calling back into the same cycle from your cycle recovery function _can_ work out, as long as 2 | //! the overall cycle still converges. 3 | 4 | mod common; 5 | use common::{DatabaseWithValue, ValueDatabase}; 6 | 7 | #[salsa::tracked] 8 | fn fallback_value(db: &dyn ValueDatabase) -> u32 { 9 | query(db) + db.get_value() 10 | } 11 | 12 | #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=cycle_initial)] 13 | fn query(db: &dyn ValueDatabase) -> u32 { 14 | let val = query(db); 15 | if val < 5 { 16 | val + 1 17 | } else { 18 | val 19 | } 20 | } 21 | 22 | fn cycle_initial(_db: &dyn ValueDatabase) -> u32 { 23 | 0 24 | } 25 | 26 | fn cycle_fn(db: &dyn ValueDatabase, _value: &u32, _count: u32) -> salsa::CycleRecoveryAction { 27 | salsa::CycleRecoveryAction::Fallback(fallback_value(db)) 28 | } 29 | 30 | #[test] 31 | fn converges() { 32 | let db = DatabaseWithValue::new(10); 33 | 34 | assert_eq!(query(&db), 10); 35 | } 36 | 37 | #[test] 38 | #[should_panic(expected = "fallback did not converge")] 39 | fn diverges() { 40 | let db = DatabaseWithValue::new(3); 41 | 42 | query(&db); 43 | } 44 | -------------------------------------------------------------------------------- /tests/cycle_recovery_call_query.rs: -------------------------------------------------------------------------------- 1 | //! It's possible to call a Salsa query from within a cycle recovery fn. 2 | 3 | #[salsa::tracked] 4 | fn fallback_value(_db: &dyn salsa::Database) -> u32 { 5 | 10 6 | } 7 | 8 | #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=cycle_initial)] 9 | fn query(db: &dyn salsa::Database) -> u32 { 10 | let val = query(db); 11 | if val < 5 { 12 | val + 1 13 | } else { 14 | val 15 | } 16 | } 17 | 18 | fn cycle_initial(_db: &dyn salsa::Database) -> u32 { 19 | 0 20 | } 21 | 22 | fn cycle_fn( 23 | db: &dyn salsa::Database, 24 | _value: &u32, 25 | _count: u32, 26 | ) -> salsa::CycleRecoveryAction { 27 | salsa::CycleRecoveryAction::Fallback(fallback_value(db)) 28 | } 29 | 30 | #[test_log::test] 31 | fn the_test() { 32 | let db = salsa::DatabaseImpl::default(); 33 | 34 | assert_eq!(query(&db), 10); 35 | } 36 | -------------------------------------------------------------------------------- /tests/cycle_regression_455.rs: -------------------------------------------------------------------------------- 1 | use salsa::{Database, Setter}; 2 | 3 | #[salsa::tracked] 4 | fn memoized(db: &dyn Database, input: MyInput) -> u32 { 5 | memoized_a(db, MyTracked::new(db, input.field(db))) 6 | } 7 | 8 | #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=cycle_initial)] 9 | fn memoized_a<'db>(db: &'db dyn Database, tracked: MyTracked<'db>) -> u32 { 10 | MyTracked::new(db, 0); 11 | memoized_b(db, tracked) 12 | } 13 | 14 | fn cycle_fn<'db>( 15 | _db: &'db dyn Database, 16 | _value: &u32, 17 | _count: u32, 18 | _input: MyTracked<'db>, 19 | ) -> salsa::CycleRecoveryAction { 20 | salsa::CycleRecoveryAction::Iterate 21 | } 22 | 23 | fn cycle_initial(_db: &dyn Database, _input: MyTracked) -> u32 { 24 | 0 25 | } 26 | 27 | #[salsa::tracked] 28 | fn memoized_b<'db>(db: &'db dyn Database, tracked: MyTracked<'db>) -> u32 { 29 | let incr = tracked.field(db); 30 | let a = memoized_a(db, tracked); 31 | if a > 8 { 32 | a 33 | } else { 34 | a + incr 35 | } 36 | } 37 | 38 | #[salsa::input] 39 | struct MyInput { 40 | field: u32, 41 | } 42 | 43 | #[salsa::tracked] 44 | struct MyTracked<'db> { 45 | field: u32, 46 | } 47 | 48 | #[test] 49 | fn cycle_memoized() { 50 | let mut db = salsa::DatabaseImpl::new(); 51 | let input = MyInput::new(&db, 2); 52 | assert_eq!(memoized(&db, input), 10); 53 | input.set_field(&mut db).to(3); 54 | assert_eq!(memoized(&db, input), 9); 55 | } 56 | -------------------------------------------------------------------------------- /tests/cycle_result_dependencies.rs: -------------------------------------------------------------------------------- 1 | use salsa::{Database, Setter}; 2 | 3 | #[salsa::input] 4 | struct Input { 5 | value: i32, 6 | } 7 | 8 | #[salsa::tracked(cycle_result=cycle_result)] 9 | fn has_cycle(db: &dyn Database, input: Input) -> i32 { 10 | has_cycle(db, input) 11 | } 12 | 13 | fn cycle_result(db: &dyn Database, input: Input) -> i32 { 14 | input.value(db) 15 | } 16 | 17 | #[test] 18 | fn cycle_result_dependencies_are_recorded() { 19 | let mut db = salsa::DatabaseImpl::default(); 20 | let input = Input::new(&db, 123); 21 | assert_eq!(has_cycle(&db, input), 123); 22 | 23 | input.set_value(&mut db).to(456); 24 | assert_eq!(has_cycle(&db, input), 456); 25 | } 26 | -------------------------------------------------------------------------------- /tests/debug_db_contents.rs: -------------------------------------------------------------------------------- 1 | #[salsa::interned(debug)] 2 | struct InternedStruct<'db> { 3 | name: String, 4 | } 5 | 6 | #[salsa::input(debug)] 7 | struct InputStruct { 8 | field: u32, 9 | } 10 | 11 | #[salsa::tracked(debug)] 12 | struct TrackedStruct<'db> { 13 | field: u32, 14 | } 15 | 16 | #[salsa::tracked] 17 | fn tracked_fn(db: &dyn salsa::Database, input: InputStruct) -> TrackedStruct<'_> { 18 | TrackedStruct::new(db, input.field(db) * 2) 19 | } 20 | 21 | #[test] 22 | fn execute() { 23 | let db = salsa::DatabaseImpl::new(); 24 | 25 | let _ = InternedStruct::new(&db, "Salsa".to_string()); 26 | let _ = InternedStruct::new(&db, "Salsa2".to_string()); 27 | 28 | // test interned structs 29 | let interned = InternedStruct::ingredient(&db) 30 | .entries(&db) 31 | .collect::>(); 32 | 33 | assert_eq!(interned.len(), 2); 34 | assert_eq!(interned[0].fields().0, "Salsa"); 35 | assert_eq!(interned[1].fields().0, "Salsa2"); 36 | 37 | // test input structs 38 | let input = InputStruct::new(&db, 22); 39 | 40 | let inputs = InputStruct::ingredient(&db) 41 | .entries(&db) 42 | .collect::>(); 43 | 44 | assert_eq!(inputs.len(), 1); 45 | assert_eq!(inputs[0].fields().0, 22); 46 | 47 | // test tracked structs 48 | let computed = tracked_fn(&db, input).field(&db); 49 | assert_eq!(computed, 44); 50 | let tracked = TrackedStruct::ingredient(&db) 51 | .entries(&db) 52 | .collect::>(); 53 | 54 | assert_eq!(tracked.len(), 1); 55 | assert_eq!(tracked[0].fields().0, computed); 56 | } 57 | -------------------------------------------------------------------------------- /tests/durability.rs: -------------------------------------------------------------------------------- 1 | //! Tests that code using the builder's durability methods compiles. 2 | 3 | use salsa::{Database, Durability, Setter}; 4 | use test_log::test; 5 | 6 | #[salsa::input] 7 | struct N { 8 | value: u32, 9 | } 10 | 11 | #[salsa::tracked] 12 | fn add3(db: &dyn Database, a: N, b: N, c: N) -> u32 { 13 | add(db, a, b) + c.value(db) 14 | } 15 | 16 | #[salsa::tracked] 17 | fn add(db: &dyn Database, a: N, b: N) -> u32 { 18 | a.value(db) + b.value(db) 19 | } 20 | 21 | #[test] 22 | fn durable_to_less_durable() { 23 | let mut db = salsa::DatabaseImpl::new(); 24 | 25 | let a = N::builder(11).value_durability(Durability::HIGH).new(&db); 26 | let b = N::builder(22).value_durability(Durability::HIGH).new(&db); 27 | let c = N::builder(33).value_durability(Durability::HIGH).new(&db); 28 | 29 | // Here, `add3` invokes `add(a, b)`, which yields 33. 30 | assert_eq!(add3(&db, a, b, c), 66); 31 | 32 | a.set_value(&mut db).with_durability(Durability::LOW).to(11); 33 | 34 | // Here, `add3` invokes `add`, which *still* yields 33, but which 35 | // is no longer of high durability. Since value didn't change, we might 36 | // preserve `add3` unchanged, not noticing that it is no longer 37 | // of high durability. 38 | 39 | assert_eq!(add3(&db, a, b, c), 66); 40 | 41 | // In that case, we would not get the correct result here, when 42 | // 'a' changes *again*. 43 | 44 | a.set_value(&mut db).to(22); 45 | 46 | assert_eq!(add3(&db, a, b, c), 77); 47 | } 48 | -------------------------------------------------------------------------------- /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 | use expect_test::expect; 7 | use salsa::Setter; 8 | use test_log::test; 9 | 10 | #[salsa::input(debug)] 11 | struct MyInput { 12 | field: u32, 13 | } 14 | 15 | #[salsa::tracked] 16 | fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { 17 | db.push_log(format!("final_result({input:?})")); 18 | intermediate_result(db, input).field(db) * 2 19 | } 20 | 21 | #[salsa::tracked] 22 | struct MyTracked<'db> { 23 | field: u32, 24 | } 25 | 26 | #[salsa::tracked] 27 | fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> { 28 | db.push_log(format!("intermediate_result({input:?})")); 29 | MyTracked::new(db, input.field(db) / 2) 30 | } 31 | 32 | #[test] 33 | fn execute() { 34 | let mut db = common::LoggerDatabase::default(); 35 | 36 | let input = MyInput::new(&db, 22); 37 | assert_eq!(final_result(&db, input), 22); 38 | db.assert_logs(expect![[r#" 39 | [ 40 | "final_result(MyInput { [salsa id]: Id(0), field: 22 })", 41 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", 42 | ]"#]]); 43 | 44 | // Intermediate result is the same, so final result does 45 | // not need to be recomputed: 46 | input.set_field(&mut db).to(23); 47 | assert_eq!(final_result(&db, input), 22); 48 | db.assert_logs(expect![[r#" 49 | [ 50 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 23 })", 51 | ]"#]]); 52 | 53 | input.set_field(&mut db).to(24); 54 | assert_eq!(final_result(&db, input), 24); 55 | db.assert_logs(expect![[r#" 56 | [ 57 | "intermediate_result(MyInput { [salsa id]: Id(0), field: 24 })", 58 | "final_result(MyInput { [salsa id]: Id(0), field: 24 })", 59 | ]"#]]); 60 | } 61 | -------------------------------------------------------------------------------- /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 | use expect_test::expect; 9 | use salsa::Setter; 10 | 11 | #[salsa::input(debug)] 12 | struct MyInput { 13 | x: u32, 14 | y: u32, 15 | } 16 | 17 | #[salsa::tracked] 18 | fn result_depends_on_x(db: &dyn LogDatabase, input: MyInput) -> u32 { 19 | db.push_log(format!("result_depends_on_x({input:?})")); 20 | input.x(db) + 1 21 | } 22 | 23 | #[salsa::tracked] 24 | fn result_depends_on_y(db: &dyn LogDatabase, input: MyInput) -> u32 { 25 | db.push_log(format!("result_depends_on_y({input:?})")); 26 | input.y(db) - 1 27 | } 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/hash_collision.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hash; 2 | 3 | #[test] 4 | fn hello() { 5 | use salsa::{Database, DatabaseImpl, Setter}; 6 | 7 | #[salsa::input] 8 | struct Bool { 9 | value: bool, 10 | } 11 | 12 | #[salsa::tracked] 13 | struct True<'db> {} 14 | 15 | #[salsa::tracked] 16 | struct False<'db> {} 17 | 18 | #[salsa::tracked] 19 | fn hello(db: &dyn Database, bool: Bool) { 20 | if bool.value(db) { 21 | True::new(db); 22 | } else { 23 | False::new(db); 24 | } 25 | } 26 | 27 | let mut db = DatabaseImpl::new(); 28 | let input = Bool::new(&db, false); 29 | hello(&db, input); 30 | input.set_value(&mut db).to(true); 31 | hello(&db, input); 32 | } 33 | -------------------------------------------------------------------------------- /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 salsa::plumbing::ZalsaDatabase; 2 | use salsa::{Durability, Setter}; 3 | use test_log::test; 4 | 5 | #[salsa::input] 6 | struct MyInput { 7 | required_field: bool, 8 | 9 | #[default] 10 | optional_field: usize, 11 | } 12 | 13 | #[test] 14 | fn execute() { 15 | let mut db = salsa::DatabaseImpl::new(); 16 | 17 | let input = MyInput::builder(true) 18 | .required_field_durability(Durability::HIGH) 19 | .new(&db); 20 | 21 | // Change the field value. It should preserve high durability. 22 | input.set_required_field(&mut db).to(false); 23 | 24 | let last_high_revision = db.zalsa().last_changed_revision(Durability::HIGH); 25 | 26 | // Changing the value again should **again** dump the high durability revision. 27 | input.set_required_field(&mut db).to(false); 28 | 29 | assert_ne!( 30 | db.zalsa().last_changed_revision(Durability::HIGH), 31 | last_high_revision 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /tests/intern_access_in_different_revision.rs: -------------------------------------------------------------------------------- 1 | use salsa::{Durability, Setter}; 2 | 3 | #[salsa::interned(no_lifetime)] 4 | struct Interned { 5 | field: u32, 6 | } 7 | 8 | #[salsa::input] 9 | struct Input { 10 | field: i32, 11 | } 12 | 13 | #[test] 14 | fn the_test() { 15 | let mut db = salsa::DatabaseImpl::default(); 16 | let input = Input::builder(-123456) 17 | .field_durability(Durability::HIGH) 18 | .new(&db); 19 | // Create an intern in an early revision. 20 | let interned = Interned::new(&db, 0xDEADBEEF); 21 | // Trigger a new revision. 22 | input 23 | .set_field(&mut db) 24 | .with_durability(Durability::HIGH) 25 | .to(123456); 26 | // Read the interned value 27 | let _ = interned.field(&db); 28 | } 29 | -------------------------------------------------------------------------------- /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 | #[returns(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/cycle_a_t1_b_t2_fallback.rs: -------------------------------------------------------------------------------- 1 | //! Test a specific cycle scenario: 2 | //! 3 | //! ```text 4 | //! Thread T1 Thread T2 5 | //! --------- --------- 6 | //! | | 7 | //! v | 8 | //! query_a() | 9 | //! ^ | v 10 | //! | +------------> query_b() 11 | //! | | 12 | //! +--------------------+ 13 | //! ``` 14 | use crate::KnobsDatabase; 15 | 16 | const FALLBACK_A: u32 = 0b01; 17 | const FALLBACK_B: u32 = 0b10; 18 | const OFFSET_A: u32 = 0b0100; 19 | const OFFSET_B: u32 = 0b1000; 20 | 21 | // Signal 1: T1 has entered `query_a` 22 | // Signal 2: T2 has entered `query_b` 23 | 24 | #[salsa::tracked(cycle_result=cycle_result_a)] 25 | fn query_a(db: &dyn KnobsDatabase) -> u32 { 26 | db.signal(1); 27 | 28 | // Wait for Thread T2 to enter `query_b` before we continue. 29 | db.wait_for(2); 30 | 31 | query_b(db) | OFFSET_A 32 | } 33 | 34 | #[salsa::tracked(cycle_result=cycle_result_b)] 35 | fn query_b(db: &dyn KnobsDatabase) -> u32 { 36 | // Wait for Thread T1 to enter `query_a` before we continue. 37 | db.wait_for(1); 38 | 39 | db.signal(2); 40 | 41 | query_a(db) | OFFSET_B 42 | } 43 | 44 | fn cycle_result_a(_db: &dyn KnobsDatabase) -> u32 { 45 | FALLBACK_A 46 | } 47 | 48 | fn cycle_result_b(_db: &dyn KnobsDatabase) -> u32 { 49 | FALLBACK_B 50 | } 51 | 52 | #[test_log::test] 53 | fn the_test() { 54 | use crate::sync::thread; 55 | use crate::Knobs; 56 | 57 | crate::sync::check(|| { 58 | let db_t1 = Knobs::default(); 59 | let db_t2 = db_t1.clone(); 60 | 61 | let t1 = thread::spawn(move || query_a(&db_t1)); 62 | let t2 = thread::spawn(move || query_b(&db_t2)); 63 | 64 | let (r_t1, r_t2) = (t1.join(), t2.join()); 65 | 66 | assert_eq!((r_t1.unwrap(), r_t2.unwrap()), (FALLBACK_A, FALLBACK_B)); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /tests/parallel/cycle_panic.rs: -------------------------------------------------------------------------------- 1 | // Shuttle doesn't like panics inside of its runtime. 2 | #![cfg(not(feature = "shuttle"))] 3 | 4 | //! Test for panic in cycle recovery function, in cross-thread cycle. 5 | use crate::setup::{Knobs, KnobsDatabase}; 6 | 7 | #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)] 8 | fn query_a(db: &dyn KnobsDatabase) -> u32 { 9 | db.signal(1); 10 | db.wait_for(2); 11 | query_b(db) 12 | } 13 | 14 | #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)] 15 | fn query_b(db: &dyn KnobsDatabase) -> u32 { 16 | db.wait_for(1); 17 | db.signal(2); 18 | query_a(db) + 1 19 | } 20 | 21 | fn cycle_fn(_db: &dyn KnobsDatabase, _value: &u32, _count: u32) -> salsa::CycleRecoveryAction { 22 | panic!("cancel!") 23 | } 24 | 25 | fn initial(_db: &dyn KnobsDatabase) -> u32 { 26 | 0 27 | } 28 | 29 | #[test] 30 | fn execute() { 31 | let db = Knobs::default(); 32 | 33 | let db_t1 = db.clone(); 34 | let t1 = std::thread::spawn(move || query_a(&db_t1)); 35 | 36 | let db_t2 = db.clone(); 37 | let t2 = std::thread::spawn(move || query_b(&db_t2)); 38 | 39 | // The main thing here is that we don't deadlock. 40 | let (r1, r2) = (t1.join(), t2.join()); 41 | assert!(r1.is_err()); 42 | assert!(r2.is_err()); 43 | } 44 | -------------------------------------------------------------------------------- /tests/parallel/main.rs: -------------------------------------------------------------------------------- 1 | mod setup; 2 | mod signal; 3 | 4 | mod cycle_a_t1_b_t2; 5 | mod cycle_a_t1_b_t2_fallback; 6 | mod cycle_ab_peeping_c; 7 | mod cycle_nested_deep; 8 | mod cycle_nested_deep_conditional; 9 | mod cycle_nested_three_threads; 10 | mod cycle_panic; 11 | mod cycle_provisional_depending_on_itself; 12 | mod parallel_cancellation; 13 | mod parallel_join; 14 | mod parallel_map; 15 | 16 | #[cfg(not(feature = "shuttle"))] 17 | pub(crate) mod sync { 18 | pub use std::sync::*; 19 | pub use std::thread; 20 | 21 | pub fn check(f: impl Fn() + Send + Sync + 'static) { 22 | f(); 23 | } 24 | } 25 | 26 | #[cfg(feature = "shuttle")] 27 | pub(crate) mod sync { 28 | pub use shuttle::sync::*; 29 | pub use shuttle::thread; 30 | 31 | pub fn check(f: impl Fn() + Send + Sync + 'static) { 32 | shuttle::check_pct(f, 1000, 50); 33 | } 34 | } 35 | 36 | pub(crate) use setup::*; 37 | -------------------------------------------------------------------------------- /tests/parallel/parallel_cancellation.rs: -------------------------------------------------------------------------------- 1 | // Shuttle doesn't like panics inside of its runtime. 2 | #![cfg(not(feature = "shuttle"))] 3 | 4 | //! Test for thread cancellation. 5 | use salsa::{Cancelled, Setter}; 6 | 7 | use crate::setup::{Knobs, KnobsDatabase}; 8 | 9 | #[salsa::input(debug)] 10 | struct MyInput { 11 | field: i32, 12 | } 13 | 14 | #[salsa::tracked] 15 | fn a1(db: &dyn KnobsDatabase, input: MyInput) -> MyInput { 16 | db.signal(1); 17 | db.wait_for(2); 18 | dummy(db, input) 19 | } 20 | 21 | #[salsa::tracked] 22 | fn dummy(_db: &dyn KnobsDatabase, _input: MyInput) -> MyInput { 23 | panic!("should never get here!") 24 | } 25 | 26 | // Cancellation signalling test 27 | // 28 | // The pattern is as follows. 29 | // 30 | // Thread A Thread B 31 | // -------- -------- 32 | // a1 33 | // | wait for stage 1 34 | // signal stage 1 set input, triggers cancellation 35 | // wait for stage 2 (blocks) triggering cancellation sends stage 2 36 | // | 37 | // (unblocked) 38 | // dummy 39 | // panics 40 | 41 | #[test] 42 | fn execute() { 43 | let mut db = Knobs::default(); 44 | 45 | let input = MyInput::new(&db, 1); 46 | 47 | let thread_a = std::thread::spawn({ 48 | let db = db.clone(); 49 | move || a1(&db, input) 50 | }); 51 | 52 | db.signal_on_did_cancel(2); 53 | input.set_field(&mut db).to(2); 54 | 55 | // Assert thread A *should* was cancelled 56 | let cancelled = thread_a 57 | .join() 58 | .unwrap_err() 59 | .downcast::() 60 | .unwrap(); 61 | 62 | // and inspect the output 63 | expect_test::expect![[r#" 64 | PendingWrite 65 | "#]] 66 | .assert_debug_eq(&cancelled); 67 | } 68 | -------------------------------------------------------------------------------- /tests/parallel/signal.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use super::sync::{Condvar, Mutex}; 4 | 5 | #[derive(Default)] 6 | pub(crate) struct Signal { 7 | value: Mutex, 8 | cond_var: Condvar, 9 | } 10 | 11 | impl Signal { 12 | pub(crate) fn signal(&self, stage: usize) { 13 | // When running with shuttle we want to explore as many possible 14 | // executions, so we avoid signals entirely. 15 | #[cfg(not(feature = "shuttle"))] 16 | { 17 | // This check avoids acquiring the lock for things that will 18 | // clearly be a no-op. Not *necessary* but helps to ensure we 19 | // are more likely to encounter weird race conditions; 20 | // otherwise calls to `sum` will tend to be unnecessarily 21 | // synchronous. 22 | if stage > 0 { 23 | let mut v = self.value.lock().unwrap(); 24 | if stage > *v { 25 | *v = stage; 26 | self.cond_var.notify_all(); 27 | } 28 | } 29 | } 30 | } 31 | 32 | /// Waits until the given condition is true; the fn is invoked 33 | /// with the current stage. 34 | pub(crate) fn wait_for(&self, stage: usize) { 35 | #[cfg(not(feature = "shuttle"))] 36 | { 37 | // As above, avoid lock if clearly a no-op. 38 | if stage > 0 { 39 | let mut v = self.value.lock().unwrap(); 40 | while *v < stage { 41 | v = self.cond_var.wait(v).unwrap(); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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 | use salsa::Database as _; 7 | use test_log::test; 8 | 9 | #[salsa::input(singleton, debug)] 10 | struct MyInput { 11 | field: u32, 12 | id_field: u16, 13 | } 14 | 15 | #[test] 16 | fn basic() { 17 | let db = salsa::DatabaseImpl::new(); 18 | let input1 = MyInput::new(&db, 3, 4); 19 | let input2 = MyInput::get(&db); 20 | 21 | assert_eq!(input1, input2); 22 | 23 | let input3 = MyInput::try_get(&db); 24 | assert_eq!(Some(input1), input3); 25 | } 26 | 27 | #[test] 28 | #[should_panic] 29 | fn twice() { 30 | let db = salsa::DatabaseImpl::new(); 31 | let input1 = MyInput::new(&db, 3, 4); 32 | let input2 = MyInput::get(&db); 33 | 34 | assert_eq!(input1, input2); 35 | 36 | // should panic here 37 | _ = MyInput::new(&db, 3, 5); 38 | } 39 | 40 | #[test] 41 | fn debug() { 42 | salsa::DatabaseImpl::new().attach(|db| { 43 | let input = MyInput::new(db, 3, 4); 44 | let actual = format!("{input:?}"); 45 | let expected = expect!["MyInput { [salsa id]: Id(0), field: 3, id_field: 4 }"]; 46 | expected.assert_eq(&actual); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /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 | field: BadEq, 32 | } 33 | 34 | #[salsa::tracked] 35 | fn the_fn(db: &dyn Database, input: MyInput) { 36 | let tracked0 = MyTracked::new(db, BadEq::from(input.field(db))); 37 | assert_eq!(tracked0.field(db).field, input.field(db)); 38 | } 39 | 40 | #[test] 41 | fn execute() { 42 | let mut db = salsa::DatabaseImpl::new(); 43 | let input = MyInput::new(&db, true); 44 | the_fn(&db, input); 45 | input.set_field(&mut db).to(false); 46 | the_fn(&db, input); 47 | } 48 | -------------------------------------------------------------------------------- /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-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 | #[tracked] 27 | #[no_eq] 28 | field: NotEq, 29 | } 30 | 31 | #[salsa::tracked] 32 | fn the_fn(db: &dyn Database, input: MyInput) { 33 | let tracked0 = MyTracked::new(db, NotEq::from(input.field(db))); 34 | assert_eq!(tracked0.field(db).field, input.field(db)); 35 | } 36 | 37 | #[test] 38 | fn execute() { 39 | let mut db = salsa::DatabaseImpl::new(); 40 | 41 | let input = MyInput::new(&db, true); 42 | the_fn(&db, input); 43 | input.set_field(&mut db).to(false); 44 | the_fn(&db, input); 45 | } 46 | -------------------------------------------------------------------------------- /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_interned_lifetime.rs: -------------------------------------------------------------------------------- 1 | #[salsa::interned] 2 | struct Interned<'db> { 3 | field: i32, 4 | } 5 | 6 | #[salsa::tracked] 7 | fn foo<'a>(_db: &'a dyn salsa::Database, _: Interned<'_>, _: Interned<'a>) {} 8 | 9 | #[test] 10 | fn the_test() { 11 | let db = salsa::DatabaseImpl::new(); 12 | let i = Interned::new(&db, 123); 13 | foo(&db, i, i); 14 | } 15 | -------------------------------------------------------------------------------- /tests/tracked_fn_multiple_args.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on multiple salsa struct args 2 | //! compiles and executes successfully. 3 | 4 | #[salsa::input] 5 | struct MyInput { 6 | field: u32, 7 | } 8 | 9 | #[salsa::interned] 10 | struct MyInterned<'db> { 11 | field: u32, 12 | } 13 | 14 | #[salsa::tracked] 15 | fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput, interned: MyInterned<'db>) -> u32 { 16 | input.field(db) + interned.field(db) 17 | } 18 | 19 | #[test] 20 | fn execute() { 21 | let db = salsa::DatabaseImpl::new(); 22 | let input = MyInput::new(&db, 22); 23 | let interned = MyInterned::new(&db, 33); 24 | assert_eq!(tracked_fn(&db, input, interned), 55); 25 | } 26 | -------------------------------------------------------------------------------- /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 common::{EventLoggerDatabase, HasLogger, LogDatabase, Logger}; 4 | use expect_test::expect; 5 | use salsa::plumbing::HasStorage; 6 | use salsa::{Database, Durability, Event, EventKind, Setter}; 7 | 8 | mod common; 9 | #[salsa::input] 10 | struct MyInput { 11 | field: u32, 12 | } 13 | 14 | #[salsa::tracked] 15 | fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> u32 { 16 | input.field(db) * 2 17 | } 18 | 19 | #[test] 20 | fn execute() { 21 | let mut db = EventLoggerDatabase::default(); 22 | let input_low = MyInput::new(&db, 22); 23 | let input_high = MyInput::builder(2200).durability(Durability::HIGH).new(&db); 24 | 25 | assert_eq!(tracked_fn(&db, input_low), 44); 26 | assert_eq!(tracked_fn(&db, input_high), 4400); 27 | 28 | db.assert_logs(expect![[r#" 29 | [ 30 | "WillCheckCancellation", 31 | "WillExecute { database_key: tracked_fn(Id(0)) }", 32 | "WillCheckCancellation", 33 | "WillExecute { database_key: tracked_fn(Id(1)) }", 34 | ]"#]]); 35 | 36 | db.synthetic_write(Durability::LOW); 37 | 38 | assert_eq!(tracked_fn(&db, input_low), 44); 39 | assert_eq!(tracked_fn(&db, input_high), 4400); 40 | 41 | // FIXME: There's currently no good way to verify whether an input was validated using shallow or deep comparison. 42 | // All we can do for now is verify that the values were validated. 43 | // Note: It maybe confusing why it validates `input_high` when the write has `Durability::LOW`. 44 | // This is because all values must be validated whenever a write occurs. It doesn't mean that it 45 | // executed the query. 46 | db.assert_logs(expect![[r#" 47 | [ 48 | "DidSetCancellationFlag", 49 | "WillCheckCancellation", 50 | "DidValidateMemoizedValue { database_key: tracked_fn(Id(0)) }", 51 | "WillCheckCancellation", 52 | "DidValidateMemoizedValue { database_key: tracked_fn(Id(1)) }", 53 | ]"#]]); 54 | } 55 | -------------------------------------------------------------------------------- /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_orphan_escape_hatch.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | #![allow(warnings)] 4 | 5 | use std::marker::PhantomData; 6 | 7 | #[salsa::input] 8 | struct MyInput { 9 | field: u32, 10 | } 11 | 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 13 | struct NotUpdate<'a>(PhantomData &'a ()>); 14 | 15 | #[salsa::tracked(unsafe(non_update_return_type))] 16 | fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> NotUpdate<'_> { 17 | NotUpdate(PhantomData) 18 | } 19 | 20 | #[test] 21 | fn execute() { 22 | let mut db = salsa::DatabaseImpl::new(); 23 | let input = MyInput::new(&db, 22); 24 | tracked_fn(&db, input); 25 | } 26 | -------------------------------------------------------------------------------- /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(debug)] 7 | struct MyInput { 8 | field: u32, 9 | } 10 | 11 | #[salsa::tracked(debug)] 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: &'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(returns(ref))] 9 | fn test(db: &dyn salsa::Database, input: Input) -> Vec { 10 | (0..input.number(db)).map(|i| format!("test {i}")).collect() 11 | } 12 | 13 | #[test] 14 | fn invoke() { 15 | salsa::DatabaseImpl::new().attach(|db| { 16 | let input = Input::new(db, 3); 17 | let x: &Vec = test(db, input); 18 | expect_test::expect![[r#" 19 | [ 20 | "test 0", 21 | "test 1", 22 | "test 2", 23 | ] 24 | "#]] 25 | .assert_debug_eq(x); 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /tests/tracked_method.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn on a `salsa::input` 2 | //! compiles and executes successfully. 3 | #![allow(warnings)] 4 | 5 | use common::LogDatabase as _; 6 | use expect_test::expect; 7 | 8 | mod common; 9 | 10 | trait TrackedTrait { 11 | fn tracked_trait_fn(self, db: &dyn salsa::Database) -> u32; 12 | } 13 | 14 | #[salsa::input] 15 | struct MyInput { 16 | field: u32, 17 | } 18 | 19 | #[salsa::tracked] 20 | impl MyInput { 21 | #[salsa::tracked] 22 | fn tracked_fn(self, db: &dyn salsa::Database) -> u32 { 23 | self.field(db) * 2 24 | } 25 | 26 | #[salsa::tracked(returns(ref))] 27 | fn tracked_fn_ref(self, db: &dyn salsa::Database) -> u32 { 28 | self.field(db) * 3 29 | } 30 | } 31 | 32 | #[salsa::tracked] 33 | impl TrackedTrait for MyInput { 34 | #[salsa::tracked] 35 | fn tracked_trait_fn(self, db: &dyn salsa::Database) -> u32 { 36 | self.field(db) * 4 37 | } 38 | } 39 | 40 | #[test] 41 | fn execute() { 42 | let mut db = salsa::DatabaseImpl::new(); 43 | let object = MyInput::new(&mut db, 22); 44 | // assert_eq!(object.tracked_fn(&db), 44); 45 | // assert_eq!(*object.tracked_fn_ref(&db), 66); 46 | assert_eq!(object.tracked_trait_fn(&db), 88); 47 | } 48 | 49 | #[test] 50 | fn debug_name() { 51 | let mut db = common::ExecuteValidateLoggerDatabase::default(); 52 | let object = MyInput::new(&mut db, 22); 53 | 54 | assert_eq!(object.tracked_trait_fn(&db), 88); 55 | db.assert_logs(expect![[r#" 56 | [ 57 | "salsa_event(WillExecute { database_key: tracked_trait_fn_(Id(0)) })", 58 | ]"#]]); 59 | } 60 | -------------------------------------------------------------------------------- /tests/tracked_method_inherent_return_deref.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(returns(deref))] 11 | fn test(self, db: &dyn salsa::Database) -> Vec { 12 | (0..self.number(db)).map(|i| format!("test {i}")).collect() 13 | } 14 | } 15 | 16 | #[test] 17 | fn invoke() { 18 | salsa::DatabaseImpl::new().attach(|db| { 19 | let input = Input::new(db, 3); 20 | let x: &[String] = input.test(db); 21 | 22 | assert_eq!( 23 | x, 24 | &[ 25 | "test 0".to_string(), 26 | "test 1".to_string(), 27 | "test 2".to_string() 28 | ] 29 | ); 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /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(returns(ref))] 11 | fn test(self, db: &dyn salsa::Database) -> Vec { 12 | (0..self.number(db)).map(|i| format!("test {i}")).collect() 13 | } 14 | } 15 | 16 | #[test] 17 | fn invoke() { 18 | salsa::DatabaseImpl::new().attach(|db| { 19 | let input = Input::new(db, 3); 20 | let x: &Vec = input.test(db); 21 | expect_test::expect![[r#" 22 | [ 23 | "test 0", 24 | "test 1", 25 | "test 2", 26 | ] 27 | "#]] 28 | .assert_debug_eq(x); 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /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(returns(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(returns(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(returns(ref))] 15 | fn test(self, db: &dyn salsa::Database) -> Vec { 16 | (0..self.number(db)).map(|i| format!("test {i}")).collect() 17 | } 18 | } 19 | 20 | #[test] 21 | fn invoke() { 22 | salsa::DatabaseImpl::new().attach(|db| { 23 | let input = Input::new(db, 3); 24 | let x: &Vec = input.test(db); 25 | expect_test::expect![[r#" 26 | [ 27 | "test 0", 28 | "test 1", 29 | "test 2", 30 | ] 31 | "#]] 32 | .assert_debug_eq(x); 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /tests/tracked_method_with_self_ty.rs: -------------------------------------------------------------------------------- 1 | //! Test that a `tracked` fn with `Self` in its signature or body on a `salsa::input` 2 | //! compiles and executes successfully. 3 | #![allow(warnings)] 4 | 5 | trait TrackedTrait { 6 | type Type; 7 | 8 | fn tracked_trait_fn(self, db: &dyn salsa::Database, ty: Self::Type) -> Self::Type; 9 | 10 | fn untracked_trait_fn(); 11 | } 12 | 13 | #[salsa::input] 14 | struct MyInput { 15 | field: u32, 16 | } 17 | 18 | #[salsa::tracked] 19 | impl MyInput { 20 | #[salsa::tracked] 21 | fn tracked_fn(self, db: &dyn salsa::Database, other: Self) -> u32 { 22 | self.field(db) + other.field(db) 23 | } 24 | } 25 | 26 | #[salsa::tracked] 27 | impl TrackedTrait for MyInput { 28 | type Type = u32; 29 | 30 | #[salsa::tracked] 31 | fn tracked_trait_fn(self, db: &dyn salsa::Database, ty: Self::Type) -> Self::Type { 32 | Self::untracked_trait_fn(); 33 | Self::tracked_fn(self, db, self) + ty 34 | } 35 | 36 | fn untracked_trait_fn() {} 37 | } 38 | 39 | #[test] 40 | fn execute() { 41 | let mut db = salsa::DatabaseImpl::new(); 42 | let object = MyInput::new(&mut db, 10); 43 | assert_eq!(object.tracked_trait_fn(&db, 1), 21); 44 | } 45 | -------------------------------------------------------------------------------- /tests/tracked_struct.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use salsa::{Database, Setter}; 4 | 5 | #[salsa::tracked] 6 | struct Tracked<'db> { 7 | untracked_1: usize, 8 | 9 | untracked_2: usize, 10 | } 11 | 12 | #[salsa::input] 13 | struct MyInput { 14 | field1: usize, 15 | field2: usize, 16 | } 17 | 18 | #[salsa::tracked] 19 | fn intermediate(db: &dyn salsa::Database, input: MyInput) -> Tracked<'_> { 20 | Tracked::new(db, input.field1(db), input.field2(db)) 21 | } 22 | 23 | #[salsa::tracked] 24 | fn accumulate(db: &dyn salsa::Database, input: MyInput) -> (usize, usize) { 25 | let tracked = intermediate(db, input); 26 | let one = read_tracked_1(db, tracked); 27 | let two = read_tracked_2(db, tracked); 28 | 29 | (one, two) 30 | } 31 | 32 | #[salsa::tracked] 33 | fn read_tracked_1<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { 34 | tracked.untracked_1(db) 35 | } 36 | 37 | #[salsa::tracked] 38 | fn read_tracked_2<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { 39 | tracked.untracked_2(db) 40 | } 41 | 42 | #[test] 43 | fn execute() { 44 | let mut db = salsa::DatabaseImpl::default(); 45 | let input = MyInput::new(&db, 1, 1); 46 | 47 | assert_eq!(accumulate(&db, input), (1, 1)); 48 | 49 | // Should only re-execute `read_tracked_1`. 50 | input.set_field1(&mut db).to(2); 51 | assert_eq!(accumulate(&db, input), (2, 1)); 52 | 53 | // Should only re-execute `read_tracked_2`. 54 | input.set_field2(&mut db).to(2); 55 | assert_eq!(accumulate(&db, input), (2, 2)); 56 | } 57 | -------------------------------------------------------------------------------- /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_struct_mixed_tracked_fields.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use salsa::{Database, Setter}; 4 | 5 | // A tracked struct with mixed tracked and untracked fields to ensure 6 | // the correct field indices are used when tracking dependencies. 7 | #[salsa::tracked] 8 | struct Tracked<'db> { 9 | untracked_1: usize, 10 | 11 | #[tracked] 12 | tracked_1: usize, 13 | 14 | untracked_2: usize, 15 | 16 | untracked_3: usize, 17 | 18 | #[tracked] 19 | tracked_2: usize, 20 | 21 | untracked_4: usize, 22 | } 23 | 24 | #[salsa::input] 25 | struct MyInput { 26 | field1: usize, 27 | field2: usize, 28 | } 29 | 30 | #[salsa::tracked] 31 | fn intermediate(db: &dyn salsa::Database, input: MyInput) -> Tracked<'_> { 32 | Tracked::new(db, 0, input.field1(db), 0, 0, input.field2(db), 0) 33 | } 34 | 35 | #[salsa::tracked] 36 | fn accumulate(db: &dyn salsa::Database, input: MyInput) -> (usize, usize) { 37 | let tracked = intermediate(db, input); 38 | let one = read_tracked_1(db, tracked); 39 | let two = read_tracked_2(db, tracked); 40 | 41 | (one, two) 42 | } 43 | 44 | #[salsa::tracked] 45 | fn read_tracked_1<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { 46 | tracked.tracked_1(db) 47 | } 48 | 49 | #[salsa::tracked] 50 | fn read_tracked_2<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { 51 | tracked.tracked_2(db) 52 | } 53 | 54 | #[test] 55 | fn execute() { 56 | let mut db = salsa::DatabaseImpl::default(); 57 | let input = MyInput::new(&db, 1, 1); 58 | 59 | assert_eq!(accumulate(&db, input), (1, 1)); 60 | 61 | // Should only re-execute `read_tracked_1`. 62 | input.set_field1(&mut db).to(2); 63 | assert_eq!(accumulate(&db, input), (2, 1)); 64 | 65 | // Should only re-execute `read_tracked_2`. 66 | input.set_field2(&mut db).to(2); 67 | assert_eq!(accumulate(&db, input), (2, 2)); 68 | } 69 | -------------------------------------------------------------------------------- /tests/tracked_struct_recreate_new_revision.rs: -------------------------------------------------------------------------------- 1 | //! Test that re-creating a `tracked` struct after it was deleted in a previous 2 | //! revision doesn't panic. 3 | #![allow(warnings)] 4 | 5 | use salsa::Setter; 6 | 7 | #[salsa::input] 8 | struct MyInput { 9 | field: u32, 10 | } 11 | 12 | #[salsa::tracked(debug)] 13 | struct TrackedStruct<'db> { 14 | field: u32, 15 | } 16 | 17 | #[salsa::tracked] 18 | fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> Option> { 19 | if input.field(db) == 1 { 20 | Some(TrackedStruct::new(db, 1)) 21 | } else { 22 | None 23 | } 24 | } 25 | 26 | #[test] 27 | fn execute() { 28 | let mut db = salsa::DatabaseImpl::new(); 29 | let input = MyInput::new(&db, 1); 30 | assert!(tracked_fn(&db, input).is_some()); 31 | input.set_field(&mut db).to(0); 32 | assert_eq!(tracked_fn(&db, input), None); 33 | input.set_field(&mut db).to(1); 34 | assert!(tracked_fn(&db, input).is_some()); 35 | } 36 | -------------------------------------------------------------------------------- /tests/tracked_struct_with_interned_query.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use salsa::Setter; 4 | 5 | #[salsa::input] 6 | struct MyInput { 7 | value: usize, 8 | } 9 | 10 | #[salsa::tracked] 11 | struct Tracked<'db> { 12 | value: String, 13 | } 14 | 15 | #[salsa::tracked] 16 | fn query_tracked(db: &dyn salsa::Database, input: MyInput) -> Tracked<'_> { 17 | Tracked::new(db, format!("{value}", value = input.value(db))) 18 | } 19 | 20 | #[salsa::tracked] 21 | fn join<'db>(db: &'db dyn salsa::Database, tracked: Tracked<'db>, with: String) -> String { 22 | format!("{}{}", tracked.value(db), with) 23 | } 24 | 25 | #[test] 26 | fn execute() { 27 | let mut db = salsa::DatabaseImpl::default(); 28 | let input = MyInput::new(&db, 1); 29 | 30 | let tracked = query_tracked(&db, input); 31 | let joined = join(&db, tracked, "world".to_string()); 32 | 33 | assert_eq!(joined, "1world"); 34 | 35 | // Create a new revision: This puts the tracked struct created in revision 0 36 | // into the free list. 37 | input.set_value(&mut db).to(2); 38 | 39 | let tracked = query_tracked(&db, input); 40 | let joined = join(&db, tracked, "world".to_string()); 41 | 42 | assert_eq!(joined, "2world"); 43 | 44 | // Create a new revision: The tracked struct created in revision 0 is now 45 | // reused, including its id. The argument to `join` will hash and compare 46 | // equal to the argument used in revision 0 but the return value should be 47 | // 3world and not 1world. 48 | input.set_value(&mut db).to(3); 49 | 50 | let tracked = query_tracked(&db, input); 51 | let joined = join(&db, tracked, "world".to_string()); 52 | 53 | assert_eq!(joined, "3world"); 54 | } 55 | -------------------------------------------------------------------------------- /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 | #[tracked] 14 | field: MyInterned<'db>, 15 | } 16 | 17 | #[salsa::interned] 18 | struct MyInterned<'db> { 19 | field: String, 20 | } 21 | 22 | #[test] 23 | fn execute() {} 24 | -------------------------------------------------------------------------------- /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, Update}; 5 | use test_log::test; 6 | 7 | #[salsa::input(debug)] 8 | struct MyInput { 9 | field: String, 10 | } 11 | 12 | #[salsa::tracked(debug)] 13 | struct MyTracked<'db> { 14 | #[tracked] 15 | data: MyInput, 16 | #[tracked] 17 | next: MyList<'db>, 18 | } 19 | 20 | #[derive(PartialEq, Eq, Clone, Debug, Update)] 21 | enum MyList<'db> { 22 | None, 23 | Next(MyTracked<'db>), 24 | } 25 | 26 | #[salsa::tracked] 27 | fn create_tracked_list(db: &dyn Database, input: MyInput) -> MyTracked<'_> { 28 | let t0 = MyTracked::new(db, input, MyList::None); 29 | let t1 = MyTracked::new(db, input, MyList::Next(t0)); 30 | t1 31 | } 32 | 33 | #[test] 34 | fn execute() { 35 | DatabaseImpl::new().attach(|db| { 36 | let input = MyInput::new(db, "foo".to_string()); 37 | let t0: MyTracked = create_tracked_list(db, input); 38 | let t1 = create_tracked_list(db, input); 39 | expect_test::expect![[r#" 40 | MyTracked { 41 | [salsa id]: Id(401), 42 | data: MyInput { 43 | [salsa id]: Id(0), 44 | field: "foo", 45 | }, 46 | next: Next( 47 | MyTracked { 48 | [salsa id]: Id(400), 49 | data: MyInput { 50 | [salsa id]: Id(0), 51 | field: "foo", 52 | }, 53 | next: None, 54 | }, 55 | ), 56 | } 57 | "#]] 58 | .assert_debug_eq(&t0); 59 | assert_eq!(t0, t1); 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /tests/tracked_with_struct_ord.rs: -------------------------------------------------------------------------------- 1 | //! Test that `PartialOrd` and `Ord` can be derived for tracked structs 2 | 3 | use salsa::{Database, DatabaseImpl}; 4 | use test_log::test; 5 | 6 | #[salsa::input] 7 | #[derive(PartialOrd, Ord)] 8 | struct Input { 9 | value: usize, 10 | } 11 | 12 | #[salsa::tracked(debug)] 13 | #[derive(Ord, PartialOrd)] 14 | struct MyTracked<'db> { 15 | value: usize, 16 | } 17 | 18 | #[salsa::tracked] 19 | fn create_tracked(db: &dyn Database, input: Input) -> MyTracked<'_> { 20 | MyTracked::new(db, input.value(db)) 21 | } 22 | 23 | #[test] 24 | fn execute() { 25 | DatabaseImpl::new().attach(|db| { 26 | let input1 = Input::new(db, 20); 27 | let input2 = Input::new(db, 10); 28 | 29 | // Compares by ID and not by value. 30 | assert!(input1 <= input2); 31 | 32 | let t0: MyTracked = create_tracked(db, input1); 33 | let t1: MyTracked = create_tracked(db, input2); 34 | 35 | assert!(t0 <= t1); 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /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 | #[returns(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(returns(ref))] 13 | pub fn all_items(self, _db: &'db dyn Db) -> Vec { 14 | todo!() 15 | } 16 | } 17 | 18 | #[salsa::tracked(returns(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 | --------------------------------------------------------------------------------