From 00124800ea83f8ca0c1955dcf1696215b62ce802 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Thu, 19 Sep 2024 22:00:22 +0200 Subject: feat: Highlight exit points of async blocks Async blocks act similar to async functions in that the await keywords are related, but also act like functions where the exit points are related. --- .../crates/ide/src/highlight_related.rs | 164 ++++++++++++--------- 1 file changed, 93 insertions(+), 71 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs index 4c8e3fc3040..97b7ba78129 100644 --- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs +++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs @@ -281,99 +281,95 @@ fn highlight_references( } } -// If `file_id` is None, -pub(crate) fn highlight_exit_points( +fn hl_exit_points( sema: &Semantics<'_, RootDatabase>, - token: SyntaxToken, -) -> FxHashMap> { - fn hl( - sema: &Semantics<'_, RootDatabase>, - def_token: Option, - body: ast::Expr, - ) -> Option>> { - let mut highlights: FxHashMap> = FxHashMap::default(); + def_token: Option, + body: ast::Expr, +) -> Option>> { + let mut highlights: FxHashMap> = FxHashMap::default(); + + let mut push_to_highlights = |file_id, range| { + if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) { + let hrange = HighlightedRange { category: ReferenceCategory::empty(), range }; + highlights.entry(file_id).or_default().insert(hrange); + } + }; - let mut push_to_highlights = |file_id, range| { - if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) { - let hrange = HighlightedRange { category: ReferenceCategory::empty(), range }; - highlights.entry(file_id).or_default().insert(hrange); + if let Some(tok) = def_token { + let file_id = sema.hir_file_for(&tok.parent()?); + let range = Some(tok.text_range()); + push_to_highlights(file_id, range); + } + + WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + let range = match &expr { + ast::Expr::TryExpr(try_) => try_.question_mark_token().map(|token| token.text_range()), + ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) + if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) => + { + Some(expr.syntax().text_range()) } + _ => None, }; - if let Some(tok) = def_token { - let file_id = sema.hir_file_for(&tok.parent()?); - let range = Some(tok.text_range()); - push_to_highlights(file_id, range); - } + push_to_highlights(file_id, range); + }); - WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| { + // We should handle `return` separately, because when it is used in a `try` block, + // it will exit the outside function instead of the block itself. + WalkExpandedExprCtx::new(sema) + .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure) + .walk(&body, &mut |_, expr| { let file_id = sema.hir_file_for(expr.syntax()); let range = match &expr { - ast::Expr::TryExpr(try_) => { - try_.question_mark_token().map(|token| token.text_range()) - } - ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) - if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) => - { - Some(expr.syntax().text_range()) - } + ast::Expr::ReturnExpr(expr) => expr.return_token().map(|token| token.text_range()), _ => None, }; push_to_highlights(file_id, range); }); - // We should handle `return` separately, because when it is used in a `try` block, - // it will exit the outside function instead of the block itself. - WalkExpandedExprCtx::new(sema) - .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure) - .walk(&body, &mut |_, expr| { - let file_id = sema.hir_file_for(expr.syntax()); - - let range = match &expr { - ast::Expr::ReturnExpr(expr) => { - expr.return_token().map(|token| token.text_range()) - } - _ => None, - }; - - push_to_highlights(file_id, range); - }); - - let tail = match body { - ast::Expr::BlockExpr(b) => b.tail_expr(), - e => Some(e), - }; + let tail = match body { + ast::Expr::BlockExpr(b) => b.tail_expr(), + e => Some(e), + }; - if let Some(tail) = tail { - for_each_tail_expr(&tail, &mut |tail| { - let file_id = sema.hir_file_for(tail.syntax()); - let range = match tail { - ast::Expr::BreakExpr(b) => b - .break_token() - .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()), - _ => tail.syntax().text_range(), - }; - push_to_highlights(file_id, Some(range)); - }); - } - Some(highlights) + if let Some(tail) = tail { + for_each_tail_expr(&tail, &mut |tail| { + let file_id = sema.hir_file_for(tail.syntax()); + let range = match tail { + ast::Expr::BreakExpr(b) => b + .break_token() + .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()), + _ => tail.syntax().text_range(), + }; + push_to_highlights(file_id, Some(range)); + }); } + Some(highlights) +} +// If `file_id` is None, +pub(crate) fn highlight_exit_points( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, +) -> FxHashMap> { let mut res = FxHashMap::default(); for def in goto_definition::find_fn_or_blocks(sema, &token) { let new_map = match_ast! { match def { - ast::Fn(fn_) => fn_.body().and_then(|body| hl(sema, fn_.fn_token(), body.into())), + ast::Fn(fn_) => fn_.body().and_then(|body| hl_exit_points(sema, fn_.fn_token(), body.into())), ast::ClosureExpr(closure) => { let pipe_tok = closure.param_list().and_then(|p| p.pipe_token()); - closure.body().and_then(|body| hl(sema, pipe_tok, body)) + closure.body().and_then(|body| hl_exit_points(sema, pipe_tok, body)) }, ast::BlockExpr(blk) => match blk.modifier() { - Some(ast::BlockModifier::Async(t)) => hl(sema, Some(t), blk.into()), + Some(ast::BlockModifier::Async(t)) => hl_exit_points(sema, Some(t), blk.into()), Some(ast::BlockModifier::Try(t)) if token.kind() != T![return] => { - hl(sema, Some(t), blk.into()) + hl_exit_points(sema, Some(t), blk.into()) }, _ => continue, }, @@ -520,6 +516,12 @@ pub(crate) fn highlight_yield_points( if block_expr.async_token().is_none() { continue; } + + // Async blocks act similar to closures. So we want to + // highlight their exit points too. + let exit_points = hl_exit_points(sema, block_expr.async_token(), block_expr.clone().into()); + merge_map(&mut res, exit_points); + hl(sema, block_expr.async_token(), Some(block_expr.into())) }, ast::ClosureExpr(closure) => hl(sema, closure.async_token(), closure.body()), @@ -876,6 +878,27 @@ pub async$0 fn foo() { ); } + #[test] + fn test_hl_exit_points_of_async_blocks() { + check( + r#" +pub fn foo() { + let x = async$0 { + // ^^^^^ + 0.await; + // ^^^^^ + 0?; + // ^ + return 0; + // ^^^^^^ + 0 + // ^ + }; +} +"#, + ); + } + #[test] fn test_hl_let_else_yield_points() { check( @@ -925,11 +948,10 @@ async fn foo() { async fn foo() { (async { // ^^^^^ - (async { - 0.await - }).await$0 } - // ^^^^^ - ).await; + (async { 0.await }).await$0 + // ^^^^^^^^^^^^^^^^^^^^^^^^^ + // ^^^^^ + }).await; } "#, ); -- cgit 1.4.1-3-g733a5 From a31ef1059f5f9e1a08d52ced7f7e171f8ec474a3 Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Fri, 20 Sep 2024 11:28:43 +0200 Subject: Add flake.nix and .envrc --- .envrc | 3 +++ .gitignore | 2 ++ src/tools/nix-dev-shell/flake.nix | 33 +++++++++++++++++++++++ src/tools/nix-dev-shell/x/default.nix | 22 +++++++++++++++ src/tools/nix-dev-shell/x/x.rs | 50 +++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+) create mode 100644 .envrc create mode 100644 src/tools/nix-dev-shell/flake.nix create mode 100644 src/tools/nix-dev-shell/x/default.nix create mode 100644 src/tools/nix-dev-shell/x/x.rs (limited to 'src') diff --git a/.envrc b/.envrc new file mode 100644 index 00000000000..ed519d8d962 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +if nix flake show path:./src/tools/nix-dev-shell &> /dev/null; then + use flake path:./src/tools/nix-dev-shell +fi diff --git a/.gitignore b/.gitignore index 2e6499081a6..89c62e126b0 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,8 @@ build/ /src/tools/x/target # Created by default with `src/ci/docker/run.sh` /obj/ +# Created by nix dev shell / .envrc +src/tools/nix-dev-shell/flake.lock ## ICE reports rustc-ice-*.txt diff --git a/src/tools/nix-dev-shell/flake.nix b/src/tools/nix-dev-shell/flake.nix new file mode 100644 index 00000000000..8ab5e097427 --- /dev/null +++ b/src/tools/nix-dev-shell/flake.nix @@ -0,0 +1,33 @@ +{ + description = "rustc dev shell"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + x = import ./x { inherit pkgs; }; + in + { + devShells.default = with pkgs; mkShell { + name = "rustc-dev-shell"; + nativeBuildInputs = with pkgs; [ + binutils cmake ninja pkg-config python3 git curl cacert patchelf nix + ]; + buildInputs = with pkgs; [ + openssl glibc.out glibc.static x + ]; + # Avoid creating text files for ICEs. + RUSTC_ICE = "0"; + # Provide `libstdc++.so.6` for the self-contained lld. + LD_LIBRARY_PATH = "${with pkgs; lib.makeLibraryPath [ + stdenv.cc.cc.lib + ]}"; + }; + } + ); +} diff --git a/src/tools/nix-dev-shell/x/default.nix b/src/tools/nix-dev-shell/x/default.nix new file mode 100644 index 00000000000..e6dfbad6f19 --- /dev/null +++ b/src/tools/nix-dev-shell/x/default.nix @@ -0,0 +1,22 @@ +{ + pkgs ? import { }, +}: +pkgs.stdenv.mkDerivation { + name = "x"; + + src = ./x.rs; + dontUnpack = true; + + nativeBuildInputs = with pkgs; [ rustc ]; + + buildPhase = '' + PYTHON=${pkgs.lib.getExe pkgs.python3} rustc -Copt-level=3 --crate-name x $src --out-dir $out/bin + ''; + + meta = with pkgs.lib; { + description = "Helper for rust-lang/rust x.py"; + homepage = "https://github.com/rust-lang/rust/blob/master/src/tools/x"; + license = licenses.mit; + mainProgram = "x"; + }; +} diff --git a/src/tools/nix-dev-shell/x/x.rs b/src/tools/nix-dev-shell/x/x.rs new file mode 100644 index 00000000000..9f83b8fd62e --- /dev/null +++ b/src/tools/nix-dev-shell/x/x.rs @@ -0,0 +1,50 @@ +// git clone https://github.com/rust-lang/rust/blob/0ea7ddcc35a2fcaa5da8a7dcfc118c9fb4a63b95/src/tools/x/src/main.rs +// patched to stop doing python probing, stop the probe, please dont, i have a python +//! Run bootstrap from any subdirectory of a rust compiler checkout. +//! +//! We prefer `exec`, to avoid adding an extra process in the process tree. +//! However, since `exec` isn't available on Windows, we indirect through +//! `exec_or_status`, which will call `exec` on unix and `status` on Windows. +//! +//! We use `powershell.exe x.ps1` on Windows, and `sh -c x` on Unix, those are +//! the ones that call `x.py`. We use `sh -c` on Unix, because it is a standard. +//! We also don't use `pwsh` on Windows, because it is not installed by default; + +use std::env; +use std::os::unix::process::CommandExt; +use std::process::{self, Command}; + +fn main() { + match env::args().skip(1).next().as_deref() { + Some("--wrapper-version") => { + println!("0.1.0"); + return; + } + _ => {} + } + let current = match env::current_dir() { + Ok(dir) => dir, + Err(err) => { + eprintln!("Failed to get current directory: {err}"); + process::exit(1); + } + }; + + for dir in current.ancestors() { + let candidate = dir.join("x.py"); + if candidate.exists() { + let mut cmd = Command::new(env!("PYTHON")); + cmd.arg(dir.join("x.py")); + cmd.args(env::args().skip(1)).current_dir(dir); + + let error = cmd.exec(); + eprintln!("Failed to invoke `{:?}`: {}", cmd, error); + } + } + + eprintln!( + "x.py not found. Please run inside of a checkout of `https://github.com/rust-lang/rust`." + ); + + process::exit(1); +} -- cgit 1.4.1-3-g733a5 From 002a6b134f910be617722f9d25e0ea8016c2a54e Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sat, 21 Sep 2024 11:44:38 +0200 Subject: Add an option of using nix-shell instead of nix flake --- .envrc | 3 --- src/tools/nix-dev-shell/envrc-flake | 8 ++++++++ src/tools/nix-dev-shell/envrc-shell | 7 +++++++ src/tools/nix-dev-shell/shell.nix | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) delete mode 100644 .envrc create mode 100644 src/tools/nix-dev-shell/envrc-flake create mode 100644 src/tools/nix-dev-shell/envrc-shell create mode 100644 src/tools/nix-dev-shell/shell.nix (limited to 'src') diff --git a/.envrc b/.envrc deleted file mode 100644 index ed519d8d962..00000000000 --- a/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -if nix flake show path:./src/tools/nix-dev-shell &> /dev/null; then - use flake path:./src/tools/nix-dev-shell -fi diff --git a/src/tools/nix-dev-shell/envrc-flake b/src/tools/nix-dev-shell/envrc-flake new file mode 100644 index 00000000000..218d88d8721 --- /dev/null +++ b/src/tools/nix-dev-shell/envrc-flake @@ -0,0 +1,8 @@ +# If you want to use this as an .envrc file to create a shell with necessery components +# to develop rustc, use the following command in the root of the rusr checkout: +# +# ln -s ./src/tools/nix-dev-shell/envrc-flake ./.envrc && echo .envrc >> .git/info/exclude + +if nix flake show path:./src/tools/nix-dev-shell &> /dev/null; then + use flake path:./src/tools/nix-dev-shell +fi diff --git a/src/tools/nix-dev-shell/envrc-shell b/src/tools/nix-dev-shell/envrc-shell new file mode 100644 index 00000000000..fb7231a6c30 --- /dev/null +++ b/src/tools/nix-dev-shell/envrc-shell @@ -0,0 +1,7 @@ +# If you want to use this as an .envrc file to create a shell with necessery components +# to develop rustc, use the following command in the root of the rusr checkout: +# +# ln -s ./src/tools/nix-dev-shell/envrc-shell ./.envrc && echo .envrc >> .git/info/exclude + +use nix ./src/tools/nix-dev-shell/shell.nix + diff --git a/src/tools/nix-dev-shell/shell.nix b/src/tools/nix-dev-shell/shell.nix new file mode 100644 index 00000000000..8a5cbb7c89e --- /dev/null +++ b/src/tools/nix-dev-shell/shell.nix @@ -0,0 +1,19 @@ +{ pkgs ? import {} }: +let + x = import ./x { inherit pkgs; }; +in +pkgs.mkShell { + name = "rustc"; + nativeBuildInputs = with pkgs; [ + binutils cmake ninja pkg-config python3 git curl cacert patchelf nix + ]; + buildInputs = with pkgs; [ + openssl glibc.out glibc.static x + ]; + # Avoid creating text files for ICEs. + RUSTC_ICE = "0"; + # Provide `libstdc++.so.6` for the self-contained lld. + LD_LIBRARY_PATH = "${with pkgs; lib.makeLibraryPath [ + stdenv.cc.cc.lib + ]}"; +} -- cgit 1.4.1-3-g733a5 From 8115a1843e4ab020695ba67968e7921ecaaf524b Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 30 Sep 2024 19:26:16 +0300 Subject: Comment out cast checks for unknown ptr kind Just like we don't check for types containing unknown. --- .../rust-analyzer/crates/hir-ty/src/infer/cast.rs | 19 +++++--- .../ide-diagnostics/src/handlers/invalid_cast.rs | 54 ++++++++++++++-------- 2 files changed, 48 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs index caa3960a227..5d5bff08644 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs @@ -70,8 +70,9 @@ pub enum CastError { NeedViaThinPtr, NeedViaInt, NonScalar, - UnknownCastPtrKind, - UnknownExprPtrKind, + // We don't want to report errors with unknown types currently. + // UnknownCastPtrKind, + // UnknownExprPtrKind, } impl CastError { @@ -272,9 +273,10 @@ impl CastCheck { match (src_kind, dst_kind) { (Some(PointerKind::Error), _) | (_, Some(PointerKind::Error)) => Ok(()), - (_, None) => Err(CastError::UnknownCastPtrKind), + // (_, None) => Err(CastError::UnknownCastPtrKind), + // (None, _) => Err(CastError::UnknownExprPtrKind), + (_, None) | (None, _) => Ok(()), (_, Some(PointerKind::Thin)) => Ok(()), - (None, _) => Err(CastError::UnknownExprPtrKind), (Some(PointerKind::Thin), _) => Err(CastError::SizedUnsizedCast), (Some(PointerKind::VTable(src_tty)), Some(PointerKind::VTable(dst_tty))) => { let principal = |tty: &Binders| { @@ -315,7 +317,8 @@ impl CastCheck { expr_ty: &Ty, ) -> Result<(), CastError> { match pointer_kind(expr_ty, table).map_err(|_| CastError::Unknown)? { - None => Err(CastError::UnknownExprPtrKind), + // None => Err(CastError::UnknownExprPtrKind), + None => Ok(()), Some(PointerKind::Error) => Ok(()), Some(PointerKind::Thin) => Ok(()), _ => Err(CastError::NeedViaThinPtr), @@ -328,7 +331,8 @@ impl CastCheck { cast_ty: &Ty, ) -> Result<(), CastError> { match pointer_kind(cast_ty, table).map_err(|_| CastError::Unknown)? { - None => Err(CastError::UnknownCastPtrKind), + // None => Err(CastError::UnknownCastPtrKind), + None => Ok(()), Some(PointerKind::Error) => Ok(()), Some(PointerKind::Thin) => Ok(()), Some(PointerKind::VTable(_)) => Err(CastError::IntToFatCast), @@ -343,7 +347,8 @@ impl CastCheck { cast_ty: &Ty, ) -> Result<(), CastError> { match pointer_kind(cast_ty, table).map_err(|_| CastError::Unknown)? { - None => Err(CastError::UnknownCastPtrKind), + // None => Err(CastError::UnknownCastPtrKind), + None => Ok(()), Some(PointerKind::Error) => Ok(()), Some(PointerKind::Thin) => Ok(()), _ => Err(CastError::IllegalCast), diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs index ad4baf5e3a4..4bd29b8c79b 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs @@ -95,10 +95,10 @@ pub(crate) fn invalid_cast(ctx: &DiagnosticsContext<'_>, d: &hir::InvalidCast) - DiagnosticCode::RustcHardError("E0605"), format_ty!(ctx, "non-primitive cast: `{}` as `{}`", d.expr_ty, d.cast_ty), ), - CastError::UnknownCastPtrKind | CastError::UnknownExprPtrKind => ( - DiagnosticCode::RustcHardError("E0641"), - "cannot cast to a pointer of an unknown kind".to_owned(), - ), + // CastError::UnknownCastPtrKind | CastError::UnknownExprPtrKind => ( + // DiagnosticCode::RustcHardError("E0641"), + // "cannot cast to a pointer of an unknown kind".to_owned(), + // ), }; Diagnostic::new(code, message, display_range) } @@ -457,20 +457,20 @@ fn foo() { ); } - #[test] - fn order_dependent_cast_inference() { - check_diagnostics( - r#" -//- minicore: sized -fn main() { - let x = &"hello"; - let mut y = 0 as *const _; - //^^^^^^^^^^^^^ error: cannot cast to a pointer of an unknown kind - y = x as *const _; -} -"#, - ); - } + // #[test] + // fn order_dependent_cast_inference() { + // check_diagnostics( + // r#" + // //- minicore: sized + // fn main() { + // let x = &"hello"; + // let mut y = 0 as *const _; + // //^^^^^^^^^^^^^ error: cannot cast to a pointer of an unknown kind + // y = x as *const _; + // } + // "#, + // ); + // } #[test] fn ptr_to_ptr_different_regions() { @@ -1111,4 +1111,22 @@ fn foo() { "#, ); } + + #[test] + fn cast_isize_to_infer_pointer() { + check_diagnostics( + r#" +//- minicore: coerce_unsized +struct Foo {} + +struct Wrap<'a>(&'a mut Foo); + +fn main() { + let lparam: isize = 0; + + let _wrap = Wrap(unsafe { &mut *(lparam as *mut _) }); +} + "#, + ); + } } -- cgit 1.4.1-3-g733a5 From 22129b18c02049a7fe14c4a81b399a4ca553785e Mon Sep 17 00:00:00 2001 From: SabrinaJewson Date: Tue, 1 Oct 2024 22:09:31 +0100 Subject: fix: correctly parse `use` in generic parameters --- .../crates/parser/src/grammar/generic_params.rs | 2 +- .../parser/test_data/parser/err/0055_impl_use.rast | 26 ++++++++++++++++++++++ .../parser/test_data/parser/err/0055_impl_use.rs | 2 ++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/err/0055_impl_use.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/err/0055_impl_use.rs (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs index e0fa753fa70..ecfabca092c 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs @@ -145,7 +145,7 @@ fn type_bound(p: &mut Parser<'_>) -> bool { T![for] => types::for_type(p, false), // test precise_capturing // fn captures<'a: 'a, 'b: 'b, T>() -> impl Sized + use<'b, T> {} - T![use] => { + T![use] if p.nth_at(1, T![<]) => { p.bump_any(); generic_param_list(p) } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0055_impl_use.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0055_impl_use.rast new file mode 100644 index 00000000000..751f007df94 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0055_impl_use.rast @@ -0,0 +1,26 @@ +SOURCE_FILE + IMPL + IMPL_KW "impl" + GENERIC_PARAM_LIST + L_ANGLE "<" + TYPE_PARAM + NAME + IDENT "T" + COLON ":" + WHITESPACE "\n" + TYPE_BOUND_LIST + ERROR + USE_KW "use" + WHITESPACE " " + MACRO_CALL + PATH + PATH_SEGMENT + NAME_REF + IDENT "std" + SEMICOLON ";" + WHITESPACE "\n" +error 8: expected R_ANGLE +error 8: expected type +error 11: expected `{` +error 15: expected BANG +error 15: expected `{`, `[`, `(` diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0055_impl_use.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0055_impl_use.rs new file mode 100644 index 00000000000..571552bda84 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0055_impl_use.rs @@ -0,0 +1,2 @@ +impl Date: Wed, 2 Oct 2024 16:47:05 +0100 Subject: fix: Join rustfmt overrideCommand with project root When providing a custom rustfmt command, join it with the project root instead of the workspace root. This fixes rust-analyzer getting the wrong invocation path in projects containing subprojects. This makes the behaviour consistent with how a custom path provided in rust-analyzer.procMacro.server behaves already. Resolves issue #18222 --- .../rust-analyzer/crates/rust-analyzer/src/handlers/request.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 9773d8dbce0..3a1770d770d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -2201,14 +2201,14 @@ fn run_rustfmt( let cmd = Utf8PathBuf::from(&command); let target_spec = TargetSpec::for_file(snap, file_id)?; let mut cmd = match target_spec { - Some(TargetSpec::Cargo(spec)) => { - // approach: if the command name contains a path separator, join it with the workspace root. + Some(TargetSpec::Cargo(_)) => { + // approach: if the command name contains a path separator, join it with the project root. // however, if the path is absolute, joining will result in the absolute path being preserved. // as a fallback, rely on $PATH-based discovery. let cmd_path = if command.contains(std::path::MAIN_SEPARATOR) || (cfg!(windows) && command.contains('/')) { - spec.workspace_root.join(cmd).into() + snap.config.root_path().join(cmd).into() } else { cmd }; -- cgit 1.4.1-3-g733a5 From 090a03a687f26d810646602b5b7bc74990c2784c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 5 Oct 2024 13:42:46 +0200 Subject: Remove ImportSource::ExternCrate as the fixed point loop can't affect it --- .../rust-analyzer/crates/base-db/src/input.rs | 5 + .../crates/hir-def/src/nameres/collector.rs | 233 +++++++++------------ .../crates/hir-def/src/nameres/tests.rs | 1 - .../crates/ide/src/navigation_target.rs | 1 + 4 files changed, 102 insertions(+), 138 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index f5109339ad1..57522d69321 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -288,6 +288,11 @@ pub struct CrateData { /// The cfg options that could be used by the crate pub potential_cfg_options: Option>, pub env: Env, + /// The dependencies of this crate. + /// + /// Note that this may contain more dependencies than the crate actually uses. + /// A common example is the test crate which is included but only actually is active when + /// declared in source via `extern crate test`. pub dependencies: Vec, pub origin: CrateOrigin, pub is_proc_macro: bool, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index 7f1d19719da..a1eaa5a6973 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -30,8 +30,8 @@ use crate::{ db::DefDatabase, item_scope::{ImportId, ImportOrExternCrate, ImportType, PerNsGlobImports}, item_tree::{ - self, AttrOwner, ExternCrate, FieldsShape, FileItemTreeId, ImportKind, ItemTree, - ItemTreeId, ItemTreeNode, Macro2, MacroCall, MacroRules, Mod, ModItem, ModKind, TreeId, + self, AttrOwner, FieldsShape, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, + ItemTreeNode, Macro2, MacroCall, MacroRules, Mod, ModItem, ModKind, TreeId, }, macro_call_as_call_id, macro_call_as_call_id_with_eager, nameres::{ @@ -93,6 +93,7 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, def_map: DefMap, tree_id: TreeI proc_macros, from_glob_import: Default::default(), skip_attrs: Default::default(), + unresolved_extern_crates: Default::default(), is_proc_macro: krate.is_proc_macro, }; if tree_id.is_block() { @@ -128,7 +129,6 @@ impl PartialResolvedImport { #[derive(Clone, Debug, Eq, PartialEq)] enum ImportSource { Use { use_tree: Idx, id: UseId, is_prelude: bool, kind: ImportKind }, - ExternCrate { id: ExternCrateId }, } #[derive(Debug, Eq, PartialEq)] @@ -158,21 +158,6 @@ impl Import { }); }); } - - fn from_extern_crate( - tree: &ItemTree, - item_tree_id: ItemTreeId, - id: ExternCrateId, - ) -> Self { - let it = &tree[item_tree_id.value]; - let visibility = &tree[it.visibility]; - Self { - path: ModPath::from_segments(PathKind::Plain, iter::once(it.name.clone())), - alias: it.alias.clone(), - visibility: visibility.clone(), - source: ImportSource::ExternCrate { id }, - } - } } #[derive(Debug, Eq, PartialEq)] @@ -218,11 +203,16 @@ enum MacroDirectiveKind { struct DefCollector<'a> { db: &'a dyn DefDatabase, def_map: DefMap, + // The dependencies of the current crate, including optional deps like `test`. deps: FxHashMap, glob_imports: FxHashMap>, unresolved_imports: Vec, indeterminate_imports: Vec<(ImportDirective, PerNs)>, unresolved_macros: Vec, + // We'd like to avoid emitting a diagnostics avalanche when some `extern crate` doesn't + // resolve. When we emit diagnostics for unresolved imports, we only do so if the import + // doesn't start with an unresolved crate's name. + unresolved_extern_crates: FxHashSet, mod_dirs: FxHashMap, cfg_options: &'a CfgOptions, /// List of procedural macros defined by this crate. This is read from the dynamic library @@ -310,6 +300,7 @@ impl DefCollector<'_> { } for (name, dep) in &self.deps { + // Add all if dep.is_prelude() { // This is a bit confusing but the gist is that `no_core` and `no_std` remove the // sysroot dependence on `core` and `std` respectively. Our `CrateGraph` is eagerly @@ -329,6 +320,7 @@ impl DefCollector<'_> { if skip { continue; } + crate_data .extern_prelude .insert(name.clone(), (CrateRootModuleId { krate: dep.crate_id }, None)); @@ -789,23 +781,6 @@ impl DefCollector<'_> { .entered(); tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.data.edition); match import.source { - ImportSource::ExternCrate { .. } => { - let name = import - .path - .as_ident() - .expect("extern crate should have been desugared to one-element path"); - - let res = self.resolve_extern_crate(name); - - match res { - Some(res) => PartialResolvedImport::Resolved(PerNs::types( - res.into(), - Visibility::Public, - None, - )), - None => PartialResolvedImport::Unresolved, - } - } ImportSource::Use { .. } => { let res = self.def_map.resolve_path_fp_with_macro( self.db, @@ -837,15 +812,6 @@ impl DefCollector<'_> { } } - fn resolve_extern_crate(&self, name: &Name) -> Option { - if *name == sym::self_.clone() { - cov_mark::hit!(extern_crate_self_as); - Some(self.def_map.crate_root()) - } else { - self.deps.get(name).map(|dep| CrateRootModuleId { krate: dep.crate_id }) - } - } - fn record_resolved_import(&mut self, directive: &ImportDirective) { let _p = tracing::info_span!("record_resolved_import").entered(); @@ -858,8 +824,7 @@ impl DefCollector<'_> { .unwrap_or(Visibility::Public); match import.source { - ImportSource::ExternCrate { .. } - | ImportSource::Use { kind: ImportKind::Plain | ImportKind::TypeOnly, .. } => { + ImportSource::Use { kind: ImportKind::Plain | ImportKind::TypeOnly, .. } => { let name = match &import.alias { Some(ImportAlias::Alias(name)) => Some(name), Some(ImportAlias::Underscore) => None, @@ -873,22 +838,6 @@ impl DefCollector<'_> { }; let imp = match import.source { - // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 - ImportSource::ExternCrate { id, .. } => { - if self.def_map.block.is_none() && module_id == DefMap::ROOT { - if let (Some(ModuleDefId::ModuleId(def)), Some(name)) = - (def.take_types(), name) - { - if let Ok(def) = def.try_into() { - Arc::get_mut(&mut self.def_map.data) - .unwrap() - .extern_prelude - .insert(name.clone(), (def, Some(id))); - } - } - } - ImportType::ExternCrate(id) - } ImportSource::Use { kind, id, use_tree, .. } => { if kind == ImportKind::TypeOnly { def.values = None; @@ -1560,45 +1509,21 @@ impl DefCollector<'_> { } // Emit diagnostics for all remaining unresolved imports. - - // We'd like to avoid emitting a diagnostics avalanche when some `extern crate` doesn't - // resolve. We first emit diagnostics for unresolved extern crates and collect the missing - // crate names. Then we emit diagnostics for unresolved imports, but only if the import - // doesn't start with an unresolved crate's name. Due to renaming and reexports, this is a - // heuristic, but it works in practice. - let mut diagnosed_extern_crates = FxHashSet::default(); - for directive in &self.unresolved_imports { - if let ImportSource::ExternCrate { id } = directive.import.source { - let item_tree_id = id.lookup(self.db).id; - let item_tree = item_tree_id.item_tree(self.db); - let extern_crate = &item_tree[item_tree_id.value]; - - diagnosed_extern_crates.insert(extern_crate.name.clone()); - - self.def_map.diagnostics.push(DefDiagnostic::unresolved_extern_crate( - directive.module_id, - InFile::new(item_tree_id.file_id(), extern_crate.ast_id), - )); - } - } - for directive in &self.unresolved_imports { - if let ImportSource::Use { use_tree, id, is_prelude: _, kind: _ } = - directive.import.source - { - if matches!( - (directive.import.path.segments().first(), &directive.import.path.kind), - (Some(krate), PathKind::Plain | PathKind::Abs) if diagnosed_extern_crates.contains(krate) - ) { - continue; - } - let item_tree_id = id.lookup(self.db).id; - self.def_map.diagnostics.push(DefDiagnostic::unresolved_import( - directive.module_id, - item_tree_id, - use_tree, - )); + let ImportSource::Use { use_tree, id, is_prelude: _, kind: _ } = + directive.import.source; + if matches!( + (directive.import.path.segments().first(), &directive.import.path.kind), + (Some(krate), PathKind::Plain | PathKind::Abs) if self.unresolved_extern_crates.contains(krate) + ) { + continue; } + let item_tree_id = id.lookup(self.db).id; + self.def_map.diagnostics.push(DefDiagnostic::unresolved_import( + directive.module_id, + item_tree_id, + use_tree, + )); } self.def_map @@ -1623,7 +1548,8 @@ impl ModCollector<'_, '_> { fn collect(&mut self, items: &[ModItem], container: ItemContainerId) { let krate = self.def_collector.def_map.krate; - let is_crate_root = self.module_id == DefMap::ROOT; + let is_crate_root = + self.module_id == DefMap::ROOT && self.def_collector.def_map.block.is_none(); // Note: don't assert that inserted value is fresh: it's simply not true // for macros. @@ -1632,10 +1558,7 @@ impl ModCollector<'_, '_> { // Prelude module is always considered to be `#[macro_use]`. if let Some((prelude_module, _use)) = self.def_collector.def_map.prelude { // Don't insert macros from the prelude into blocks, as they can be shadowed by other macros. - if prelude_module.krate != krate - && is_crate_root - && self.def_collector.def_map.block.is_none() - { + if prelude_module.krate != krate && is_crate_root { cov_mark::hit!(prelude_is_macro_use); self.def_collector.import_macros_from_extern_crate( prelude_module.krate, @@ -1709,26 +1632,73 @@ impl ModCollector<'_, '_> { id: ItemTreeId::new(self.tree_id, item_tree_id), } .intern(db); - if is_crate_root { - self.process_macro_use_extern_crate( - item_tree_id, - id, - attrs.by_key(&sym::macro_use).attrs(), + def_map.modules[self.module_id].scope.define_extern_crate_decl(id); + + let item_tree::ExternCrate { name, visibility, alias, ast_id } = + &self.item_tree[item_tree_id]; + + let is_self = *name == sym::self_; + let resolved = if is_self { + cov_mark::hit!(extern_crate_self_as); + Some(def_map.crate_root()) + } else { + self.def_collector + .deps + .get(name) + .map(|dep| CrateRootModuleId { krate: dep.crate_id }) + }; + + let name = match alias { + Some(ImportAlias::Alias(name)) => Some(name), + Some(ImportAlias::Underscore) => None, + None => Some(name), + }; + + if let Some(resolved) = resolved { + let vis = resolve_vis(def_map, &self.item_tree[*visibility]); + + if is_crate_root { + // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 + if let Some(name) = name { + Arc::get_mut(&mut def_map.data) + .unwrap() + .extern_prelude + .insert(name.clone(), (resolved, Some(id))); + } + // they also allow `#[macro_use]` + if !is_self { + self.process_macro_use_extern_crate( + id, + attrs.by_key(&sym::macro_use).attrs(), + resolved.krate, + ); + } + } + + self.def_collector.update( + module_id, + &[( + name.cloned(), + PerNs::types( + resolved.into(), + vis, + Some(ImportOrExternCrate::ExternCrate(id)), + ), + )], + vis, + Some(ImportType::ExternCrate(id)), + ); + } else { + if let Some(name) = name { + self.def_collector.unresolved_extern_crates.insert(name.clone()); + } + self.def_collector.def_map.diagnostics.push( + DefDiagnostic::unresolved_extern_crate( + module_id, + InFile::new(self.file_id(), *ast_id), + ), ); } - - self.def_collector.def_map.modules[self.module_id] - .scope - .define_extern_crate_decl(id); - self.def_collector.unresolved_imports.push(ImportDirective { - module_id: self.module_id, - import: Import::from_extern_crate( - self.item_tree, - ItemTreeId::new(self.tree_id, item_tree_id), - id, - ), - status: PartialResolvedImport::Unresolved, - }) } ModItem::ExternBlock(block) => self.collect( &self.item_tree[block].children, @@ -1939,27 +1909,15 @@ impl ModCollector<'_, '_> { fn process_macro_use_extern_crate<'a>( &mut self, - extern_crate: FileItemTreeId, extern_crate_id: ExternCrateId, macro_use_attrs: impl Iterator, + target_crate: CrateId, ) { - let db = self.def_collector.db; - - let target_crate = - match self.def_collector.resolve_extern_crate(&self.item_tree[extern_crate].name) { - Some(m) if m.krate == self.def_collector.def_map.krate => { - cov_mark::hit!(ignore_macro_use_extern_crate_self); - return; - } - Some(m) => m.krate, - None => return, - }; - cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); - let mut single_imports = Vec::new(); for attr in macro_use_attrs { - let Some(paths) = attr.parse_path_comma_token_tree(db.upcast()) else { + let Some(paths) = attr.parse_path_comma_token_tree(self.def_collector.db.upcast()) + else { // `#[macro_use]` (without any paths) found, forget collected names and just import // all visible macros. self.def_collector.import_macros_from_extern_crate( @@ -2523,6 +2481,7 @@ mod tests { from_glob_import: Default::default(), skip_attrs: Default::default(), is_proc_macro: false, + unresolved_extern_crates: Default::default(), }; collector.seed_with_top_level(); collector.collect(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs index 17e82dc16c4..7b02a89e5de 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs @@ -416,7 +416,6 @@ pub struct Arc; #[test] fn macro_use_extern_crate_self() { - cov_mark::check!(ignore_macro_use_extern_crate_self); check( r#" //- /main.rs crate:main diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs index 9bc7bf411f0..9259243db85 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs @@ -792,6 +792,7 @@ pub(crate) fn orig_range_with_focus_r( .definition_range(db) }; + // FIXME: Also make use of the syntax context to determine which site we are at? let value_range = InFile::new(hir_file, value).original_node_file_range_opt(db); let ((call_site_range, call_site_focus), def_site) = match InFile::new(hir_file, name).original_node_file_range_opt(db) { -- cgit 1.4.1-3-g733a5 From 6b286bc6967b9076f1c97cee5c5dbf43de8b864e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 5 Oct 2024 14:55:39 +0200 Subject: Turn ImportSource into a struct --- .../crates/hir-def/src/nameres/collector.rs | 100 +++++++++++---------- 1 file changed, 54 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index a1eaa5a6973..012dc4773f2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -127,8 +127,11 @@ impl PartialResolvedImport { } #[derive(Clone, Debug, Eq, PartialEq)] -enum ImportSource { - Use { use_tree: Idx, id: UseId, is_prelude: bool, kind: ImportKind }, +struct ImportSource { + use_tree: Idx, + id: UseId, + is_prelude: bool, + kind: ImportKind, } #[derive(Debug, Eq, PartialEq)] @@ -154,7 +157,7 @@ impl Import { path, alias, visibility: visibility.clone(), - source: ImportSource::Use { use_tree: idx, id, is_prelude, kind }, + source: ImportSource { use_tree: idx, id, is_prelude, kind }, }); }); } @@ -780,35 +783,31 @@ impl DefCollector<'_> { let _p = tracing::info_span!("resolve_import", import_path = %import.path.display(self.db.upcast(), Edition::LATEST)) .entered(); tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.data.edition); - match import.source { - ImportSource::Use { .. } => { - let res = self.def_map.resolve_path_fp_with_macro( - self.db, - ResolveMode::Import, - module_id, - &import.path, - BuiltinShadowMode::Module, - None, // An import may resolve to any kind of macro. - ); + let res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Import, + module_id, + &import.path, + BuiltinShadowMode::Module, + None, // An import may resolve to any kind of macro. + ); - let def = res.resolved_def; - if res.reached_fixedpoint == ReachedFixedPoint::No || def.is_none() { - return PartialResolvedImport::Unresolved; - } + let def = res.resolved_def; + if res.reached_fixedpoint == ReachedFixedPoint::No || def.is_none() { + return PartialResolvedImport::Unresolved; + } - if res.from_differing_crate { - return PartialResolvedImport::Resolved( - def.filter_visibility(|v| matches!(v, Visibility::Public)), - ); - } + if res.from_differing_crate { + return PartialResolvedImport::Resolved( + def.filter_visibility(|v| matches!(v, Visibility::Public)), + ); + } - // Check whether all namespaces are resolved. - if def.is_full() { - PartialResolvedImport::Resolved(def) - } else { - PartialResolvedImport::Indeterminate(def) - } - } + // Check whether all namespaces are resolved. + if def.is_full() { + PartialResolvedImport::Resolved(def) + } else { + PartialResolvedImport::Indeterminate(def) } } @@ -824,7 +823,12 @@ impl DefCollector<'_> { .unwrap_or(Visibility::Public); match import.source { - ImportSource::Use { kind: ImportKind::Plain | ImportKind::TypeOnly, .. } => { + ImportSource { + kind: kind @ (ImportKind::Plain | ImportKind::TypeOnly), + id, + use_tree, + .. + } => { let name = match &import.alias { Some(ImportAlias::Alias(name)) => Some(name), Some(ImportAlias::Underscore) => None, @@ -837,24 +841,20 @@ impl DefCollector<'_> { }, }; - let imp = match import.source { - ImportSource::Use { kind, id, use_tree, .. } => { - if kind == ImportKind::TypeOnly { - def.values = None; - def.macros = None; - } - ImportType::Import(ImportId { import: id, idx: use_tree }) - } - }; + if kind == ImportKind::TypeOnly { + def.values = None; + def.macros = None; + } + let imp = ImportType::Import(ImportId { import: id, idx: use_tree }); tracing::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); self.update(module_id, &[(name.cloned(), def)], vis, Some(imp)); } - ImportSource::Use { kind: ImportKind::Glob, id, .. } => { + ImportSource { kind: ImportKind::Glob, id, is_prelude, .. } => { tracing::debug!("glob import: {:?}", import); match def.take_types() { Some(ModuleDefId::ModuleId(m)) => { - if let ImportSource::Use { id, is_prelude: true, .. } = import.source { + if is_prelude { // Note: This dodgily overrides the injected prelude. The rustc // implementation seems to work the same though. cov_mark::hit!(std_prelude); @@ -1509,18 +1509,26 @@ impl DefCollector<'_> { } // Emit diagnostics for all remaining unresolved imports. - for directive in &self.unresolved_imports { - let ImportSource::Use { use_tree, id, is_prelude: _, kind: _ } = - directive.import.source; + for import in &self.unresolved_imports { + let &ImportDirective { + module_id, + import: + Import { + ref path, + source: ImportSource { use_tree, id, is_prelude: _, kind: _ }, + .. + }, + .. + } = import; if matches!( - (directive.import.path.segments().first(), &directive.import.path.kind), + (path.segments().first(), &path.kind), (Some(krate), PathKind::Plain | PathKind::Abs) if self.unresolved_extern_crates.contains(krate) ) { continue; } let item_tree_id = id.lookup(self.db).id; self.def_map.diagnostics.push(DefDiagnostic::unresolved_import( - directive.module_id, + module_id, item_tree_id, use_tree, )); -- cgit 1.4.1-3-g733a5 From a219568f9aadfd97124e2ec5c1c2bd46f49e9b13 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 5 Oct 2024 15:17:34 +0200 Subject: Add excluded extern-prelude IDE resolution test --- .../src/syntax_highlighting/test_data/highlight_extern_crate.html | 4 +++- .../rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html index 129b287e52f..0c08a7d5ecd 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html @@ -45,7 +45,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; } .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } -
extern crate std;
+
extern crate self as this;
+extern crate std;
 extern crate alloc as abc;
 extern crate unresolved as definitely_unresolved;
+extern crate test as opt_in_crate;
 
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs index 94cee4ef43b..3b2421a095f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs @@ -874,14 +874,18 @@ pub fn block_comments2() {} fn test_extern_crate() { check_highlighting( r#" -//- /main.rs crate:main deps:std,alloc +//- /main.rs crate:main deps:std,alloc,test extern-prelude:std,alloc +extern crate self as this; extern crate std; extern crate alloc as abc; extern crate unresolved as definitely_unresolved; +extern crate test as opt_in_crate; //- /std/lib.rs crate:std pub struct S; //- /alloc/lib.rs crate:alloc -pub struct A +pub struct A; +//- /test/lib.rs crate:test +pub struct T; "#, expect_file!["./test_data/highlight_extern_crate.html"], false, -- cgit 1.4.1-3-g733a5 From 96b3402e1bc59351da5117fecc95188009d90600 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 5 Oct 2024 15:34:05 +0200 Subject: Fix IDE layer not correctly resolving opt-in extern crates --- src/tools/rust-analyzer/crates/hir-def/src/data.rs | 13 ++++++++----- .../test_data/highlight_extern_crate.html | 5 ++++- .../crates/ide/src/syntax_highlighting/tests.rs | 7 ++++++- 3 files changed, 18 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs index d17ebd7ff92..3ecb57c7567 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/data.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs @@ -506,14 +506,17 @@ impl ExternCrateDeclData { let crate_id = if name == sym::self_.clone() { Some(krate) } else { - db.crate_def_map(krate) - .extern_prelude() - .find(|&(prelude_name, ..)| *prelude_name == name) - .map(|(_, (root, _))| root.krate()) + db.crate_graph()[krate].dependencies.iter().find_map(|dep| { + if dep.name.symbol() == name.symbol() { + Some(dep.crate_id) + } else { + None + } + }) }; Arc::new(Self { - name: extern_crate.name.clone(), + name, visibility: item_tree[extern_crate.visibility].clone(), alias: extern_crate.alias.clone(), crate_id, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html index 0c08a7d5ecd..7820e4e5a5f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html @@ -49,5 +49,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd extern crate std; extern crate alloc as abc; extern crate unresolved as definitely_unresolved; -extern crate test as opt_in_crate; +extern crate unresolved as _; +extern crate test as opt_in_crate; +extern crate test as _; +extern crate proc_macro;
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs index 3b2421a095f..a20147add36 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs @@ -874,18 +874,23 @@ pub fn block_comments2() {} fn test_extern_crate() { check_highlighting( r#" -//- /main.rs crate:main deps:std,alloc,test extern-prelude:std,alloc +//- /main.rs crate:main deps:std,alloc,test,proc_macro extern-prelude:std,alloc extern crate self as this; extern crate std; extern crate alloc as abc; extern crate unresolved as definitely_unresolved; +extern crate unresolved as _; extern crate test as opt_in_crate; +extern crate test as _; +extern crate proc_macro; //- /std/lib.rs crate:std pub struct S; //- /alloc/lib.rs crate:alloc pub struct A; //- /test/lib.rs crate:test pub struct T; +//- /proc_macro/lib.rs crate:proc_macro +pub struct ProcMacro; "#, expect_file!["./test_data/highlight_extern_crate.html"], false, -- cgit 1.4.1-3-g733a5 From 429d46dbb153ff2a3c4608777bf21ea66148aaea Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 5 Oct 2024 13:27:03 -0400 Subject: Include description in label details when detail field is marked for resolution --- src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 375b7428c2d..1fb6535258e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -339,7 +339,7 @@ fn completion_item( something_to_resolve = item.detail.is_some(); None } else { - item.detail + item.detail.clone() }; let documentation = if fields_to_resolve.resolve_documentation { @@ -370,7 +370,7 @@ fn completion_item( } else { lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails { detail: item.label_detail.as_ref().map(ToString::to_string), - description: lsp_item.detail.clone(), + description: item.detail, }); } } else if let Some(label_detail) = item.label_detail { -- cgit 1.4.1-3-g733a5 From 29821411bd74dad44f94dd621fc6f54a6dc16418 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 5 Oct 2024 22:39:33 +0300 Subject: Fix `prettify_macro_expansion()` when the node isn't the whole file --- .../hir-expand/src/prettify_macro_expansion_.rs | 5 +++- .../rust-analyzer/crates/ide/src/hover/tests.rs | 30 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs b/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs index d928cafdefc..6ff7831fd81 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs @@ -15,11 +15,14 @@ pub fn prettify_macro_expansion( span_map: &ExpansionSpanMap, target_crate_id: CrateId, ) -> SyntaxNode { + // Because `syntax_bridge::prettify_macro_expansion::prettify_macro_expansion()` clones subtree for `syn`, + // that means it will be offsetted to the beginning. + let span_offset = syn.text_range().start(); let crate_graph = db.crate_graph(); let target_crate = &crate_graph[target_crate_id]; let mut syntax_ctx_id_to_dollar_crate_replacement = FxHashMap::default(); syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(syn, &mut |dollar_crate| { - let ctx = span_map.span_at(dollar_crate.text_range().start()).ctx; + let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx; let replacement = syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| { let ctx_data = db.lookup_intern_syntax_context(ctx); diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index e60be577f79..81397b07855 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -8988,3 +8988,33 @@ mod m { "#]], ); } + +#[test] +fn regression_18238() { + check( + r#" +macro_rules! foo { + ($name:ident) => { + pub static $name = Foo::new(|| { + $crate; + }); + }; +} + +foo!(BAR_$0); +"#, + expect![[r#" + *BAR_* + + ```rust + test + ``` + + ```rust + pub static BAR_: {error} = Foo::new(||{ + crate; + }) + ``` + "#]], + ); +} -- cgit 1.4.1-3-g733a5 From 5544bf54e35e840b4fbfdebef4367978f76cc037 Mon Sep 17 00:00:00 2001 From: Jake Date: Sat, 5 Oct 2024 15:51:23 -0700 Subject: lsp: fix completion_item something_to_resolve not being a latch to true --- .../rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 375b7428c2d..c3d13d40a55 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -280,14 +280,14 @@ fn completion_item( let mut something_to_resolve = false; let filter_text = if fields_to_resolve.resolve_filter_text { - something_to_resolve = !item.lookup().is_empty(); + something_to_resolve |= !item.lookup().is_empty(); None } else { Some(item.lookup().to_owned()) }; let text_edit = if fields_to_resolve.resolve_text_edit { - something_to_resolve = true; + something_to_resolve |= true; None } else { // LSP does not allow arbitrary edits in completion, so we have to do a @@ -319,14 +319,14 @@ fn completion_item( let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET); let tags = if fields_to_resolve.resolve_tags { - something_to_resolve = item.deprecated; + something_to_resolve |= item.deprecated; None } else { item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]) }; let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints { if fields_to_resolve.resolve_command { - something_to_resolve = true; + something_to_resolve |= true; None } else { Some(command::trigger_parameter_hints()) @@ -336,14 +336,14 @@ fn completion_item( }; let detail = if fields_to_resolve.resolve_detail { - something_to_resolve = item.detail.is_some(); + something_to_resolve |= item.detail.is_some(); None } else { item.detail }; let documentation = if fields_to_resolve.resolve_documentation { - something_to_resolve = item.documentation.is_some(); + something_to_resolve |= item.documentation.is_some(); None } else { item.documentation.map(documentation) @@ -366,7 +366,7 @@ fn completion_item( if config.completion_label_details_support() { if fields_to_resolve.resolve_label_details { - something_to_resolve = true; + something_to_resolve |= true; } else { lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails { detail: item.label_detail.as_ref().map(ToString::to_string), -- cgit 1.4.1-3-g733a5 From 3eea7afd98835b7325439a16d564d8a1d0d5005c Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Mon, 7 Oct 2024 01:49:14 +0900 Subject: fix: Do not consider mutable usage of deref to `*mut T` as deref_mut --- .../crates/hir-ty/src/infer/mutability.rs | 13 ++++++++++- .../src/handlers/mutability_errors.rs | 25 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs index 8e52725e536..8dcaa9c581e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs @@ -180,6 +180,7 @@ impl InferenceContext<'_> { self.infer_mut_expr(index, Mutability::Not); } Expr::UnaryOp { expr, op: UnaryOp::Deref } => { + let mut mutability = mutability; if let Some((f, _)) = self.result.method_resolutions.get_mut(&tgt_expr) { if mutability == Mutability::Mut { if let Some(deref_trait) = self @@ -187,7 +188,17 @@ impl InferenceContext<'_> { .lang_item(self.table.trait_env.krate, LangItem::DerefMut) .and_then(|l| l.as_trait()) { - if let Some(deref_fn) = self + let ty = self.result.type_of_expr.get(*expr); + let is_mut_ptr = ty.is_some_and(|ty| { + let ty = self.table.resolve_ty_shallow(ty); + matches!( + ty.kind(Interner), + chalk_ir::TyKind::Raw(Mutability::Mut, _) + ) + }); + if is_mut_ptr { + mutability = Mutability::Not; + } else if let Some(deref_fn) = self .db .trait_data(deref_trait) .method_by_name(&Name::new_symbol_root(sym::deref_mut.clone())) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs index 95542793915..6fa0e7a5a89 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs @@ -1255,6 +1255,31 @@ pub unsafe fn foo(a: *mut A) { //^^^^^ 💡 warn: variable does not need to be mutable let _ = b(); } +"#, + ); + } + + #[test] + fn regression_15799() { + check_diagnostics( + r#" +//- minicore: deref_mut +struct WrapPtr(*mut u32); + +impl core::ops::Deref for WrapPtr { + type Target = *mut u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn main() { + let mut x = 0u32; + let wrap = WrapPtr(&mut x); + unsafe { + **wrap = 6; + } +} "#, ); } -- cgit 1.4.1-3-g733a5 From 87baa0e380966c02afee8ecf319265d3d1ba1041 Mon Sep 17 00:00:00 2001 From: kouhe3 <25522053+kouhe3@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:22:58 +0800 Subject: add knownEngines lldb-dap --- src/tools/rust-analyzer/editors/code/src/debug.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src') diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index fb7e340e517..8ccc80b2be1 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -105,9 +105,11 @@ async function getDebugConfiguration( const commandCCpp: string = createCommandLink("ms-vscode.cpptools"); const commandCodeLLDB: string = createCommandLink("vadimcn.vscode-lldb"); const commandNativeDebug: string = createCommandLink("webfreak.debug"); + const commandLLDBDap: string = createCommandLink("llvm-vs-code-extensions.lldb-dap"); await vscode.window.showErrorMessage( `Install [CodeLLDB](command:${commandCodeLLDB} "Open CodeLLDB")` + + `, [lldb-dap](command:${commandLLDBDap} "Open lldb-dap")` + `, [C/C++](command:${commandCCpp} "Open C/C++") ` + `or [Native Debug](command:${commandNativeDebug} "Open Native Debug") for debugging.`, ); @@ -220,10 +222,21 @@ type DebugConfigProvider; "vadimcn.vscode-lldb": DebugConfigProvider<"lldb", CodeLldbDebugConfig>; "ms-vscode.cpptools": DebugConfigProvider<"cppvsdbg" | "cppdbg", CCppDebugConfig>; "webfreak.debug": DebugConfigProvider<"gdb", NativeDebugConfig>; } = { + "llvm-vs-code-extensions.lldb-dap":{ + type: "lldb-dap", + executableProperty: "program", + environmentProperty: (env) => ["env", env], + runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [ + "args", + runnableArgs.executableArgs, + ], + +}, "vadimcn.vscode-lldb": { type: "lldb", executableProperty: "program", @@ -336,6 +349,13 @@ type CCppDebugConfig = { }; } & BaseDebugConfig<"cppvsdbg" | "cppdbg">; +type LldbDapDebugConfig = { + program: string; + args: string[]; + env: Record; +} & BaseDebugConfig<"lldb-dap">; + + type CodeLldbDebugConfig = { program: string; args: string[]; -- cgit 1.4.1-3-g733a5 From 90e3c8885cc53cc0c5be5b27612d82ac5c7c5b38 Mon Sep 17 00:00:00 2001 From: kouhe3 <25522053+kouhe3@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:11:31 +0800 Subject: lldbdap env dict to string --- src/tools/rust-analyzer/editors/code/src/debug.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 8ccc80b2be1..933a8c56b21 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -230,7 +230,7 @@ const knownEngines: { "llvm-vs-code-extensions.lldb-dap":{ type: "lldb-dap", executableProperty: "program", - environmentProperty: (env) => ["env", env], + environmentProperty: (env) => ["env", Object.entries(env).map(([k,v])=>`${k}=${v}`)], runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [ "args", runnableArgs.executableArgs, @@ -352,7 +352,8 @@ type CCppDebugConfig = { type LldbDapDebugConfig = { program: string; args: string[]; - env: Record; + env: string[]; + sourceMap: [string,string][]; } & BaseDebugConfig<"lldb-dap">; -- cgit 1.4.1-3-g733a5 From 63b0e270fbeedd34b073cc668977f8fea4d3378b Mon Sep 17 00:00:00 2001 From: kouhe3 <25522053+kouhe3@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:41:20 +0800 Subject: fix array sourceMap --- src/tools/rust-analyzer/editors/code/src/debug.ts | 28 +++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 933a8c56b21..8966bdf36db 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -6,6 +6,7 @@ import type * as ra from "./lsp_ext"; import { Cargo } from "./toolchain"; import type { Ctx } from "./ctx"; import { createTaskFromRunnable, prepareEnv } from "./run"; +import { execSync } from 'node:child_process' import { execute, isCargoRunnableArgs, unwrapUndefinable } from "./util"; import type { Config } from "./config"; @@ -109,9 +110,9 @@ async function getDebugConfiguration( await vscode.window.showErrorMessage( `Install [CodeLLDB](command:${commandCodeLLDB} "Open CodeLLDB")` + - `, [lldb-dap](command:${commandLLDBDap} "Open lldb-dap")` + - `, [C/C++](command:${commandCCpp} "Open C/C++") ` + - `or [Native Debug](command:${commandNativeDebug} "Open Native Debug") for debugging.`, + `, [lldb-dap](command:${commandLLDBDap} "Open lldb-dap")` + + `, [C/C++](command:${commandCCpp} "Open C/C++") ` + + `or [Native Debug](command:${commandNativeDebug} "Open Native Debug") for debugging.`, ); return; } @@ -129,7 +130,7 @@ async function getDebugConfiguration( !isMultiFolderWorkspace || !runnableArgs.workspaceRoot ? firstWorkspace : workspaceFolders.find((w) => runnableArgs.workspaceRoot?.includes(w.uri.fsPath)) || - firstWorkspace; + firstWorkspace; const workspace = unwrapUndefinable(maybeWorkspace); let wsFolder = path.normalize(workspace.uri.fsPath); @@ -227,16 +228,25 @@ const knownEngines: { "ms-vscode.cpptools": DebugConfigProvider<"cppvsdbg" | "cppdbg", CCppDebugConfig>; "webfreak.debug": DebugConfigProvider<"gdb", NativeDebugConfig>; } = { - "llvm-vs-code-extensions.lldb-dap":{ + "llvm-vs-code-extensions.lldb-dap": { type: "lldb-dap", executableProperty: "program", - environmentProperty: (env) => ["env", Object.entries(env).map(([k,v])=>`${k}=${v}`)], + environmentProperty: (env) => ["env", Object.entries(env).map(([k, v]) => `${k}=${v}`)], runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [ "args", runnableArgs.executableArgs, ], - -}, + additional: { + "sourceMap": [ + [ + `/rustc/${/commit-hash:\s(.*)$/m.exec(execSync("rustc -V -v", {}).toString())?.[1] + }/library`, + "${config:rust-analyzer.cargo.sysroot}/lib/rustlib/src/rust/library" + + ] + ] + } + }, "vadimcn.vscode-lldb": { type: "lldb", executableProperty: "program", @@ -353,7 +363,7 @@ type LldbDapDebugConfig = { program: string; args: string[]; env: string[]; - sourceMap: [string,string][]; + sourceMap: [string, string][]; } & BaseDebugConfig<"lldb-dap">; -- cgit 1.4.1-3-g733a5 From 3918e29facdff489f0fc259b42bad061aefd22dd Mon Sep 17 00:00:00 2001 From: kouhe3 <25522053+kouhe3@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:47:54 +0800 Subject: semicolon --- src/tools/rust-analyzer/editors/code/src/debug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 8966bdf36db..0114edf6890 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -6,7 +6,7 @@ import type * as ra from "./lsp_ext"; import { Cargo } from "./toolchain"; import type { Ctx } from "./ctx"; import { createTaskFromRunnable, prepareEnv } from "./run"; -import { execSync } from 'node:child_process' +import { execSync } from 'node:child_process'; import { execute, isCargoRunnableArgs, unwrapUndefinable } from "./util"; import type { Config } from "./config"; -- cgit 1.4.1-3-g733a5 From a2304baaf2608c08d473f6bb60b743108157c728 Mon Sep 17 00:00:00 2001 From: kouhe3 <25522053+kouhe3@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:50:29 +0800 Subject: prettier format --- src/tools/rust-analyzer/editors/code/src/debug.ts | 27 +++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 0114edf6890..77ab44f24ce 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -6,7 +6,7 @@ import type * as ra from "./lsp_ext"; import { Cargo } from "./toolchain"; import type { Ctx } from "./ctx"; import { createTaskFromRunnable, prepareEnv } from "./run"; -import { execSync } from 'node:child_process'; +import { execSync } from "node:child_process"; import { execute, isCargoRunnableArgs, unwrapUndefinable } from "./util"; import type { Config } from "./config"; @@ -110,9 +110,9 @@ async function getDebugConfiguration( await vscode.window.showErrorMessage( `Install [CodeLLDB](command:${commandCodeLLDB} "Open CodeLLDB")` + - `, [lldb-dap](command:${commandLLDBDap} "Open lldb-dap")` + - `, [C/C++](command:${commandCCpp} "Open C/C++") ` + - `or [Native Debug](command:${commandNativeDebug} "Open Native Debug") for debugging.`, + `, [lldb-dap](command:${commandLLDBDap} "Open lldb-dap")` + + `, [C/C++](command:${commandCCpp} "Open C/C++") ` + + `or [Native Debug](command:${commandNativeDebug} "Open Native Debug") for debugging.`, ); return; } @@ -130,7 +130,7 @@ async function getDebugConfiguration( !isMultiFolderWorkspace || !runnableArgs.workspaceRoot ? firstWorkspace : workspaceFolders.find((w) => runnableArgs.workspaceRoot?.includes(w.uri.fsPath)) || - firstWorkspace; + firstWorkspace; const workspace = unwrapUndefinable(maybeWorkspace); let wsFolder = path.normalize(workspace.uri.fsPath); @@ -237,15 +237,15 @@ const knownEngines: { runnableArgs.executableArgs, ], additional: { - "sourceMap": [ + sourceMap: [ [ - `/rustc/${/commit-hash:\s(.*)$/m.exec(execSync("rustc -V -v", {}).toString())?.[1] - }/library`, - "${config:rust-analyzer.cargo.sysroot}/lib/rustlib/src/rust/library" - - ] - ] - } + `/rustc/${/commit-hash:\s(.*)$/m.exec( + execSync("rustc -V -v", {}).toString(), + )?.[1]}/library`, + "${config:rust-analyzer.cargo.sysroot}/lib/rustlib/src/rust/library", + ], + ], + }, }, "vadimcn.vscode-lldb": { type: "lldb", @@ -366,7 +366,6 @@ type LldbDapDebugConfig = { sourceMap: [string, string][]; } & BaseDebugConfig<"lldb-dap">; - type CodeLldbDebugConfig = { program: string; args: string[]; -- cgit 1.4.1-3-g733a5 From 4c5b250419822435fb98646f8fd8bdca7ec8abe2 Mon Sep 17 00:00:00 2001 From: Jake Date: Tue, 8 Oct 2024 11:05:03 -0700 Subject: hir-ty: change struct constructor formatting. before, when formatting struct constructor for `struct S(usize, usize)` it would format as: extern "rust-call" S(usize, usize) -> S but after this change, we'll format as: fn S(usize, usize) -> S --- .../rust-analyzer/crates/hir-ty/src/display.rs | 3 +- .../crates/hir-ty/src/tests/macros.rs | 4 +- .../crates/hir-ty/src/tests/patterns.rs | 12 +++--- .../crates/hir-ty/src/tests/regression.rs | 8 ++-- .../crates/hir-ty/src/tests/simple.rs | 50 +++++++++++----------- .../crates/hir-ty/src/tests/traits.rs | 30 ++++++------- 6 files changed, 54 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index 70c03477c4c..af7da4cedf3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -1008,7 +1008,7 @@ impl HirDisplay for Ty { if let Safety::Unsafe = sig.safety { write!(f, "unsafe ")?; } - if !matches!(sig.abi, FnAbi::Rust) { + if !matches!(sig.abi, FnAbi::Rust | FnAbi::RustCall) { f.write_str("extern \"")?; f.write_str(sig.abi.as_str())?; f.write_str("\" ")?; @@ -1025,6 +1025,7 @@ impl HirDisplay for Ty { )? } CallableDefId::StructId(s) => { + write!(f, "fn ")?; f.start_location_link(def.into()); write!(f, "{}", db.struct_data(s).name.display(f.db.upcast(), f.edition()))? } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs index 53b69c12f05..446f0b21a2a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs @@ -64,7 +64,7 @@ fn infer_macros_expanded() { "#, expect![[r#" !0..17 '{Foo(v...,2,])}': Foo - !1..4 'Foo': extern "rust-call" Foo({unknown}) -> Foo + !1..4 'Foo': fn Foo({unknown}) -> Foo !1..16 'Foo(vec![1,2,])': Foo !5..15 'vec![1,2,]': {unknown} 155..181 '{ ...,2); }': () @@ -97,7 +97,7 @@ fn infer_legacy_textual_scoped_macros_expanded() { "#, expect![[r#" !0..17 '{Foo(v...,2,])}': Foo - !1..4 'Foo': extern "rust-call" Foo({unknown}) -> Foo + !1..4 'Foo': fn Foo({unknown}) -> Foo !1..16 'Foo(vec![1,2,])': Foo !5..15 'vec![1,2,]': {unknown} 194..250 '{ ...,2); }': () diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs index 51c27f8714a..4949d4016bf 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs @@ -227,13 +227,13 @@ fn infer_pattern_match_ergonomics() { 37..41 'A(n)': A 39..40 'n': &'? i32 44..49 '&A(1)': &'? A - 45..46 'A': extern "rust-call" A(i32) -> A + 45..46 'A': fn A(i32) -> A 45..49 'A(1)': A 47..48 '1': i32 59..63 'A(n)': A 61..62 'n': &'? mut i32 66..75 '&mut A(1)': &'? mut A - 71..72 'A': extern "rust-call" A(i32) -> A + 71..72 'A': fn A(i32) -> A 71..75 'A(1)': A 73..74 '1': i32 "#]], @@ -548,18 +548,18 @@ impl Foo { 56..64 'Self(s,)': Foo 61..62 's': &'? usize 67..75 '&Foo(0,)': &'? Foo - 68..71 'Foo': extern "rust-call" Foo(usize) -> Foo + 68..71 'Foo': fn Foo(usize) -> Foo 68..75 'Foo(0,)': Foo 72..73 '0': usize 89..97 'Self(s,)': Foo 94..95 's': &'? mut usize 100..112 '&mut Foo(0,)': &'? mut Foo - 105..108 'Foo': extern "rust-call" Foo(usize) -> Foo + 105..108 'Foo': fn Foo(usize) -> Foo 105..112 'Foo(0,)': Foo 109..110 '0': usize 126..134 'Self(s,)': Foo 131..132 's': usize - 137..140 'Foo': extern "rust-call" Foo(usize) -> Foo + 137..140 'Foo': fn Foo(usize) -> Foo 137..144 'Foo(0,)': Foo 141..142 '0': usize "#]], @@ -933,7 +933,7 @@ fn foo(foo: Foo) { 48..51 'foo': Foo 62..84 'const ... 32) }': Foo 68..84 '{ Foo(... 32) }': Foo - 70..73 'Foo': extern "rust-call" Foo(usize) -> Foo + 70..73 'Foo': fn Foo(usize) -> Foo 70..82 'Foo(15 + 32)': Foo 74..76 '15': usize 74..81 '15 + 32': usize diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index a3cf12d8a16..c4822a90f9e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -645,7 +645,7 @@ fn issue_4953() { "#, expect![[r#" 58..72 '{ Self(0i64) }': Foo - 60..64 'Self': extern "rust-call" Foo(i64) -> Foo + 60..64 'Self': fn Foo(i64) -> Foo 60..70 'Self(0i64)': Foo 65..69 '0i64': i64 "#]], @@ -659,7 +659,7 @@ fn issue_4953() { "#, expect![[r#" 64..78 '{ Self(0i64) }': Foo - 66..70 'Self': extern "rust-call" Foo(i64) -> Foo + 66..70 'Self': fn Foo(i64) -> Foo 66..76 'Self(0i64)': Foo 71..75 '0i64': i64 "#]], @@ -859,7 +859,7 @@ fn main() { 94..96 '{}': () 109..160 '{ ...10); }': () 119..120 's': S - 123..124 'S': extern "rust-call" S() -> S + 123..124 'S': fn S() -> S 123..126 'S()': S 132..133 's': S 132..144 's.g(|_x| {})': () @@ -1616,7 +1616,7 @@ fn main() { 37..48 'S(.., a, b)': S 43..44 'a': usize 46..47 'b': {unknown} - 51..52 'S': extern "rust-call" S(usize) -> S + 51..52 'S': fn S(usize) -> S 51..55 'S(1)': S 53..54 '1': usize 65..75 '(.., a, b)': (i32, {unknown}) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 0473ee02fab..71618db2be6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -236,14 +236,14 @@ fn test() { expect![[r#" 71..153 '{ ...a.c; }': () 81..82 'c': C - 85..86 'C': extern "rust-call" C(usize) -> C + 85..86 'C': fn C(usize) -> C 85..89 'C(1)': C 87..88 '1': usize 95..96 'B': B 106..107 'a': A 113..132 'A { b:...C(1) }': A 120..121 'B': B - 126..127 'C': extern "rust-call" C(usize) -> C + 126..127 'C': fn C(usize) -> C 126..130 'C(1)': C 128..129 '1': usize 138..139 'a': A @@ -629,12 +629,12 @@ impl E { 86..107 '{ ... }': () 96..100 'Self': S1 134..158 '{ ... }': () - 144..148 'Self': extern "rust-call" S2(isize) -> S2 + 144..148 'Self': fn S2(isize) -> S2 144..151 'Self(1)': S2 149..150 '1': isize 184..230 '{ ... }': () 194..202 'Self::V1': E - 212..220 'Self::V2': extern "rust-call" V2(u32) -> E + 212..220 'Self::V2': V2(u32) -> E 212..223 'Self::V2(1)': E 221..222 '1': u32 "#]], @@ -860,11 +860,11 @@ fn test() { 256..277 'A::foo...42))))': &'? i32 263..276 '&&B(B(A(42)))': &'? &'? B>> 264..276 '&B(B(A(42)))': &'? B>> - 265..266 'B': extern "rust-call" B>>(B>) -> B>> + 265..266 'B': fn B>>(B>) -> B>> 265..276 'B(B(A(42)))': B>> - 267..268 'B': extern "rust-call" B>(A) -> B> + 267..268 'B': fn B>(A) -> B> 267..275 'B(A(42))': B> - 269..270 'A': extern "rust-call" A(i32) -> A + 269..270 'A': fn A(i32) -> A 269..274 'A(42)': A 271..273 '42': i32 "#]], @@ -914,16 +914,16 @@ fn test(a: A) { 253..254 'a': A 264..310 '{ ...))); }': () 274..275 't': &'? i32 - 278..279 'A': extern "rust-call" A(*mut i32) -> A + 278..279 'A': fn A(*mut i32) -> A 278..292 'A(0 as *mut _)': A 278..307 'A(0 as...B(a)))': &'? i32 280..281 '0': usize 280..291 '0 as *mut _': *mut i32 297..306 '&&B(B(a))': &'? &'? B>> 298..306 '&B(B(a))': &'? B>> - 299..300 'B': extern "rust-call" B>>(B>) -> B>> + 299..300 'B': fn B>>(B>) -> B>> 299..306 'B(B(a))': B>> - 301..302 'B': extern "rust-call" B>(A) -> B> + 301..302 'B': fn B>(A) -> B> 301..305 'B(a)': B> 303..304 'a': A "#]], @@ -1277,16 +1277,16 @@ fn infer_tuple_struct_generics() { "#, expect![[r#" 75..183 '{ ...one; }': () - 81..82 'A': extern "rust-call" A(i32) -> A + 81..82 'A': fn A(i32) -> A 81..86 'A(42)': A 83..85 '42': i32 - 92..93 'A': extern "rust-call" A(u128) -> A + 92..93 'A': fn A(u128) -> A 92..101 'A(42u128)': A 94..100 '42u128': u128 - 107..111 'Some': extern "rust-call" Some<&'static str>(&'static str) -> Option<&'static str> + 107..111 'Some': Some<&'static str>(&'static str) -> Option<&'static str> 107..116 'Some("x")': Option<&'static str> 112..115 '"x"': &'static str - 122..134 'Option::Some': extern "rust-call" Some<&'static str>(&'static str) -> Option<&'static str> + 122..134 'Option::Some': Some<&'static str>(&'static str) -> Option<&'static str> 122..139 'Option...e("x")': Option<&'static str> 135..138 '"x"': &'static str 145..149 'None': Option<{unknown}> @@ -1572,7 +1572,7 @@ fn infer_type_alias() { 204..207 'z.y': i8 298..362 '{ ... &e; }': () 308..309 'e': Enum - 312..325 'm::Alias::Foo': extern "rust-call" Foo(u8) -> Enum + 312..325 'm::Alias::Foo': Foo(u8) -> Enum 312..328 'm::Ali...Foo(0)': Enum 326..327 '0': u8 338..354 'm::Ali...Foo(x)': Enum @@ -2191,10 +2191,10 @@ fn main() { 103..231 '{ ... }); }': () 109..161 'async ... }': impl Future> 125..139 'return Err(())': ! - 132..135 'Err': extern "rust-call" Err<(), ()>(()) -> Result<(), ()> + 132..135 'Err': Err<(), ()>(()) -> Result<(), ()> 132..139 'Err(())': Result<(), ()> 136..138 '()': () - 149..151 'Ok': extern "rust-call" Ok<(), ()>(()) -> Result<(), ()> + 149..151 'Ok': Ok<(), ()>(()) -> Result<(), ()> 149..155 'Ok(())': Result<(), ()> 152..154 '()': () 167..171 'test': fn test<(), (), impl FnMut() -> impl Future>, impl Future>>(impl FnMut() -> impl Future>) @@ -2202,10 +2202,10 @@ fn main() { 172..227 '|| asy... }': impl FnMut() -> impl Future> 175..227 'async ... }': impl Future> 191..205 'return Err(())': ! - 198..201 'Err': extern "rust-call" Err<(), ()>(()) -> Result<(), ()> + 198..201 'Err': Err<(), ()>(()) -> Result<(), ()> 198..205 'Err(())': Result<(), ()> 202..204 '()': () - 215..217 'Ok': extern "rust-call" Ok<(), ()>(()) -> Result<(), ()> + 215..217 'Ok': Ok<(), ()>(()) -> Result<(), ()> 215..221 'Ok(())': Result<(), ()> 218..220 '()': () "#]], @@ -2234,7 +2234,7 @@ fn infer_generic_from_later_assignment() { 94..127 '{ ... }': () 104..107 'end': Option 104..120 'end = ...(true)': () - 110..114 'Some': extern "rust-call" Some(bool) -> Option + 110..114 'Some': Some(bool) -> Option 110..120 'Some(true)': Option 115..119 'true': bool "#]], @@ -2269,7 +2269,7 @@ fn infer_loop_break_with_val() { 111..121 'break None': ! 117..121 'None': Option 142..158 'break ...(true)': ! - 148..152 'Some': extern "rust-call" Some(bool) -> Option + 148..152 'Some': Some(bool) -> Option 148..158 'Some(true)': Option 153..157 'true': bool "#]], @@ -2516,7 +2516,7 @@ fn generic_default_in_struct_literal() { 254..281 'OtherT...1i32 }': OtherThing 275..279 '1i32': i32 291..292 'b': OtherThing - 295..310 'OtherThing::Two': extern "rust-call" Two(i32) -> OtherThing + 295..310 'OtherThing::Two': Two(i32) -> OtherThing 295..316 'OtherT...(1i32)': OtherThing 311..315 '1i32': i32 "#]], @@ -3028,7 +3028,7 @@ fn f() { expect![[r#" 72..166 '{ ... } }': () 78..164 'match ... }': () - 84..92 'Foo::Bar': extern "rust-call" Bar(i32) -> Foo + 84..92 'Foo::Bar': Bar(i32) -> Foo 84..95 'Foo::Bar(3)': Foo 93..94 '3': i32 106..119 'Qux::Bar(bar)': Foo @@ -3087,9 +3087,9 @@ fn main() { 322..324 '{}': Foo 338..559 '{ ...r(); }': () 348..353 'boxed': Box> - 356..359 'Box': extern "rust-call" Box>(Foo) -> Box> + 356..359 'Box': fn Box>(Foo) -> Box> 356..371 'Box(Foo(0_i32))': Box> - 360..363 'Foo': extern "rust-call" Foo(i32) -> Foo + 360..363 'Foo': fn Foo(i32) -> Foo 360..370 'Foo(0_i32)': Foo 364..369 '0_i32': i32 382..386 'bad1': &'? i32 diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index 0b2d6bdd259..55848ef1bde 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -523,7 +523,7 @@ fn test() -> u64 { expect![[r#" 37..86 '{ ... a.1 }': u64 47..48 'a': S - 51..52 'S': extern "rust-call" S(i32, u64) -> S + 51..52 'S': fn S(i32, u64) -> S 51..58 'S(4, 6)': S 53..54 '4': i32 56..57 '6': u64 @@ -549,7 +549,7 @@ fn test() -> u64 { expect![[r#" 43..108 '{ ...0(2) }': u64 53..54 'a': S - 57..58 'S': extern "rust-call" S(fn(u32) -> u64) -> S + 57..58 'S': fn S(fn(u32) -> u64) -> S 57..74 'S(|i| ...s u64)': S 59..73 '|i| 2*i as u64': impl Fn(u32) -> u64 60..61 'i': u32 @@ -1027,7 +1027,7 @@ fn test(x: impl Trait, y: &impl Trait) { 201..202 'x': impl Trait 208..209 'y': &'? impl Trait 219..220 'z': S - 223..224 'S': extern "rust-call" S(u16) -> S + 223..224 'S': fn S(u16) -> S 223..227 'S(1)': S 225..226 '1': u16 233..236 'bar': fn bar(S) @@ -1269,10 +1269,10 @@ fn bar() { 226..229 'foo': fn foo([R<(), i32>; 2]) -> i32 226..250 'foo([R...B(7)])': i32 230..249 '[R::A(...:B(7)]': [R<(), i32>; 2] - 231..235 'R::A': extern "rust-call" A<(), i32>(()) -> R<(), i32> + 231..235 'R::A': A<(), i32>(()) -> R<(), i32> 231..239 'R::A(())': R<(), i32> 236..238 '()': () - 241..245 'R::B': extern "rust-call" B<(), i32>(i32) -> R<(), i32> + 241..245 'R::B': B<(), i32>(i32) -> R<(), i32> 241..248 'R::B(7)': R<(), i32> 246..247 '7': i32 "#]], @@ -1421,7 +1421,7 @@ fn foo() -> (impl FnOnce(&str, T), impl Trait) { 142..147 'input': &'? str 149..150 't': T 152..154 '{}': () - 156..159 'Bar': extern "rust-call" Bar(u8) -> Bar + 156..159 'Bar': fn Bar(u8) -> Bar 156..162 'Bar(C)': Bar 160..161 'C': u8 "#]], @@ -2046,7 +2046,7 @@ fn test() { 118..120 '{}': () 136..255 '{ ... 1); }': () 146..147 'x': Option - 150..162 'Option::Some': extern "rust-call" Some(u32) -> Option + 150..162 'Option::Some': Some(u32) -> Option 150..168 'Option...(1u32)': Option 163..167 '1u32': u32 174..175 'x': Option @@ -2602,7 +2602,7 @@ fn test() -> impl Trait { 178..180 '{}': () 213..309 '{ ...t()) }': S 223..225 's1': S - 228..229 'S': extern "rust-call" S(u32) -> S + 228..229 'S': fn S(u32) -> S 228..240 'S(default())': S 230..237 'default': fn default() -> u32 230..239 'default()': u32 @@ -2612,11 +2612,11 @@ fn test() -> impl Trait { 263..264 'x': i32 272..275 'bar': fn bar(S) -> i32 272..289 'bar(S(...lt()))': i32 - 276..277 'S': extern "rust-call" S(i32) -> S + 276..277 'S': fn S(i32) -> S 276..288 'S(default())': S 278..285 'default': fn default() -> i32 278..287 'default()': i32 - 295..296 'S': extern "rust-call" S(i32) -> S + 295..296 'S': fn S(i32) -> S 295..307 'S(default())': S 297..304 'default': fn default() -> i32 297..306 'default()': i32 @@ -2846,7 +2846,7 @@ fn main() { 1036..1041 'x > 0': bool 1040..1041 '0': i32 1042..1060 '{ Some...u32) }': Option - 1044..1048 'Some': extern "rust-call" Some(u32) -> Option + 1044..1048 'Some': Some(u32) -> Option 1044..1058 'Some(x as u32)': Option 1049..1050 'x': i32 1049..1057 'x as u32': u32 @@ -2982,9 +2982,9 @@ fn test() { 175..185 'foo.test()': bool 191..194 'bar': fn bar<{unknown}>({unknown}) -> {unknown} 191..201 'bar.test()': bool - 207..213 'Struct': extern "rust-call" Struct(usize) -> Struct + 207..213 'Struct': fn Struct(usize) -> Struct 207..220 'Struct.test()': bool - 226..239 'Enum::Variant': extern "rust-call" Variant(usize) -> Enum + 226..239 'Enum::Variant': Variant(usize) -> Enum 226..246 'Enum::...test()': bool "#]], ); @@ -3563,12 +3563,12 @@ fn main(){ 95..99 'self': Wrapper 101..104 'rhs': u32 122..150 '{ ... }': Wrapper - 132..139 'Wrapper': extern "rust-call" Wrapper(u32) -> Wrapper + 132..139 'Wrapper': fn Wrapper(u32) -> Wrapper 132..144 'Wrapper(rhs)': Wrapper 140..143 'rhs': u32 162..248 '{ ...um; }': () 172..179 'wrapped': Wrapper - 182..189 'Wrapper': extern "rust-call" Wrapper(u32) -> Wrapper + 182..189 'Wrapper': fn Wrapper(u32) -> Wrapper 182..193 'Wrapper(10)': Wrapper 190..192 '10': u32 203..206 'num': u32 -- cgit 1.4.1-3-g733a5 From 35e268d406f96caab9d10428af27645947cf1af7 Mon Sep 17 00:00:00 2001 From: Jake Date: Tue, 8 Oct 2024 11:37:08 -0700 Subject: include fn prefix for all callable defs --- .../rust-analyzer/crates/hir-ty/src/display.rs | 31 +++++++++------------- .../crates/hir-ty/src/tests/simple.rs | 24 ++++++++--------- .../crates/hir-ty/src/tests/traits.rs | 10 +++---- 3 files changed, 29 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index af7da4cedf3..10f5bcdad86 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -1014,29 +1014,22 @@ impl HirDisplay for Ty { f.write_str("\" ")?; } + write!(f, "fn ")?; + f.start_location_link(def.into()); match def { - CallableDefId::FunctionId(ff) => { - write!(f, "fn ")?; - f.start_location_link(def.into()); - write!( - f, - "{}", - db.function_data(ff).name.display(f.db.upcast(), f.edition()) - )? - } + CallableDefId::FunctionId(ff) => write!( + f, + "{}", + db.function_data(ff).name.display(f.db.upcast(), f.edition()) + )?, CallableDefId::StructId(s) => { - write!(f, "fn ")?; - f.start_location_link(def.into()); write!(f, "{}", db.struct_data(s).name.display(f.db.upcast(), f.edition()))? } - CallableDefId::EnumVariantId(e) => { - f.start_location_link(def.into()); - write!( - f, - "{}", - db.enum_variant_data(e).name.display(f.db.upcast(), f.edition()) - )? - } + CallableDefId::EnumVariantId(e) => write!( + f, + "{}", + db.enum_variant_data(e).name.display(f.db.upcast(), f.edition()) + )?, }; f.end_location_link(); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 71618db2be6..a8170b60606 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -634,7 +634,7 @@ impl E { 149..150 '1': isize 184..230 '{ ... }': () 194..202 'Self::V1': E - 212..220 'Self::V2': V2(u32) -> E + 212..220 'Self::V2': fn V2(u32) -> E 212..223 'Self::V2(1)': E 221..222 '1': u32 "#]], @@ -1283,10 +1283,10 @@ fn infer_tuple_struct_generics() { 92..93 'A': fn A(u128) -> A 92..101 'A(42u128)': A 94..100 '42u128': u128 - 107..111 'Some': Some<&'static str>(&'static str) -> Option<&'static str> + 107..111 'Some': fn Some<&'static str>(&'static str) -> Option<&'static str> 107..116 'Some("x")': Option<&'static str> 112..115 '"x"': &'static str - 122..134 'Option::Some': Some<&'static str>(&'static str) -> Option<&'static str> + 122..134 'Option::Some': fn Some<&'static str>(&'static str) -> Option<&'static str> 122..139 'Option...e("x")': Option<&'static str> 135..138 '"x"': &'static str 145..149 'None': Option<{unknown}> @@ -1572,7 +1572,7 @@ fn infer_type_alias() { 204..207 'z.y': i8 298..362 '{ ... &e; }': () 308..309 'e': Enum - 312..325 'm::Alias::Foo': Foo(u8) -> Enum + 312..325 'm::Alias::Foo': fn Foo(u8) -> Enum 312..328 'm::Ali...Foo(0)': Enum 326..327 '0': u8 338..354 'm::Ali...Foo(x)': Enum @@ -2191,10 +2191,10 @@ fn main() { 103..231 '{ ... }); }': () 109..161 'async ... }': impl Future> 125..139 'return Err(())': ! - 132..135 'Err': Err<(), ()>(()) -> Result<(), ()> + 132..135 'Err': fn Err<(), ()>(()) -> Result<(), ()> 132..139 'Err(())': Result<(), ()> 136..138 '()': () - 149..151 'Ok': Ok<(), ()>(()) -> Result<(), ()> + 149..151 'Ok': fn Ok<(), ()>(()) -> Result<(), ()> 149..155 'Ok(())': Result<(), ()> 152..154 '()': () 167..171 'test': fn test<(), (), impl FnMut() -> impl Future>, impl Future>>(impl FnMut() -> impl Future>) @@ -2202,10 +2202,10 @@ fn main() { 172..227 '|| asy... }': impl FnMut() -> impl Future> 175..227 'async ... }': impl Future> 191..205 'return Err(())': ! - 198..201 'Err': Err<(), ()>(()) -> Result<(), ()> + 198..201 'Err': fn Err<(), ()>(()) -> Result<(), ()> 198..205 'Err(())': Result<(), ()> 202..204 '()': () - 215..217 'Ok': Ok<(), ()>(()) -> Result<(), ()> + 215..217 'Ok': fn Ok<(), ()>(()) -> Result<(), ()> 215..221 'Ok(())': Result<(), ()> 218..220 '()': () "#]], @@ -2234,7 +2234,7 @@ fn infer_generic_from_later_assignment() { 94..127 '{ ... }': () 104..107 'end': Option 104..120 'end = ...(true)': () - 110..114 'Some': Some(bool) -> Option + 110..114 'Some': fn Some(bool) -> Option 110..120 'Some(true)': Option 115..119 'true': bool "#]], @@ -2269,7 +2269,7 @@ fn infer_loop_break_with_val() { 111..121 'break None': ! 117..121 'None': Option 142..158 'break ...(true)': ! - 148..152 'Some': Some(bool) -> Option + 148..152 'Some': fn Some(bool) -> Option 148..158 'Some(true)': Option 153..157 'true': bool "#]], @@ -2516,7 +2516,7 @@ fn generic_default_in_struct_literal() { 254..281 'OtherT...1i32 }': OtherThing 275..279 '1i32': i32 291..292 'b': OtherThing - 295..310 'OtherThing::Two': Two(i32) -> OtherThing + 295..310 'OtherThing::Two': fn Two(i32) -> OtherThing 295..316 'OtherT...(1i32)': OtherThing 311..315 '1i32': i32 "#]], @@ -3028,7 +3028,7 @@ fn f() { expect![[r#" 72..166 '{ ... } }': () 78..164 'match ... }': () - 84..92 'Foo::Bar': Bar(i32) -> Foo + 84..92 'Foo::Bar': fn Bar(i32) -> Foo 84..95 'Foo::Bar(3)': Foo 93..94 '3': i32 106..119 'Qux::Bar(bar)': Foo diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index 55848ef1bde..9b982a124e7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -1269,10 +1269,10 @@ fn bar() { 226..229 'foo': fn foo([R<(), i32>; 2]) -> i32 226..250 'foo([R...B(7)])': i32 230..249 '[R::A(...:B(7)]': [R<(), i32>; 2] - 231..235 'R::A': A<(), i32>(()) -> R<(), i32> + 231..235 'R::A': fn A<(), i32>(()) -> R<(), i32> 231..239 'R::A(())': R<(), i32> 236..238 '()': () - 241..245 'R::B': B<(), i32>(i32) -> R<(), i32> + 241..245 'R::B': fn B<(), i32>(i32) -> R<(), i32> 241..248 'R::B(7)': R<(), i32> 246..247 '7': i32 "#]], @@ -2046,7 +2046,7 @@ fn test() { 118..120 '{}': () 136..255 '{ ... 1); }': () 146..147 'x': Option - 150..162 'Option::Some': Some(u32) -> Option + 150..162 'Option::Some': fn Some(u32) -> Option 150..168 'Option...(1u32)': Option 163..167 '1u32': u32 174..175 'x': Option @@ -2846,7 +2846,7 @@ fn main() { 1036..1041 'x > 0': bool 1040..1041 '0': i32 1042..1060 '{ Some...u32) }': Option - 1044..1048 'Some': Some(u32) -> Option + 1044..1048 'Some': fn Some(u32) -> Option 1044..1058 'Some(x as u32)': Option 1049..1050 'x': i32 1049..1057 'x as u32': u32 @@ -2984,7 +2984,7 @@ fn test() { 191..201 'bar.test()': bool 207..213 'Struct': fn Struct(usize) -> Struct 207..220 'Struct.test()': bool - 226..239 'Enum::Variant': Variant(usize) -> Enum + 226..239 'Enum::Variant': fn Variant(usize) -> Enum 226..246 'Enum::...test()': bool "#]], ); -- cgit 1.4.1-3-g733a5 From 4b30d25d21d372773cb3e13d7c768006b45d563b Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Tue, 8 Oct 2024 23:30:05 +0200 Subject: Only Highlight Exit Points on `async` Token This ensures that when being on an `await` token, it still only highlights the yield points and not the exit points. --- .../crates/ide/src/highlight_related.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs index 97b7ba78129..fc29ba06dad 100644 --- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs +++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs @@ -513,16 +513,23 @@ pub(crate) fn highlight_yield_points( match anc { ast::Fn(fn_) => hl(sema, fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)), ast::BlockExpr(block_expr) => { - if block_expr.async_token().is_none() { + let Some(async_token) = block_expr.async_token() else { continue; - } + }; // Async blocks act similar to closures. So we want to - // highlight their exit points too. - let exit_points = hl_exit_points(sema, block_expr.async_token(), block_expr.clone().into()); - merge_map(&mut res, exit_points); + // highlight their exit points too, but only if we are on + // the async token. + if async_token == token { + let exit_points = hl_exit_points( + sema, + Some(async_token.clone()), + block_expr.clone().into(), + ); + merge_map(&mut res, exit_points); + } - hl(sema, block_expr.async_token(), Some(block_expr.into())) + hl(sema, Some(async_token), Some(block_expr.into())) }, ast::ClosureExpr(closure) => hl(sema, closure.async_token(), closure.body()), _ => continue, @@ -949,7 +956,6 @@ async fn foo() { (async { // ^^^^^ (async { 0.await }).await$0 - // ^^^^^^^^^^^^^^^^^^^^^^^^^ // ^^^^^ }).await; } -- cgit 1.4.1-3-g733a5 From ee684c176acdf22b6ee8740b20c7c03e3351244c Mon Sep 17 00:00:00 2001 From: David Richey Date: Wed, 9 Oct 2024 19:34:08 -0500 Subject: Skip #[test_case] expansion --- src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs | 4 ++-- src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index 7f1d19719da..769be872947 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -1323,7 +1323,7 @@ impl DefCollector<'_> { _ => return Resolved::No, }; - // Skip #[test]/#[bench] expansion, which would merely result in more memory usage + // Skip #[test]/#[bench]/#[test_case] expansion, which would merely result in more memory usage // due to duplicating functions into macro expansions, but only if `cfg(test)` is active, // otherwise they are expanded to nothing and this can impact e.g. diagnostics (due to things // being cfg'ed out). @@ -1332,7 +1332,7 @@ impl DefCollector<'_> { if matches!( def.kind, MacroDefKind::BuiltInAttr(_, expander) - if expander.is_test() || expander.is_bench() + if expander.is_test() || expander.is_bench() || expander.is_test_case() ) { let test_is_active = self.cfg_options.check_atom(&CfgAtom::Flag(sym::test.clone())); diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs index 2a8691b461c..74effd2fb16 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs @@ -51,6 +51,9 @@ impl BuiltinAttrExpander { pub fn is_bench(self) -> bool { matches!(self, BuiltinAttrExpander::Bench) } + pub fn is_test_case(self) -> bool { + matches!(self, BuiltinAttrExpander::TestCase) + } } register_builtin! { -- cgit 1.4.1-3-g733a5 From 0460ff71b2470f24c77c2f6aafd60a6f88afa800 Mon Sep 17 00:00:00 2001 From: David Richey Date: Thu, 10 Oct 2024 20:00:33 -0500 Subject: Run subprocesses async in vscode extension --- .../rust-analyzer/editors/code/src/bootstrap.ts | 39 ++++++++-------- src/tools/rust-analyzer/editors/code/src/util.ts | 54 +++++++++++++++++++++- .../editors/code/tests/unit/bootstrap.test.ts | 22 ++++----- 3 files changed, 83 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index 35867f710d5..8fc9f09324a 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -1,9 +1,9 @@ import * as vscode from "vscode"; import * as os from "os"; import type { Config } from "./config"; -import { type Env, log } from "./util"; +import { type Env, log, spawnAsync } from "./util"; import type { PersistentState } from "./persistent_state"; -import { exec, spawnSync } from "child_process"; +import { exec } from "child_process"; import { TextDecoder } from "node:util"; export async function bootstrap( @@ -61,13 +61,12 @@ async function getServer( // if so, use the rust-analyzer component const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml"); if (await hasToolchainFileWithRaDeclared(toolchainUri)) { - const res = spawnSync("rustup", ["which", "rust-analyzer"], { - encoding: "utf8", + const res = await spawnAsync("rustup", ["which", "rust-analyzer"], { env: { ...process.env }, cwd: workspaceFolder.uri.fsPath, }); if (!res.error && res.status === 0) { - toolchainServerPath = earliestToolchainPath( + toolchainServerPath = await earliestToolchainPath( toolchainServerPath, res.stdout.trim(), raVersionResolver, @@ -114,10 +113,8 @@ async function getServer( } // Given a path to a rust-analyzer executable, resolve its version and return it. -function raVersionResolver(path: string): string | undefined { - const res = spawnSync(path, ["--version"], { - encoding: "utf8", - }); +async function raVersionResolver(path: string): Promise { + const res = await spawnAsync(path, ["--version"]); if (!res.error && res.status === 0) { return res.stdout; } else { @@ -126,13 +123,16 @@ function raVersionResolver(path: string): string | undefined { } // Given a path to two rust-analyzer executables, return the earliest one by date. -function earliestToolchainPath( +async function earliestToolchainPath( path0: string | undefined, path1: string, - raVersionResolver: (path: string) => string | undefined, -): string { + raVersionResolver: (path: string) => Promise, +): Promise { if (path0) { - if (orderFromPath(path0, raVersionResolver) < orderFromPath(path1, raVersionResolver)) { + if ( + (await orderFromPath(path0, raVersionResolver)) < + (await orderFromPath(path1, raVersionResolver)) + ) { return path0; } else { return path1; @@ -150,11 +150,11 @@ function earliestToolchainPath( // nightly - /Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer // versioned - /Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer // stable - /Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer -function orderFromPath( +async function orderFromPath( path: string, - raVersionResolver: (path: string) => string | undefined, -): string { - const raVersion = raVersionResolver(path); + raVersionResolver: (path: string) => Promise, +): Promise { + const raVersion = await raVersionResolver(path); const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); if (raDate?.length === 2) { const precedence = path.includes("nightly-") ? "0" : "1"; @@ -184,11 +184,10 @@ async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise } } -export function isValidExecutable(path: string, extraEnv: Env): boolean { +export async function isValidExecutable(path: string, extraEnv: Env): Promise { log.debug("Checking availability of a binary at", path); - const res = spawnSync(path, ["--version"], { - encoding: "utf8", + const res = await spawnAsync(path, ["--version"], { env: { ...process.env, ...extraEnv }, }); diff --git a/src/tools/rust-analyzer/editors/code/src/util.ts b/src/tools/rust-analyzer/editors/code/src/util.ts index db64a013fda..d7ca6b3557d 100644 --- a/src/tools/rust-analyzer/editors/code/src/util.ts +++ b/src/tools/rust-analyzer/editors/code/src/util.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; import { strict as nativeAssert } from "assert"; -import { exec, type ExecOptions } from "child_process"; +import { exec, spawn, type SpawnOptionsWithoutStdio, type ExecOptions } from "child_process"; import { inspect } from "util"; import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext"; @@ -233,3 +233,55 @@ export function expectNotUndefined(input: Undefinable, msg: string): NotUn export function unwrapUndefinable(input: Undefinable): NotUndefined { return expectNotUndefined(input, `unwrapping \`undefined\``); } + +interface SpawnAsyncReturns { + stdout: string; + stderr: string; + status: number | null; + error?: Error | undefined; +} + +export async function spawnAsync( + path: string, + args?: ReadonlyArray, + options?: SpawnOptionsWithoutStdio, +): Promise { + const child = spawn(path, args, options); + const stdout: Array = []; + const stderr: Array = []; + try { + const res = await new Promise<{ stdout: string; stderr: string; status: number | null }>( + (resolve, reject) => { + child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk))); + child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk))); + child.on("error", (error) => + reject({ + stdout: Buffer.concat(stdout).toString("utf8"), + stderr: Buffer.concat(stderr).toString("utf8"), + error, + }), + ); + child.on("close", (status) => + resolve({ + stdout: Buffer.concat(stdout).toString("utf8"), + stderr: Buffer.concat(stderr).toString("utf8"), + status, + }), + ); + }, + ); + + return { + stdout: res.stdout, + stderr: res.stderr, + status: res.status, + }; + } catch (e: any) { + return { + stdout: e.stdout, + stderr: e.stderr, + status: e.status, + error: e.error, + }; + } +} diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts index 8aeb72180a0..baabf4f8977 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts @@ -6,9 +6,9 @@ export async function getTests(ctx: Context) { await ctx.suite("Bootstrap/Select toolchain RA", (suite) => { suite.addTest("Order of nightly RA", async () => { assert.deepStrictEqual( - _private.orderFromPath( + await _private.orderFromPath( "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer", - function (path: string) { + async function (path: string) { assert.deepStrictEqual( path, "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer", @@ -22,9 +22,9 @@ export async function getTests(ctx: Context) { suite.addTest("Order of versioned RA", async () => { assert.deepStrictEqual( - _private.orderFromPath( + await _private.orderFromPath( "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", - function (path: string) { + async function (path: string) { assert.deepStrictEqual( path, "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", @@ -38,9 +38,9 @@ export async function getTests(ctx: Context) { suite.addTest("Order of versioned RA when unable to obtain version date", async () => { assert.deepStrictEqual( - _private.orderFromPath( + await _private.orderFromPath( "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", - function () { + async function () { return "rust-analyzer 1.72.1"; }, ), @@ -50,9 +50,9 @@ export async function getTests(ctx: Context) { suite.addTest("Order of stable RA", async () => { assert.deepStrictEqual( - _private.orderFromPath( + await _private.orderFromPath( "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", - function (path: string) { + async function (path: string) { assert.deepStrictEqual( path, "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", @@ -66,7 +66,7 @@ export async function getTests(ctx: Context) { suite.addTest("Order with invalid path to RA", async () => { assert.deepStrictEqual( - _private.orderFromPath("some-weird-path", function () { + await _private.orderFromPath("some-weird-path", async function () { return undefined; }), "2", @@ -75,10 +75,10 @@ export async function getTests(ctx: Context) { suite.addTest("Earliest RA between nightly and stable", async () => { assert.deepStrictEqual( - _private.earliestToolchainPath( + await _private.earliestToolchainPath( "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer", - function (path: string) { + async function (path: string) { if ( path === "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer" -- cgit 1.4.1-3-g733a5 From 0865296a277c2248e90c69f19eb84c1d8442f161 Mon Sep 17 00:00:00 2001 From: David Richey Date: Sat, 12 Oct 2024 01:51:24 -0500 Subject: Fix panic when json project has relative buildfile paths --- src/tools/rust-analyzer/crates/project-model/src/workspace.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index 71b9b61e205..d1ee579c0d8 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -553,7 +553,7 @@ impl ProjectWorkspace { ProjectWorkspaceKind::Json(project) => project .crates() .filter_map(|(_, krate)| krate.build.as_ref().map(|build| build.build_file.clone())) - .map(AbsPathBuf::assert) + .map(|build_file| self.workspace_root().join(build_file)) .collect(), _ => vec![], } -- cgit 1.4.1-3-g733a5 From 7bc615050f3d9bfd241360aabfc74943e0578060 Mon Sep 17 00:00:00 2001 From: roife Date: Sun, 13 Oct 2024 04:43:53 +0800 Subject: feat: respect references.exclude_tests in call-hierarchy --- .../rust-analyzer/crates/ide/src/call_hierarchy.rs | 118 +++++++++++++++++++-- src/tools/rust-analyzer/crates/ide/src/lib.rs | 18 +++- .../crates/rust-analyzer/src/config.rs | 15 ++- .../crates/rust-analyzer/src/handlers/request.rs | 6 +- .../rust-analyzer/docs/user/generated_config.adoc | 2 +- src/tools/rust-analyzer/editors/code/package.json | 2 +- 6 files changed, 141 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs index 155259a1380..1b82c00d1dc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs +++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs @@ -19,6 +19,12 @@ pub struct CallItem { pub ranges: Vec, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CallHierarchyConfig { + /// Whether to exclude tests from the call hierarchy + pub exclude_tests: bool, +} + pub(crate) fn call_hierarchy( db: &RootDatabase, position: FilePosition, @@ -28,6 +34,7 @@ pub(crate) fn call_hierarchy( pub(crate) fn incoming_calls( db: &RootDatabase, + CallHierarchyConfig { exclude_tests }: CallHierarchyConfig, FilePosition { file_id, offset }: FilePosition, ) -> Option> { let sema = &Semantics::new(db); @@ -56,11 +63,18 @@ pub(crate) fn incoming_calls( references.iter().filter_map(|FileReference { name, .. }| name.as_name_ref()); for name in references { // This target is the containing function - let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| { + let def_nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| { let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?; - def.try_to_nav(sema.db) + // We should return def before check if it is a test, so that we + // will not continue to search for outer fn in nested fns + def.try_to_nav(sema.db).map(|nav| (def, nav)) }); - if let Some(nav) = nav { + + if let Some((def, nav)) = def_nav { + if exclude_tests && def.is_test(db) { + continue; + } + let range = sema.original_range(name.syntax()); calls.add(nav.call_site, range.into()); if let Some(other) = nav.def_site { @@ -75,6 +89,7 @@ pub(crate) fn incoming_calls( pub(crate) fn outgoing_calls( db: &RootDatabase, + CallHierarchyConfig { exclude_tests }: CallHierarchyConfig, FilePosition { file_id, offset }: FilePosition, ) -> Option> { let sema = Semantics::new(db); @@ -103,7 +118,12 @@ pub(crate) fn outgoing_calls( let expr = call.expr()?; let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?; match callable.kind() { - hir::CallableKind::Function(it) => it.try_to_nav(db), + hir::CallableKind::Function(it) => { + if exclude_tests && it.is_test(db) { + return None; + } + it.try_to_nav(db) + } hir::CallableKind::TupleEnumVariant(it) => it.try_to_nav(db), hir::CallableKind::TupleStruct(it) => it.try_to_nav(db), _ => None, @@ -112,6 +132,9 @@ pub(crate) fn outgoing_calls( } ast::CallableExpr::MethodCall(expr) => { let function = sema.resolve_method_call(&expr)?; + if exclude_tests && function.is_test(db) { + return None; + } function .try_to_nav(db) .zip(Some(sema.original_range(expr.name_ref()?.syntax()))) @@ -149,6 +172,7 @@ mod tests { use crate::fixture; fn check_hierarchy( + exclude_tests: bool, ra_fixture: &str, expected_nav: Expect, expected_incoming: Expect, @@ -172,18 +196,21 @@ mod tests { let nav = navs.pop().unwrap(); expected_nav.assert_eq(&nav.debug_render()); + let config = crate::CallHierarchyConfig { exclude_tests }; + let item_pos = FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() }; - let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap(); + let incoming_calls = analysis.incoming_calls(config, item_pos).unwrap().unwrap(); expected_incoming.assert_eq(&incoming_calls.into_iter().map(debug_render).join("\n")); - let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap(); + let outgoing_calls = analysis.outgoing_calls(config, item_pos).unwrap().unwrap(); expected_outgoing.assert_eq(&outgoing_calls.into_iter().map(debug_render).join("\n")); } #[test] fn test_call_hierarchy_on_ref() { check_hierarchy( + false, r#" //- /lib.rs fn callee() {} @@ -200,6 +227,7 @@ fn caller() { #[test] fn test_call_hierarchy_on_def() { check_hierarchy( + false, r#" //- /lib.rs fn call$0ee() {} @@ -216,6 +244,7 @@ fn caller() { #[test] fn test_call_hierarchy_in_same_fn() { check_hierarchy( + false, r#" //- /lib.rs fn callee() {} @@ -233,6 +262,7 @@ fn caller() { #[test] fn test_call_hierarchy_in_different_fn() { check_hierarchy( + false, r#" //- /lib.rs fn callee() {} @@ -255,6 +285,7 @@ fn caller2() { #[test] fn test_call_hierarchy_in_tests_mod() { check_hierarchy( + false, r#" //- /lib.rs cfg:test fn callee() {} @@ -283,6 +314,7 @@ mod tests { #[test] fn test_call_hierarchy_in_different_files() { check_hierarchy( + false, r#" //- /lib.rs mod foo; @@ -304,6 +336,7 @@ pub fn callee() {} #[test] fn test_call_hierarchy_outgoing() { check_hierarchy( + false, r#" //- /lib.rs fn callee() {} @@ -321,6 +354,7 @@ fn call$0er() { #[test] fn test_call_hierarchy_outgoing_in_different_files() { check_hierarchy( + false, r#" //- /lib.rs mod foo; @@ -342,6 +376,7 @@ pub fn callee() {} #[test] fn test_call_hierarchy_incoming_outgoing() { check_hierarchy( + false, r#" //- /lib.rs fn caller1() { @@ -365,6 +400,7 @@ fn caller3() { #[test] fn test_call_hierarchy_issue_5103() { check_hierarchy( + false, r#" fn a() { b() @@ -382,6 +418,7 @@ fn main() { ); check_hierarchy( + false, r#" fn a() { b$0() @@ -402,6 +439,7 @@ fn main() { #[test] fn test_call_hierarchy_in_macros_incoming() { check_hierarchy( + false, r#" macro_rules! define { ($ident:ident) => { @@ -423,6 +461,7 @@ fn caller() { expect![[]], ); check_hierarchy( + false, r#" macro_rules! define { ($ident:ident) => { @@ -448,6 +487,7 @@ fn caller() { #[test] fn test_call_hierarchy_in_macros_outgoing() { check_hierarchy( + false, r#" macro_rules! define { ($ident:ident) => { @@ -473,6 +513,7 @@ fn caller$0() { #[test] fn test_call_hierarchy_in_macros_incoming_different_files() { check_hierarchy( + false, r#" //- /lib.rs #[macro_use] @@ -498,6 +539,7 @@ macro_rules! call { expect![[]], ); check_hierarchy( + false, r#" //- /lib.rs #[macro_use] @@ -523,6 +565,7 @@ macro_rules! call { expect![[]], ); check_hierarchy( + false, r#" //- /lib.rs #[macro_use] @@ -558,6 +601,7 @@ macro_rules! call { #[test] fn test_call_hierarchy_in_macros_outgoing_different_files() { check_hierarchy( + false, r#" //- /lib.rs #[macro_use] @@ -585,6 +629,7 @@ macro_rules! call { expect![[]], ); check_hierarchy( + false, r#" //- /lib.rs #[macro_use] @@ -616,6 +661,7 @@ macro_rules! call { #[test] fn test_trait_method_call_hierarchy() { check_hierarchy( + false, r#" trait T1 { fn call$0ee(); @@ -636,4 +682,64 @@ fn caller() { expect![[]], ); } + + #[test] + fn test_call_hierarchy_excluding_tests() { + check_hierarchy( + false, + r#" +fn main() { + f1(); +} + +fn f1$0() { + f2(); f3(); +} + +fn f2() { + f1(); f3(); +} + +#[test] +fn f3() { + f1(); f2(); +} +"#, + expect!["f1 Function FileId(0) 25..52 28..30"], + expect![[r#" + main Function FileId(0) 0..23 3..7 : FileId(0):16..18 + f2 Function FileId(0) 54..81 57..59 : FileId(0):68..70 + f3 Function FileId(0) 83..118 94..96 : FileId(0):105..107"#]], + expect![[r#" + f2 Function FileId(0) 54..81 57..59 : FileId(0):39..41 + f3 Function FileId(0) 83..118 94..96 : FileId(0):45..47"#]], + ); + + check_hierarchy( + true, + r#" +fn main() { + f1(); +} + +fn f1$0() { + f2(); f3(); +} + +fn f2() { + f1(); f3(); +} + +#[test] +fn f3() { + f1(); f2(); +} +"#, + expect!["f1 Function FileId(0) 25..52 28..30"], + expect![[r#" + main Function FileId(0) 0..23 3..7 : FileId(0):16..18 + f2 Function FileId(0) 54..81 57..59 : FileId(0):68..70"#]], + expect!["f2 Function FileId(0) 54..81 57..59 : FileId(0):39..41"], + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index c46c4c8ce94..3a8f7bb7a1a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -79,7 +79,7 @@ use crate::navigation_target::ToNav; pub use crate::{ annotations::{Annotation, AnnotationConfig, AnnotationKind, AnnotationLocation}, - call_hierarchy::CallItem, + call_hierarchy::{CallHierarchyConfig, CallItem}, expand_macro::ExpandedMacro, file_structure::{StructureNode, StructureNodeKind}, folding_ranges::{Fold, FoldKind}, @@ -564,13 +564,21 @@ impl Analysis { } /// Computes incoming calls for the given file position. - pub fn incoming_calls(&self, position: FilePosition) -> Cancellable>> { - self.with_db(|db| call_hierarchy::incoming_calls(db, position)) + pub fn incoming_calls( + &self, + config: CallHierarchyConfig, + position: FilePosition, + ) -> Cancellable>> { + self.with_db(|db| call_hierarchy::incoming_calls(db, config, position)) } /// Computes outgoing calls for the given file position. - pub fn outgoing_calls(&self, position: FilePosition) -> Cancellable>> { - self.with_db(|db| call_hierarchy::outgoing_calls(db, position)) + pub fn outgoing_calls( + &self, + config: CallHierarchyConfig, + position: FilePosition, + ) -> Cancellable>> { + self.with_db(|db| call_hierarchy::outgoing_calls(db, config, position)) } /// Returns a `mod name;` declaration which created the current module. diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index ef2e542cf22..518b588cb7d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -12,10 +12,11 @@ use std::{ use cfg::{CfgAtom, CfgDiff}; use hir::Symbol; use ide::{ - AssistConfig, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig, - ExprFillDefaultMode, GenericParameterHints, HighlightConfig, HighlightRelatedConfig, - HoverConfig, HoverDocFormat, InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, - MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope, SourceRootId, + AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig, + CompletionFieldsToResolve, DiagnosticsConfig, ExprFillDefaultMode, GenericParameterHints, + HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, + InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, + Snippet, SnippetScope, SourceRootId, }; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, @@ -262,7 +263,7 @@ config_data! { /// Exclude imports from find-all-references. references_excludeImports: bool = false, - /// Exclude tests from find-all-references. + /// Exclude tests from find-all-references and call-hierarchy. references_excludeTests: bool = false, /// Inject additional highlighting into doc comments. @@ -1392,6 +1393,10 @@ impl Config { } } + pub fn call_hierarchy(&self) -> CallHierarchyConfig { + CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned() } + } + pub fn completion(&self, source_root: Option) -> CompletionConfig { let client_capability_fields = self.completion_resolve_support_properties(); CompletionConfig { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 9773d8dbce0..5094c1fa5f2 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -1708,7 +1708,8 @@ pub(crate) fn handle_call_hierarchy_incoming( let frange = from_proto::file_range(&snap, &doc, item.selection_range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let call_items = match snap.analysis.incoming_calls(fpos)? { + let config = snap.config.call_hierarchy(); + let call_items = match snap.analysis.incoming_calls(config, fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1745,7 +1746,8 @@ pub(crate) fn handle_call_hierarchy_outgoing( let frange = from_proto::file_range(&snap, &doc, item.selection_range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let call_items = match snap.analysis.outgoing_calls(fpos)? { + let config = snap.config.call_hierarchy(); + let call_items = match snap.analysis.outgoing_calls(config, fpos)? { None => return Ok(None), Some(it) => it, }; diff --git a/src/tools/rust-analyzer/docs/user/generated_config.adoc b/src/tools/rust-analyzer/docs/user/generated_config.adoc index 708fc2b7891..babeb4272be 100644 --- a/src/tools/rust-analyzer/docs/user/generated_config.adoc +++ b/src/tools/rust-analyzer/docs/user/generated_config.adoc @@ -852,7 +852,7 @@ Exclude imports from find-all-references. [[rust-analyzer.references.excludeTests]]rust-analyzer.references.excludeTests (default: `false`):: + -- -Exclude tests from find-all-references. +Exclude tests from find-all-references and call-hierarchy. -- [[rust-analyzer.runnables.command]]rust-analyzer.runnables.command (default: `null`):: + diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index a823e5bb96c..a52b3d1ec5c 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2333,7 +2333,7 @@ "title": "references", "properties": { "rust-analyzer.references.excludeTests": { - "markdownDescription": "Exclude tests from find-all-references.", + "markdownDescription": "Exclude tests from find-all-references and call-hierarchy.", "default": false, "type": "boolean" } -- cgit 1.4.1-3-g733a5 From d21aa86c652c523f6b39e53866690a1d89d97f15 Mon Sep 17 00:00:00 2001 From: "许杰友 Jieyou Xu (Joe)" <39484203+jieyouxu@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:11:58 +0800 Subject: unicode-table-generator: match bin name with tool name Bootstrap assumes that the binary name is the same as tool name, just makes everyone's lives easier. --- Cargo.lock | 14 +++++++------- src/tools/unicode-table-generator/Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/Cargo.lock b/Cargo.lock index c62c379f70d..33e855bccc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5584,13 +5584,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicode-bdd" -version = "0.1.0" -dependencies = [ - "ucd-parse", -] - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -5640,6 +5633,13 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-table-generator" +version = "0.1.0" +dependencies = [ + "ucd-parse", +] + [[package]] name = "unicode-width" version = "0.1.14" diff --git a/src/tools/unicode-table-generator/Cargo.toml b/src/tools/unicode-table-generator/Cargo.toml index ef01877c0b9..f8a500922d0 100644 --- a/src/tools/unicode-table-generator/Cargo.toml +++ b/src/tools/unicode-table-generator/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "unicode-bdd" +name = "unicode-table-generator" version = "0.1.0" edition = "2021" -- cgit 1.4.1-3-g733a5 From 3748c5eecf5e2884f469132e2ba46f782c671a26 Mon Sep 17 00:00:00 2001 From: "许杰友 Jieyou Xu (Joe)" <39484203+jieyouxu@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:13:38 +0800 Subject: bootstrap: register `src/tools/unicode-table-generator` as a runnable tool --- src/bootstrap/src/core/build_steps/run.rs | 22 ++++++++++++++++++++++ src/bootstrap/src/core/build_steps/tool.rs | 1 + src/bootstrap/src/core/builder.rs | 1 + 3 files changed, 24 insertions(+) (limited to 'src') diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index c7bcd76cadd..a6dff7fde80 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -283,3 +283,25 @@ impl Step for GenerateCompletions { run.builder.ensure(GenerateCompletions); } } + +#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +pub struct UnicodeTableGenerator; + +impl Step for UnicodeTableGenerator { + type Output = (); + const ONLY_HOSTS: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path("src/tools/unicode-table-generator") + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(UnicodeTableGenerator); + } + + fn run(self, builder: &Builder<'_>) { + let mut cmd = builder.tool_cmd(Tool::UnicodeTableGenerator); + cmd.arg(builder.src.join("library/core/src/unicode/unicode_data.rs")); + cmd.run(builder); + } +} diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index a01497c2bb9..0d2ffd1c08d 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -360,6 +360,7 @@ bootstrap_tool!( CoverageDump, "src/tools/coverage-dump", "coverage-dump"; RustcPerfWrapper, "src/tools/rustc-perf-wrapper", "rustc-perf-wrapper"; WasmComponentLd, "src/tools/wasm-component-ld", "wasm-component-ld", is_unstable_tool = true, allow_features = "min_specialization"; + UnicodeTableGenerator, "src/tools/unicode-table-generator", "unicode-table-generator"; ); /// These are the submodules that are required for rustbook to work due to diff --git a/src/bootstrap/src/core/builder.rs b/src/bootstrap/src/core/builder.rs index 9ac0b0a01f7..2b82e6c569c 100644 --- a/src/bootstrap/src/core/builder.rs +++ b/src/bootstrap/src/core/builder.rs @@ -1010,6 +1010,7 @@ impl<'a> Builder<'a> { run::GenerateCopyright, run::GenerateWindowsSys, run::GenerateCompletions, + run::UnicodeTableGenerator, ), Kind::Setup => { describe!(setup::Profile, setup::Hook, setup::Link, setup::Editor) -- cgit 1.4.1-3-g733a5 From 75a9c86a7786e72dd432bbef1e3bf99f25654496 Mon Sep 17 00:00:00 2001 From: "许杰友 Jieyou Xu (Joe)" <39484203+jieyouxu@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:33:10 +0800 Subject: unicode-table-generator: sync comments These comments were updated on master but not through this tool, so the comments in the tool became outdated. Sync the comments to stay consistent. --- src/tools/unicode-table-generator/src/range_search.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/tools/unicode-table-generator/src/range_search.rs b/src/tools/unicode-table-generator/src/range_search.rs index 3a5b869f72f..221e5d75ef5 100644 --- a/src/tools/unicode-table-generator/src/range_search.rs +++ b/src/tools/unicode-table-generator/src/range_search.rs @@ -16,16 +16,14 @@ const fn bitset_search< let bucket_idx = (needle / 64) as usize; let chunk_map_idx = bucket_idx / CHUNK_SIZE; let chunk_piece = bucket_idx % CHUNK_SIZE; - // FIXME: const-hack: Revert to `slice::get` after `const_slice_index` - // feature stabilizes. + // FIXME(const-hack): Revert to `slice::get` when slice indexing becomes possible in const. let chunk_idx = if chunk_map_idx < chunk_idx_map.len() { chunk_idx_map[chunk_map_idx] } else { return false; }; let idx = bitset_chunk_idx[chunk_idx as usize][chunk_piece] as usize; - // FIXME: const-hack: Revert to `slice::get` after `const_slice_index` - // feature stabilizes. + // FIXME(const-hack): Revert to `slice::get` when slice indexing becomes possible in const. let word = if idx < bitset_canonical.len() { bitset_canonical[idx] } else { -- cgit 1.4.1-3-g733a5 From b1936c9179d80baab7a158b4659c1b6775e60b73 Mon Sep 17 00:00:00 2001 From: Jakub Beránek Date: Mon, 7 Oct 2024 16:15:58 +0200 Subject: CI: use free runners for 4core Linux jobs --- src/ci/github-actions/jobs.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 8f49f623afa..11238cad3e7 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -5,6 +5,11 @@ runners: env: { } - &job-linux-4c + os: ubuntu-20.04 + <<: *base-job + + # Large runner used mainly for its bigger disk capacity + - &job-linux-4c-largedisk os: ubuntu-20.04-4core-16gb <<: *base-job @@ -127,7 +132,7 @@ auto: - image: dist-aarch64-linux env: CODEGEN_BACKENDS: llvm,cranelift - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - image: dist-android <<: *job-linux-4c @@ -148,28 +153,28 @@ auto: <<: *job-linux-4c - image: dist-loongarch64-linux - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - image: dist-loongarch64-musl - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - image: dist-ohos <<: *job-linux-4c - image: dist-powerpc-linux - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - image: dist-powerpc64-linux - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - image: dist-powerpc64le-linux - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - image: dist-riscv64-linux <<: *job-linux-4c - image: dist-s390x-linux - <<: *job-linux-4c + <<: *job-linux-4c-largedisk - image: dist-various-1 <<: *job-linux-4c -- cgit 1.4.1-3-g733a5 From efd8e15a2e4363e0c8b610b77dc5d6a0c9227b91 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Mon, 30 Sep 2024 11:07:33 -0400 Subject: internal: switch remaining OpQueues to use named structs --- .../crates/rust-analyzer/src/global_state.rs | 8 ++++++-- .../rust-analyzer/crates/rust-analyzer/src/main_loop.rs | 9 ++++++--- .../rust-analyzer/crates/rust-analyzer/src/reload.rs | 16 +++++++++++----- 3 files changed, 23 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs index c3142c9cfca..7fbeaa4e3ea 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs @@ -51,6 +51,11 @@ pub(crate) struct FetchWorkspaceResponse { pub(crate) force_crate_graph_reload: bool, } +pub(crate) struct FetchBuildDataResponse { + pub(crate) workspaces: Arc>, + pub(crate) build_scripts: Vec>, +} + // Enforces drop order pub(crate) struct Handle { pub(crate) handle: H, @@ -152,8 +157,7 @@ pub(crate) struct GlobalState { // op queues pub(crate) fetch_workspaces_queue: OpQueue, - pub(crate) fetch_build_data_queue: - OpQueue<(), (Arc>, Vec>)>, + pub(crate) fetch_build_data_queue: OpQueue<(), FetchBuildDataResponse>, pub(crate) fetch_proc_macros_queue: OpQueue, bool>, pub(crate) prime_caches_queue: OpQueue, pub(crate) discover_workspace_queue: OpQueue, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index ef289720568..20be38a9e4b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -23,7 +23,8 @@ use crate::{ discover::{DiscoverArgument, DiscoverCommand, DiscoverProjectMessage}, flycheck::{self, FlycheckMessage}, global_state::{ - file_id_to_url, url_to_file_id, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState, + file_id_to_url, url_to_file_id, FetchBuildDataResponse, FetchWorkspaceRequest, + FetchWorkspaceResponse, GlobalState, }, hack_recover_crate_name, handlers::dispatch::{NotificationDispatcher, RequestDispatcher}, @@ -738,8 +739,10 @@ impl GlobalState { let (state, msg) = match progress { BuildDataProgress::Begin => (Some(Progress::Begin), None), BuildDataProgress::Report(msg) => (Some(Progress::Report), Some(msg)), - BuildDataProgress::End(build_data_result) => { - self.fetch_build_data_queue.op_completed(build_data_result); + BuildDataProgress::End((workspaces, build_scripts)) => { + let resp = FetchBuildDataResponse { workspaces, build_scripts }; + self.fetch_build_data_queue.op_completed(resp); + if let Err(e) = self.fetch_build_data_error() { error!("FetchBuildDataError: {e}"); } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 60ee0295a3a..2d0ccbf767f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -33,7 +33,9 @@ use vfs::{AbsPath, AbsPathBuf, ChangeKind}; use crate::{ config::{Config, FilesWatcher, LinkedProject}, flycheck::{FlycheckConfig, FlycheckHandle}, - global_state::{FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState}, + global_state::{ + FetchBuildDataResponse, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState, + }, lsp_ext, main_loop::{DiscoverProjectParam, Task}, op_queue::Cause, @@ -475,7 +477,9 @@ impl GlobalState { if same_workspaces { let (workspaces, build_scripts) = match self.fetch_build_data_queue.last_op_result() { - Some((workspaces, build_scripts)) => (workspaces.clone(), build_scripts.as_slice()), + Some(FetchBuildDataResponse { workspaces, build_scripts }) => { + (workspaces.clone(), build_scripts.as_slice()) + } None => (Default::default(), Default::default()), }; @@ -769,12 +773,14 @@ impl GlobalState { pub(super) fn fetch_build_data_error(&self) -> Result<(), String> { let mut buf = String::new(); - let Some((_, ws)) = &self.fetch_build_data_queue.last_op_result() else { + let Some(FetchBuildDataResponse { build_scripts, .. }) = + &self.fetch_build_data_queue.last_op_result() + else { return Ok(()); }; - for ws in ws { - match ws { + for script in build_scripts { + match script { Ok(data) => { if let Some(stderr) = data.error() { stdx::format_to!(buf, "{:#}\n", stderr) -- cgit 1.4.1-3-g733a5 From d28811bccea320694a05533edfb3c5a58af9e593 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 4 Oct 2024 15:48:11 -0400 Subject: chore: rename salsa to ra_salsa --- src/tools/rust-analyzer/Cargo.toml | 4 +- src/tools/rust-analyzer/crates/base-db/Cargo.toml | 2 +- .../rust-analyzer/crates/base-db/src/change.rs | 2 +- src/tools/rust-analyzer/crates/base-db/src/lib.rs | 30 +- src/tools/rust-analyzer/crates/hir-def/src/db.rs | 146 ++-- src/tools/rust-analyzer/crates/hir-def/src/lib.rs | 42 +- .../rust-analyzer/crates/hir-def/src/test_db.rs | 16 +- .../rust-analyzer/crates/hir-expand/src/change.rs | 2 +- .../rust-analyzer/crates/hir-expand/src/db.rs | 40 +- .../rust-analyzer/crates/hir-expand/src/hygiene.rs | 8 +- .../rust-analyzer/crates/hir-expand/src/lib.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/consteval.rs | 2 +- src/tools/rust-analyzer/crates/hir-ty/src/db.rs | 150 ++-- .../rust-analyzer/crates/hir-ty/src/interner.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/layout.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/layout/adt.rs | 2 +- src/tools/rust-analyzer/crates/hir-ty/src/lib.rs | 2 +- src/tools/rust-analyzer/crates/hir-ty/src/lower.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/mapping.rs | 22 +- .../rust-analyzer/crates/hir-ty/src/mir/lower.rs | 2 +- .../crates/hir-ty/src/mir/monomorphization.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/test_db.rs | 22 +- .../crates/hir-ty/src/tests/closure_captures.rs | 2 +- .../crates/ide-db/src/apply_change.rs | 2 +- src/tools/rust-analyzer/crates/ide-db/src/lib.rs | 20 +- .../crates/ide-db/src/prime_caches.rs | 2 +- .../rust-analyzer/crates/ide-db/src/search.rs | 2 +- .../crates/ide-db/src/symbol_index.rs | 16 +- .../rust-analyzer/crates/ide-ssr/src/tests.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/lib.rs | 4 +- src/tools/rust-analyzer/crates/ide/src/ssr.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/status.rs | 2 +- src/tools/rust-analyzer/crates/ra-salsa/Cargo.toml | 35 + src/tools/rust-analyzer/crates/ra-salsa/FAQ.md | 34 + .../rust-analyzer/crates/ra-salsa/LICENSE-APACHE | 176 +++++ .../rust-analyzer/crates/ra-salsa/LICENSE-MIT | 23 + src/tools/rust-analyzer/crates/ra-salsa/README.md | 42 + .../crates/ra-salsa/ra-salsa-macros/Cargo.toml | 23 + .../crates/ra-salsa/ra-salsa-macros/LICENSE-APACHE | 1 + .../crates/ra-salsa/ra-salsa-macros/LICENSE-MIT | 1 + .../crates/ra-salsa/ra-salsa-macros/README.md | 1 + .../ra-salsa-macros/src/database_storage.rs | 243 ++++++ .../crates/ra-salsa/ra-salsa-macros/src/lib.rs | 125 +++ .../ra-salsa/ra-salsa-macros/src/parenthesized.rs | 13 + .../ra-salsa/ra-salsa-macros/src/query_group.rs | 753 ++++++++++++++++++ .../rust-analyzer/crates/ra-salsa/src/debug.rs | 65 ++ .../rust-analyzer/crates/ra-salsa/src/derived.rs | 163 ++++ .../crates/ra-salsa/src/derived/slot.rs | 775 +++++++++++++++++++ .../crates/ra-salsa/src/derived_lru.rs | 233 ++++++ .../crates/ra-salsa/src/derived_lru/slot.rs | 845 +++++++++++++++++++++ .../crates/ra-salsa/src/durability.rs | 49 ++ .../rust-analyzer/crates/ra-salsa/src/hash.rs | 3 + .../rust-analyzer/crates/ra-salsa/src/input.rs | 371 +++++++++ .../rust-analyzer/crates/ra-salsa/src/intern_id.rs | 131 ++++ .../rust-analyzer/crates/ra-salsa/src/interned.rs | 510 +++++++++++++ src/tools/rust-analyzer/crates/ra-salsa/src/lib.rs | 750 ++++++++++++++++++ src/tools/rust-analyzer/crates/ra-salsa/src/lru.rs | 325 ++++++++ .../rust-analyzer/crates/ra-salsa/src/plumbing.rs | 245 ++++++ .../rust-analyzer/crates/ra-salsa/src/revision.rs | 66 ++ .../rust-analyzer/crates/ra-salsa/src/runtime.rs | 668 ++++++++++++++++ .../ra-salsa/src/runtime/dependency_graph.rs | 250 ++++++ .../crates/ra-salsa/src/runtime/local_state.rs | 204 +++++ .../rust-analyzer/crates/ra-salsa/src/storage.rs | 53 ++ .../rust-analyzer/crates/ra-salsa/tests/cycles.rs | 492 ++++++++++++ .../crates/ra-salsa/tests/dyn_trait.rs | 28 + .../crates/ra-salsa/tests/incremental/constants.rs | 145 ++++ .../crates/ra-salsa/tests/incremental/counter.rs | 14 + .../ra-salsa/tests/incremental/implementation.rs | 59 ++ .../crates/ra-salsa/tests/incremental/log.rs | 16 + .../crates/ra-salsa/tests/incremental/main.rs | 9 + .../tests/incremental/memoized_dep_inputs.rs | 60 ++ .../ra-salsa/tests/incremental/memoized_inputs.rs | 76 ++ .../tests/incremental/memoized_volatile.rs | 77 ++ .../crates/ra-salsa/tests/interned.rs | 90 +++ .../rust-analyzer/crates/ra-salsa/tests/lru.rs | 104 +++ .../rust-analyzer/crates/ra-salsa/tests/macros.rs | 12 + .../crates/ra-salsa/tests/no_send_sync.rs | 31 + .../crates/ra-salsa/tests/on_demand_inputs.rs | 147 ++++ .../crates/ra-salsa/tests/panic_safely.rs | 93 +++ .../crates/ra-salsa/tests/parallel/cancellation.rs | 132 ++++ .../crates/ra-salsa/tests/parallel/frozen.rs | 57 ++ .../crates/ra-salsa/tests/parallel/independent.rs | 29 + .../crates/ra-salsa/tests/parallel/main.rs | 13 + .../tests/parallel/parallel_cycle_all_recover.rs | 109 +++ .../tests/parallel/parallel_cycle_mid_recover.rs | 109 +++ .../tests/parallel/parallel_cycle_none_recover.rs | 68 ++ .../tests/parallel/parallel_cycle_one_recovers.rs | 94 +++ .../crates/ra-salsa/tests/parallel/race.rs | 37 + .../crates/ra-salsa/tests/parallel/setup.rs | 197 +++++ .../crates/ra-salsa/tests/parallel/signal.rs | 40 + .../crates/ra-salsa/tests/parallel/stress.rs | 168 ++++ .../ra-salsa/tests/parallel/true_parallel.rs | 125 +++ .../tests/storage_varieties/implementation.rs | 19 + .../ra-salsa/tests/storage_varieties/main.rs | 5 + .../ra-salsa/tests/storage_varieties/queries.rs | 22 + .../ra-salsa/tests/storage_varieties/tests.rs | 49 ++ .../crates/ra-salsa/tests/transparent.rs | 39 + .../crates/ra-salsa/tests/variadic.rs | 51 ++ .../crates/rust-analyzer/src/cli/analysis_stats.rs | 6 +- .../crates/rust-analyzer/src/reload.rs | 2 +- src/tools/rust-analyzer/crates/salsa/Cargo.toml | 35 - src/tools/rust-analyzer/crates/salsa/FAQ.md | 34 - .../rust-analyzer/crates/salsa/LICENSE-APACHE | 176 ----- src/tools/rust-analyzer/crates/salsa/LICENSE-MIT | 23 - src/tools/rust-analyzer/crates/salsa/README.md | 42 - .../crates/salsa/salsa-macros/Cargo.toml | 23 - .../crates/salsa/salsa-macros/LICENSE-APACHE | 1 - .../crates/salsa/salsa-macros/LICENSE-MIT | 1 - .../crates/salsa/salsa-macros/README.md | 1 - .../salsa/salsa-macros/src/database_storage.rs | 243 ------ .../crates/salsa/salsa-macros/src/lib.rs | 125 --- .../crates/salsa/salsa-macros/src/parenthesized.rs | 13 - .../crates/salsa/salsa-macros/src/query_group.rs | 753 ------------------ src/tools/rust-analyzer/crates/salsa/src/debug.rs | 65 -- .../rust-analyzer/crates/salsa/src/derived.rs | 163 ---- .../rust-analyzer/crates/salsa/src/derived/slot.rs | 775 ------------------- .../rust-analyzer/crates/salsa/src/derived_lru.rs | 233 ------ .../crates/salsa/src/derived_lru/slot.rs | 845 --------------------- .../rust-analyzer/crates/salsa/src/durability.rs | 49 -- src/tools/rust-analyzer/crates/salsa/src/hash.rs | 3 - src/tools/rust-analyzer/crates/salsa/src/input.rs | 371 --------- .../rust-analyzer/crates/salsa/src/intern_id.rs | 131 ---- .../rust-analyzer/crates/salsa/src/interned.rs | 510 ------------- src/tools/rust-analyzer/crates/salsa/src/lib.rs | 750 ------------------ src/tools/rust-analyzer/crates/salsa/src/lru.rs | 325 -------- .../rust-analyzer/crates/salsa/src/plumbing.rs | 245 ------ .../rust-analyzer/crates/salsa/src/revision.rs | 66 -- .../rust-analyzer/crates/salsa/src/runtime.rs | 668 ---------------- .../crates/salsa/src/runtime/dependency_graph.rs | 250 ------ .../crates/salsa/src/runtime/local_state.rs | 204 ----- .../rust-analyzer/crates/salsa/src/storage.rs | 53 -- .../rust-analyzer/crates/salsa/tests/cycles.rs | 492 ------------ .../rust-analyzer/crates/salsa/tests/dyn_trait.rs | 28 - .../crates/salsa/tests/incremental/constants.rs | 145 ---- .../crates/salsa/tests/incremental/counter.rs | 14 - .../salsa/tests/incremental/implementation.rs | 59 -- .../crates/salsa/tests/incremental/log.rs | 16 - .../crates/salsa/tests/incremental/main.rs | 9 - .../salsa/tests/incremental/memoized_dep_inputs.rs | 60 -- .../salsa/tests/incremental/memoized_inputs.rs | 76 -- .../salsa/tests/incremental/memoized_volatile.rs | 77 -- .../rust-analyzer/crates/salsa/tests/interned.rs | 90 --- src/tools/rust-analyzer/crates/salsa/tests/lru.rs | 104 --- .../rust-analyzer/crates/salsa/tests/macros.rs | 12 - .../crates/salsa/tests/no_send_sync.rs | 31 - .../crates/salsa/tests/on_demand_inputs.rs | 147 ---- .../crates/salsa/tests/panic_safely.rs | 93 --- .../crates/salsa/tests/parallel/cancellation.rs | 132 ---- .../crates/salsa/tests/parallel/frozen.rs | 57 -- .../crates/salsa/tests/parallel/independent.rs | 29 - .../crates/salsa/tests/parallel/main.rs | 13 - .../tests/parallel/parallel_cycle_all_recover.rs | 109 --- .../tests/parallel/parallel_cycle_mid_recover.rs | 109 --- .../tests/parallel/parallel_cycle_none_recover.rs | 68 -- .../tests/parallel/parallel_cycle_one_recovers.rs | 94 --- .../crates/salsa/tests/parallel/race.rs | 37 - .../crates/salsa/tests/parallel/setup.rs | 197 ----- .../crates/salsa/tests/parallel/signal.rs | 40 - .../crates/salsa/tests/parallel/stress.rs | 168 ---- .../crates/salsa/tests/parallel/true_parallel.rs | 125 --- .../tests/storage_varieties/implementation.rs | 19 - .../crates/salsa/tests/storage_varieties/main.rs | 5 - .../salsa/tests/storage_varieties/queries.rs | 22 - .../crates/salsa/tests/storage_varieties/tests.rs | 49 -- .../crates/salsa/tests/transparent.rs | 39 - .../rust-analyzer/crates/salsa/tests/variadic.rs | 51 -- src/tools/rust-analyzer/crates/span/Cargo.toml | 2 +- src/tools/rust-analyzer/crates/span/src/hygiene.rs | 8 +- src/tools/rust-analyzer/crates/span/src/lib.rs | 10 +- src/tools/rust-analyzer/xtask/src/tidy.rs | 2 +- 170 files changed, 10287 insertions(+), 10285 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/Cargo.toml create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/FAQ.md create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/LICENSE-APACHE create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/LICENSE-MIT create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/README.md create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/Cargo.toml create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/LICENSE-APACHE create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/LICENSE-MIT create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/README.md create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/database_storage.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/lib.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/parenthesized.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/query_group.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/debug.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/derived.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/derived/slot.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/derived_lru.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/derived_lru/slot.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/durability.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/hash.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/input.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/intern_id.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/interned.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/lib.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/lru.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/plumbing.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/revision.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/runtime.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/runtime/dependency_graph.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/runtime/local_state.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/src/storage.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/cycles.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/dyn_trait.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/constants.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/counter.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/implementation.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/log.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/main.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_dep_inputs.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_inputs.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_volatile.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/interned.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/lru.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/macros.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/no_send_sync.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/on_demand_inputs.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/panic_safely.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/cancellation.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/frozen.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/independent.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/main.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_all_recover.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_mid_recover.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_none_recover.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_one_recovers.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/race.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/setup.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/signal.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/stress.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/true_parallel.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/implementation.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/main.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/queries.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/tests.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/transparent.rs create mode 100644 src/tools/rust-analyzer/crates/ra-salsa/tests/variadic.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/Cargo.toml delete mode 100644 src/tools/rust-analyzer/crates/salsa/FAQ.md delete mode 100644 src/tools/rust-analyzer/crates/salsa/LICENSE-APACHE delete mode 100644 src/tools/rust-analyzer/crates/salsa/LICENSE-MIT delete mode 100644 src/tools/rust-analyzer/crates/salsa/README.md delete mode 100644 src/tools/rust-analyzer/crates/salsa/salsa-macros/Cargo.toml delete mode 100644 src/tools/rust-analyzer/crates/salsa/salsa-macros/LICENSE-APACHE delete mode 100644 src/tools/rust-analyzer/crates/salsa/salsa-macros/LICENSE-MIT delete mode 100644 src/tools/rust-analyzer/crates/salsa/salsa-macros/README.md delete mode 100644 src/tools/rust-analyzer/crates/salsa/salsa-macros/src/database_storage.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/salsa-macros/src/lib.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/salsa-macros/src/parenthesized.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/salsa-macros/src/query_group.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/debug.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/derived.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/derived/slot.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/derived_lru.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/derived_lru/slot.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/durability.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/hash.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/input.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/intern_id.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/interned.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/lib.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/lru.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/plumbing.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/revision.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/runtime.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/runtime/dependency_graph.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/runtime/local_state.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/src/storage.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/cycles.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/dyn_trait.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/incremental/constants.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/incremental/counter.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/incremental/implementation.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/incremental/log.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/incremental/main.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_dep_inputs.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_inputs.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_volatile.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/interned.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/lru.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/macros.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/no_send_sync.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/on_demand_inputs.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/panic_safely.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/cancellation.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/frozen.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/independent.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/main.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_all_recover.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_mid_recover.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_none_recover.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_one_recovers.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/race.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/setup.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/signal.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/stress.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/parallel/true_parallel.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/implementation.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/main.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/queries.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/tests.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/transparent.rs delete mode 100644 src/tools/rust-analyzer/crates/salsa/tests/variadic.rs (limited to 'src') diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 0b3d6e2a1ef..ee310c06c08 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -20,7 +20,7 @@ rustc-hash.opt-level = 3 smol_str.opt-level = 3 text-size.opt-level = 3 serde.opt-level = 3 -salsa.opt-level = 3 +ra-salsa.opt-level = 3 # This speeds up `cargo xtask dist`. miniz_oxide.opt-level = 3 @@ -74,7 +74,7 @@ proc-macro-srv = { path = "./crates/proc-macro-srv", version = "0.0.0" } proc-macro-srv-cli = { path = "./crates/proc-macro-srv-cli", version = "0.0.0" } profile = { path = "./crates/profile", version = "0.0.0" } project-model = { path = "./crates/project-model", version = "0.0.0" } -salsa = { path = "./crates/salsa", version = "0.0.0" } +ra-salsa = { path = "./crates/ra-salsa", package = "salsa", version = "0.0.0" } span = { path = "./crates/span", version = "0.0.0" } stdx = { path = "./crates/stdx", version = "0.0.0" } syntax = { path = "./crates/syntax", version = "0.0.0" } diff --git a/src/tools/rust-analyzer/crates/base-db/Cargo.toml b/src/tools/rust-analyzer/crates/base-db/Cargo.toml index b17b08a720c..788ceb8857e 100644 --- a/src/tools/rust-analyzer/crates/base-db/Cargo.toml +++ b/src/tools/rust-analyzer/crates/base-db/Cargo.toml @@ -16,7 +16,7 @@ doctest = false lz4_flex = { version = "0.11", default-features = false } la-arena.workspace = true -salsa.workspace = true +ra-salsa.workspace = true rustc-hash.workspace = true triomphe.workspace = true semver.workspace = true diff --git a/src/tools/rust-analyzer/crates/base-db/src/change.rs b/src/tools/rust-analyzer/crates/base-db/src/change.rs index 4fb6654b612..7e40f5408f1 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/change.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/change.rs @@ -3,8 +3,8 @@ use std::fmt; +use ra_salsa::Durability; use rustc_hash::FxHashMap; -use salsa::Durability; use triomphe::Arc; use vfs::FileId; diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index 46e258d46f5..0a9e83bc3ba 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -5,8 +5,8 @@ mod input; use std::panic; +use ra_salsa::Durability; use rustc_hash::FxHashMap; -use salsa::Durability; use span::EditionedFileId; use syntax::{ast, Parse, SourceFile, SyntaxError}; use triomphe::Arc; @@ -20,7 +20,7 @@ pub use crate::{ TargetLayoutLoadResult, }, }; -pub use salsa::{self, Cancelled}; +pub use ra_salsa::{self, Cancelled}; pub use vfs::{file_set::FileSet, AnchoredPath, AnchoredPathBuf, VfsPath}; pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; @@ -28,11 +28,11 @@ pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; #[macro_export] macro_rules! impl_intern_key { ($name:ident) => { - impl $crate::salsa::InternKey for $name { - fn from_intern_id(v: $crate::salsa::InternId) -> Self { + impl $crate::ra_salsa::InternKey for $name { + fn from_intern_id(v: $crate::ra_salsa::InternId) -> Self { $name(v) } - fn as_intern_id(&self) -> $crate::salsa::InternId { + fn as_intern_id(&self) -> $crate::ra_salsa::InternId { self.0 } } @@ -55,30 +55,30 @@ pub trait FileLoader { /// Database which stores all significant input facts: source code and project /// model. Everything else in rust-analyzer is derived from these queries. -#[salsa::query_group(SourceDatabaseStorage)] +#[ra_salsa::query_group(SourceDatabaseStorage)] pub trait SourceDatabase: FileLoader + std::fmt::Debug { - #[salsa::input] + #[ra_salsa::input] fn compressed_file_text(&self, file_id: FileId) -> Arc<[u8]>; /// Text of the file. - #[salsa::lru] + #[ra_salsa::lru] fn file_text(&self, file_id: FileId) -> Arc; /// Parses the file into the syntax tree. - #[salsa::lru] + #[ra_salsa::lru] fn parse(&self, file_id: EditionedFileId) -> Parse; /// Returns the set of errors obtained from parsing the file including validation errors. fn parse_errors(&self, file_id: EditionedFileId) -> Option>; /// The crate graph. - #[salsa::input] + #[ra_salsa::input] fn crate_graph(&self) -> Arc; - #[salsa::input] + #[ra_salsa::input] fn crate_workspace_data(&self) -> Arc>>; - #[salsa::transparent] + #[ra_salsa::transparent] fn toolchain_channel(&self, krate: CrateId) -> Option; } @@ -126,14 +126,14 @@ fn file_text(db: &dyn SourceDatabase, file_id: FileId) -> Arc { /// We don't want to give HIR knowledge of source roots, hence we extract these /// methods into a separate DB. -#[salsa::query_group(SourceRootDatabaseStorage)] +#[ra_salsa::query_group(SourceRootDatabaseStorage)] pub trait SourceRootDatabase: SourceDatabase { /// Path to a file, relative to the root of its source root. /// Source root of the file. - #[salsa::input] + #[ra_salsa::input] fn file_source_root(&self, file_id: FileId) -> SourceRootId; /// Contents of the source root. - #[salsa::input] + #[ra_salsa::input] fn source_root(&self, id: SourceRootId) -> Arc; /// Crates whose root fool is in `id`. diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index b1103d35cab..aeda302f35c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -1,5 +1,5 @@ //! Defines database & queries for name resolution. -use base_db::{salsa, CrateId, SourceDatabase, Upcast}; +use base_db::{ra_salsa, CrateId, SourceDatabase, Upcast}; use either::Either; use hir_expand::{db::ExpandDatabase, HirFileId, MacroDefId}; use intern::{sym, Interned}; @@ -31,71 +31,71 @@ use crate::{ UseId, UseLoc, VariantId, }; -#[salsa::query_group(InternDatabaseStorage)] +#[ra_salsa::query_group(InternDatabaseStorage)] pub trait InternDatabase: SourceDatabase { // region: items - #[salsa::interned] + #[ra_salsa::interned] fn intern_use(&self, loc: UseLoc) -> UseId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_extern_crate(&self, loc: ExternCrateLoc) -> ExternCrateId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_function(&self, loc: FunctionLoc) -> FunctionId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_struct(&self, loc: StructLoc) -> StructId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_union(&self, loc: UnionLoc) -> UnionId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_enum(&self, loc: EnumLoc) -> EnumId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_enum_variant(&self, loc: EnumVariantLoc) -> EnumVariantId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_const(&self, loc: ConstLoc) -> ConstId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_static(&self, loc: StaticLoc) -> StaticId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_trait(&self, loc: TraitLoc) -> TraitId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_trait_alias(&self, loc: TraitAliasLoc) -> TraitAliasId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_type_alias(&self, loc: TypeAliasLoc) -> TypeAliasId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_impl(&self, loc: ImplLoc) -> ImplId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_extern_block(&self, loc: ExternBlockLoc) -> ExternBlockId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_macro2(&self, loc: Macro2Loc) -> Macro2Id; - #[salsa::interned] + #[ra_salsa::interned] fn intern_proc_macro(&self, loc: ProcMacroLoc) -> ProcMacroId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_macro_rules(&self, loc: MacroRulesLoc) -> MacroRulesId; // endregion: items - #[salsa::interned] + #[ra_salsa::interned] fn intern_block(&self, loc: BlockLoc) -> BlockId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_anonymous_const(&self, id: ConstBlockLoc) -> ConstBlockId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_in_type_const(&self, id: InTypeConstLoc) -> InTypeConstId; } -#[salsa::query_group(DefDatabaseStorage)] +#[ra_salsa::query_group(DefDatabaseStorage)] pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast { /// Whether to expand procedural macros during name resolution. - #[salsa::input] + #[ra_salsa::input] fn expand_proc_attr_macros(&self) -> bool; /// Computes an [`ItemTree`] for the given file or macro expansion. - #[salsa::invoke(ItemTree::file_item_tree_query)] + #[ra_salsa::invoke(ItemTree::file_item_tree_query)] fn file_item_tree(&self, file_id: HirFileId) -> Arc; - #[salsa::invoke(ItemTree::block_item_tree_query)] + #[ra_salsa::invoke(ItemTree::block_item_tree_query)] fn block_item_tree(&self, block_id: BlockId) -> Arc; - #[salsa::invoke(DefMap::crate_def_map_query)] + #[ra_salsa::invoke(DefMap::crate_def_map_query)] fn crate_def_map(&self, krate: CrateId) -> Arc; /// Computes the block-level `DefMap`. - #[salsa::invoke(DefMap::block_def_map_query)] + #[ra_salsa::invoke(DefMap::block_def_map_query)] fn block_def_map(&self, block: BlockId) -> Arc; /// Turns a MacroId into a MacroDefId, describing the macro's definition post name resolution. @@ -103,139 +103,139 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast Arc; - #[salsa::invoke(StructData::struct_data_with_diagnostics_query)] + #[ra_salsa::invoke(StructData::struct_data_with_diagnostics_query)] fn struct_data_with_diagnostics(&self, id: StructId) -> (Arc, DefDiagnostics); - #[salsa::transparent] - #[salsa::invoke(StructData::union_data_query)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(StructData::union_data_query)] fn union_data(&self, id: UnionId) -> Arc; - #[salsa::invoke(StructData::union_data_with_diagnostics_query)] + #[ra_salsa::invoke(StructData::union_data_with_diagnostics_query)] fn union_data_with_diagnostics(&self, id: UnionId) -> (Arc, DefDiagnostics); - #[salsa::invoke(EnumData::enum_data_query)] + #[ra_salsa::invoke(EnumData::enum_data_query)] fn enum_data(&self, e: EnumId) -> Arc; - #[salsa::transparent] - #[salsa::invoke(EnumVariantData::enum_variant_data_query)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(EnumVariantData::enum_variant_data_query)] fn enum_variant_data(&self, id: EnumVariantId) -> Arc; - #[salsa::invoke(EnumVariantData::enum_variant_data_with_diagnostics_query)] + #[ra_salsa::invoke(EnumVariantData::enum_variant_data_with_diagnostics_query)] fn enum_variant_data_with_diagnostics( &self, id: EnumVariantId, ) -> (Arc, DefDiagnostics); - #[salsa::transparent] - #[salsa::invoke(VariantData::variant_data)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(VariantData::variant_data)] fn variant_data(&self, id: VariantId) -> Arc; - #[salsa::transparent] - #[salsa::invoke(ImplData::impl_data_query)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(ImplData::impl_data_query)] fn impl_data(&self, e: ImplId) -> Arc; - #[salsa::invoke(ImplData::impl_data_with_diagnostics_query)] + #[ra_salsa::invoke(ImplData::impl_data_with_diagnostics_query)] fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc, DefDiagnostics); - #[salsa::transparent] - #[salsa::invoke(TraitData::trait_data_query)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(TraitData::trait_data_query)] fn trait_data(&self, e: TraitId) -> Arc; - #[salsa::invoke(TraitData::trait_data_with_diagnostics_query)] + #[ra_salsa::invoke(TraitData::trait_data_with_diagnostics_query)] fn trait_data_with_diagnostics(&self, tr: TraitId) -> (Arc, DefDiagnostics); - #[salsa::invoke(TraitAliasData::trait_alias_query)] + #[ra_salsa::invoke(TraitAliasData::trait_alias_query)] fn trait_alias_data(&self, e: TraitAliasId) -> Arc; - #[salsa::invoke(TypeAliasData::type_alias_data_query)] + #[ra_salsa::invoke(TypeAliasData::type_alias_data_query)] fn type_alias_data(&self, e: TypeAliasId) -> Arc; - #[salsa::invoke(FunctionData::fn_data_query)] + #[ra_salsa::invoke(FunctionData::fn_data_query)] fn function_data(&self, func: FunctionId) -> Arc; - #[salsa::invoke(ConstData::const_data_query)] + #[ra_salsa::invoke(ConstData::const_data_query)] fn const_data(&self, konst: ConstId) -> Arc; - #[salsa::invoke(StaticData::static_data_query)] + #[ra_salsa::invoke(StaticData::static_data_query)] fn static_data(&self, statik: StaticId) -> Arc; - #[salsa::invoke(Macro2Data::macro2_data_query)] + #[ra_salsa::invoke(Macro2Data::macro2_data_query)] fn macro2_data(&self, makro: Macro2Id) -> Arc; - #[salsa::invoke(MacroRulesData::macro_rules_data_query)] + #[ra_salsa::invoke(MacroRulesData::macro_rules_data_query)] fn macro_rules_data(&self, makro: MacroRulesId) -> Arc; - #[salsa::invoke(ProcMacroData::proc_macro_data_query)] + #[ra_salsa::invoke(ProcMacroData::proc_macro_data_query)] fn proc_macro_data(&self, makro: ProcMacroId) -> Arc; - #[salsa::invoke(ExternCrateDeclData::extern_crate_decl_data_query)] + #[ra_salsa::invoke(ExternCrateDeclData::extern_crate_decl_data_query)] fn extern_crate_decl_data(&self, extern_crate: ExternCrateId) -> Arc; // endregion:data - #[salsa::invoke(Body::body_with_source_map_query)] - #[salsa::lru] + #[ra_salsa::invoke(Body::body_with_source_map_query)] + #[ra_salsa::lru] fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc, Arc); - #[salsa::invoke(Body::body_query)] + #[ra_salsa::invoke(Body::body_query)] fn body(&self, def: DefWithBodyId) -> Arc; - #[salsa::invoke(ExprScopes::expr_scopes_query)] + #[ra_salsa::invoke(ExprScopes::expr_scopes_query)] fn expr_scopes(&self, def: DefWithBodyId) -> Arc; - #[salsa::invoke(GenericParams::generic_params_query)] + #[ra_salsa::invoke(GenericParams::generic_params_query)] fn generic_params(&self, def: GenericDefId) -> Interned; // region:attrs - #[salsa::invoke(Attrs::fields_attrs_query)] + #[ra_salsa::invoke(Attrs::fields_attrs_query)] fn fields_attrs(&self, def: VariantId) -> Arc>; // should this really be a query? - #[salsa::invoke(crate::attr::fields_attrs_source_map)] + #[ra_salsa::invoke(crate::attr::fields_attrs_source_map)] fn fields_attrs_source_map( &self, def: VariantId, ) -> Arc>>>; - #[salsa::invoke(AttrsWithOwner::attrs_query)] + #[ra_salsa::invoke(AttrsWithOwner::attrs_query)] fn attrs(&self, def: AttrDefId) -> Attrs; - #[salsa::transparent] - #[salsa::invoke(lang_item::lang_attr)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(lang_item::lang_attr)] fn lang_attr(&self, def: AttrDefId) -> Option; // endregion:attrs - #[salsa::invoke(LangItems::lang_item_query)] + #[ra_salsa::invoke(LangItems::lang_item_query)] fn lang_item(&self, start_crate: CrateId, item: LangItem) -> Option; - #[salsa::invoke(ImportMap::import_map_query)] + #[ra_salsa::invoke(ImportMap::import_map_query)] fn import_map(&self, krate: CrateId) -> Arc; // region:visibilities - #[salsa::invoke(visibility::field_visibilities_query)] + #[ra_salsa::invoke(visibility::field_visibilities_query)] fn field_visibilities(&self, var: VariantId) -> Arc>; // FIXME: unify function_visibility and const_visibility? - #[salsa::invoke(visibility::function_visibility_query)] + #[ra_salsa::invoke(visibility::function_visibility_query)] fn function_visibility(&self, def: FunctionId) -> Visibility; - #[salsa::invoke(visibility::const_visibility_query)] + #[ra_salsa::invoke(visibility::const_visibility_query)] fn const_visibility(&self, def: ConstId) -> Visibility; // endregion:visibilities - #[salsa::invoke(LangItems::crate_lang_items_query)] + #[ra_salsa::invoke(LangItems::crate_lang_items_query)] fn crate_lang_items(&self, krate: CrateId) -> Option>; - #[salsa::invoke(crate::lang_item::notable_traits_in_deps)] + #[ra_salsa::invoke(crate::lang_item::notable_traits_in_deps)] fn notable_traits_in_deps(&self, krate: CrateId) -> Arc<[Arc<[TraitId]>]>; - #[salsa::invoke(crate::lang_item::crate_notable_traits)] + #[ra_salsa::invoke(crate::lang_item::crate_notable_traits)] fn crate_notable_traits(&self, krate: CrateId) -> Option>; fn crate_supports_no_std(&self, crate_id: CrateId) -> bool; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index 0213bd904b6..157c9ef0805 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -71,7 +71,7 @@ use std::{ use base_db::{ impl_intern_key, - salsa::{self, InternValueTrivial}, + ra_salsa::{self, InternValueTrivial}, CrateId, }; use hir_expand::{ @@ -206,85 +206,85 @@ macro_rules! impl_loc { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct FunctionId(salsa::InternId); +pub struct FunctionId(ra_salsa::InternId); type FunctionLoc = AssocItemLoc; impl_intern!(FunctionId, FunctionLoc, intern_function, lookup_intern_function); impl_loc!(FunctionLoc, id: Function, container: ItemContainerId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct StructId(salsa::InternId); +pub struct StructId(ra_salsa::InternId); type StructLoc = ItemLoc; impl_intern!(StructId, StructLoc, intern_struct, lookup_intern_struct); impl_loc!(StructLoc, id: Struct, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct UnionId(salsa::InternId); +pub struct UnionId(ra_salsa::InternId); pub type UnionLoc = ItemLoc; impl_intern!(UnionId, UnionLoc, intern_union, lookup_intern_union); impl_loc!(UnionLoc, id: Union, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct EnumId(salsa::InternId); +pub struct EnumId(ra_salsa::InternId); pub type EnumLoc = ItemLoc; impl_intern!(EnumId, EnumLoc, intern_enum, lookup_intern_enum); impl_loc!(EnumLoc, id: Enum, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ConstId(salsa::InternId); +pub struct ConstId(ra_salsa::InternId); type ConstLoc = AssocItemLoc; impl_intern!(ConstId, ConstLoc, intern_const, lookup_intern_const); impl_loc!(ConstLoc, id: Const, container: ItemContainerId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct StaticId(salsa::InternId); +pub struct StaticId(ra_salsa::InternId); pub type StaticLoc = AssocItemLoc; impl_intern!(StaticId, StaticLoc, intern_static, lookup_intern_static); impl_loc!(StaticLoc, id: Static, container: ItemContainerId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct TraitId(salsa::InternId); +pub struct TraitId(ra_salsa::InternId); pub type TraitLoc = ItemLoc; impl_intern!(TraitId, TraitLoc, intern_trait, lookup_intern_trait); impl_loc!(TraitLoc, id: Trait, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TraitAliasId(salsa::InternId); +pub struct TraitAliasId(ra_salsa::InternId); pub type TraitAliasLoc = ItemLoc; impl_intern!(TraitAliasId, TraitAliasLoc, intern_trait_alias, lookup_intern_trait_alias); impl_loc!(TraitAliasLoc, id: TraitAlias, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TypeAliasId(salsa::InternId); +pub struct TypeAliasId(ra_salsa::InternId); type TypeAliasLoc = AssocItemLoc; impl_intern!(TypeAliasId, TypeAliasLoc, intern_type_alias, lookup_intern_type_alias); impl_loc!(TypeAliasLoc, id: TypeAlias, container: ItemContainerId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ImplId(salsa::InternId); +pub struct ImplId(ra_salsa::InternId); type ImplLoc = ItemLoc; impl_intern!(ImplId, ImplLoc, intern_impl, lookup_intern_impl); impl_loc!(ImplLoc, id: Impl, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct UseId(salsa::InternId); +pub struct UseId(ra_salsa::InternId); type UseLoc = ItemLoc; impl_intern!(UseId, UseLoc, intern_use, lookup_intern_use); impl_loc!(UseLoc, id: Use, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ExternCrateId(salsa::InternId); +pub struct ExternCrateId(ra_salsa::InternId); type ExternCrateLoc = ItemLoc; impl_intern!(ExternCrateId, ExternCrateLoc, intern_extern_crate, lookup_intern_extern_crate); impl_loc!(ExternCrateLoc, id: ExternCrate, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ExternBlockId(salsa::InternId); +pub struct ExternBlockId(ra_salsa::InternId); type ExternBlockLoc = ItemLoc; impl_intern!(ExternBlockId, ExternBlockLoc, intern_extern_block, lookup_intern_extern_block); impl_loc!(ExternBlockLoc, id: ExternBlock, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct EnumVariantId(salsa::InternId); +pub struct EnumVariantId(ra_salsa::InternId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct EnumVariantLoc { @@ -296,7 +296,7 @@ impl_intern!(EnumVariantId, EnumVariantLoc, intern_enum_variant, lookup_intern_e impl_loc!(EnumVariantLoc, id: Variant, parent: EnumId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct Macro2Id(salsa::InternId); +pub struct Macro2Id(ra_salsa::InternId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Macro2Loc { pub container: ModuleId, @@ -309,7 +309,7 @@ impl_intern!(Macro2Id, Macro2Loc, intern_macro2, lookup_intern_macro2); impl_loc!(Macro2Loc, id: Macro2, container: ModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct MacroRulesId(salsa::InternId); +pub struct MacroRulesId(ra_salsa::InternId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MacroRulesLoc { pub container: ModuleId, @@ -338,7 +338,7 @@ pub enum MacroExpander { BuiltInEager(EagerExpander), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ProcMacroId(salsa::InternId); +pub struct ProcMacroId(ra_salsa::InternId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ProcMacroLoc { pub container: CrateRootModuleId, @@ -351,7 +351,7 @@ impl_intern!(ProcMacroId, ProcMacroLoc, intern_proc_macro, lookup_intern_proc_ma impl_loc!(ProcMacroLoc, id: Function, container: CrateRootModuleId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct BlockId(salsa::InternId); +pub struct BlockId(ra_salsa::InternId); #[derive(Debug, Hash, PartialEq, Eq, Clone)] pub struct BlockLoc { ast_id: AstId, @@ -363,7 +363,7 @@ impl_intern!(BlockId, BlockLoc, intern_block, lookup_intern_block); /// Id of the anonymous const block expression and patterns. This is very similar to `ClosureId` and /// shouldn't be a `DefWithBodyId` since its type inference is dependent on its parent. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct ConstBlockId(salsa::InternId); +pub struct ConstBlockId(ra_salsa::InternId); impl_intern!(ConstBlockId, ConstBlockLoc, intern_anonymous_const, lookup_intern_anonymous_const); #[derive(Debug, Hash, PartialEq, Eq, Clone)] @@ -803,7 +803,7 @@ impl Clone for Box { /// length (like `[u8; 2 + 2]`). These constants are body owner and are a variant of `DefWithBodyId`. These /// are not called `AnonymousConstId` to prevent confusion with [`ConstBlockId`]. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct InTypeConstId(salsa::InternId); +pub struct InTypeConstId(ra_salsa::InternId); impl_intern!(InTypeConstId, InTypeConstLoc, intern_in_type_const, lookup_intern_in_type_const); // We would like to set `derive(PartialEq)` diff --git a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs index df9dec69d46..4db21eb46bd 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs @@ -3,7 +3,7 @@ use std::{fmt, panic, sync::Mutex}; use base_db::{ - salsa::{self, Durability}, + ra_salsa::{self, Durability}, AnchoredPath, CrateId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast, }; use hir_expand::{db::ExpandDatabase, files::FilePosition, InFile}; @@ -18,7 +18,7 @@ use crate::{ LocalModuleId, Lookup, ModuleDefId, ModuleId, }; -#[salsa::database( +#[ra_salsa::database( base_db::SourceRootDatabaseStorage, base_db::SourceDatabaseStorage, hir_expand::db::ExpandDatabaseStorage, @@ -26,8 +26,8 @@ use crate::{ crate::db::DefDatabaseStorage )] pub(crate) struct TestDB { - storage: salsa::Storage, - events: Mutex>>, + storage: ra_salsa::Storage, + events: Mutex>>, } impl Default for TestDB { @@ -51,8 +51,8 @@ impl Upcast for TestDB { } } -impl salsa::Database for TestDB { - fn salsa_event(&self, event: salsa::Event) { +impl ra_salsa::Database for TestDB { + fn salsa_event(&self, event: ra_salsa::Event) { let mut events = self.events.lock().unwrap(); if let Some(events) = &mut *events { events.push(event); @@ -215,7 +215,7 @@ impl TestDB { None } - pub(crate) fn log(&self, f: impl FnOnce()) -> Vec { + pub(crate) fn log(&self, f: impl FnOnce()) -> Vec { *self.events.lock().unwrap() = Some(Vec::new()); f(); self.events.lock().unwrap().take().unwrap() @@ -228,7 +228,7 @@ impl TestDB { .filter_map(|e| match e.kind { // This is pretty horrible, but `Debug` is the only way to inspect // QueryDescriptor at the moment. - salsa::EventKind::WillExecute { database_key } => { + ra_salsa::EventKind::WillExecute { database_key } => { Some(format!("{:?}", database_key.debug(self))) } _ => None, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/change.rs b/src/tools/rust-analyzer/crates/hir-expand/src/change.rs index de3a7b9f561..1fdf251ba52 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/change.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/change.rs @@ -1,7 +1,7 @@ //! Defines a unit of change that can applied to the database to get the next //! state. Changes are transactional. use base_db::{ - salsa::Durability, CrateGraph, CrateId, CrateWorkspaceData, FileChange, SourceRoot, + ra_salsa::Durability, CrateGraph, CrateId, CrateWorkspaceData, FileChange, SourceRoot, SourceRootDatabase, }; use rustc_hash::FxHashMap; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index 484a8662eb1..d412bf4eee5 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -1,6 +1,6 @@ //! Defines database & queries for macro expansion. -use base_db::{salsa, CrateId, SourceDatabase}; +use base_db::{ra_salsa, CrateId, SourceDatabase}; use either::Either; use limit::Limit; use mbe::MatchedArmIndex; @@ -53,32 +53,32 @@ pub enum TokenExpander { ProcMacro(CustomProcMacroExpander), } -#[salsa::query_group(ExpandDatabaseStorage)] +#[ra_salsa::query_group(ExpandDatabaseStorage)] pub trait ExpandDatabase: SourceDatabase { /// The proc macros. - #[salsa::input] + #[ra_salsa::input] fn proc_macros(&self) -> Arc; fn ast_id_map(&self, file_id: HirFileId) -> Arc; /// Main public API -- parses a hir file, not caring whether it's a real /// file or a macro expansion. - #[salsa::transparent] + #[ra_salsa::transparent] fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode; /// Implementation for the macro case. - #[salsa::lru] + #[ra_salsa::lru] fn parse_macro_expansion( &self, macro_file: MacroFileId, ) -> ExpandResult<(Parse, Arc)>; - #[salsa::transparent] - #[salsa::invoke(SpanMap::new)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(SpanMap::new)] fn span_map(&self, file_id: HirFileId) -> SpanMap; - #[salsa::transparent] - #[salsa::invoke(crate::span_map::expansion_span_map)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(crate::span_map::expansion_span_map)] fn expansion_span_map(&self, file_id: MacroFileId) -> Arc; - #[salsa::invoke(crate::span_map::real_span_map)] + #[ra_salsa::invoke(crate::span_map::real_span_map)] fn real_span_map(&self, file_id: EditionedFileId) -> Arc; /// Macro ids. That's probably the tricksiest bit in rust-analyzer, and the @@ -86,15 +86,15 @@ pub trait ExpandDatabase: SourceDatabase { /// /// We encode macro definitions into ids of macro calls, this what allows us /// to be incremental. - #[salsa::interned] + #[ra_salsa::interned] fn intern_macro_call(&self, macro_call: MacroCallLoc) -> MacroCallId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_syntax_context(&self, ctx: SyntaxContextData) -> SyntaxContextId; - #[salsa::transparent] + #[ra_salsa::transparent] fn setup_syntax_context_root(&self) -> (); - #[salsa::transparent] - #[salsa::invoke(crate::hygiene::dump_syntax_contexts)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(crate::hygiene::dump_syntax_contexts)] fn dump_syntax_contexts(&self) -> String; /// Lowers syntactic macro call to a token tree representation. That's a firewall @@ -102,18 +102,18 @@ pub trait ExpandDatabase: SourceDatabase { /// subtree. #[deprecated = "calling this is incorrect, call `macro_arg_considering_derives` instead"] fn macro_arg(&self, id: MacroCallId) -> MacroArgResult; - #[salsa::transparent] + #[ra_salsa::transparent] fn macro_arg_considering_derives( &self, id: MacroCallId, kind: &MacroCallKind, ) -> MacroArgResult; /// Fetches the expander for this macro. - #[salsa::transparent] - #[salsa::invoke(TokenExpander::macro_expander)] + #[ra_salsa::transparent] + #[ra_salsa::invoke(TokenExpander::macro_expander)] fn macro_expander(&self, id: MacroDefId) -> TokenExpander; /// Fetches (and compiles) the expander of this decl macro. - #[salsa::invoke(DeclarativeMacroExpander::expander)] + #[ra_salsa::invoke(DeclarativeMacroExpander::expander)] fn decl_macro_expander( &self, def_crate: CrateId, @@ -135,7 +135,7 @@ pub trait ExpandDatabase: SourceDatabase { &self, macro_call: MacroCallId, ) -> Option>>>; - #[salsa::transparent] + #[ra_salsa::transparent] fn syntax_context(&self, file: HirFileId) -> SyntaxContextId; } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs index 5e1448f7950..a8191189157 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs @@ -97,7 +97,7 @@ fn apply_mark_internal( call_id: MacroCallId, transparency: Transparency, ) -> SyntaxContextId { - use base_db::salsa; + use base_db::ra_salsa; let call_id = Some(call_id); @@ -107,7 +107,7 @@ fn apply_mark_internal( if transparency >= Transparency::Opaque { let parent = opaque; - opaque = salsa::plumbing::get_query_table::(db).get_or_insert( + opaque = ra_salsa::plumbing::get_query_table::(db).get_or_insert( (parent, call_id, transparency), |new_opaque| SyntaxContextData { outer_expn: call_id, @@ -122,7 +122,7 @@ fn apply_mark_internal( if transparency >= Transparency::SemiTransparent { let parent = opaque_and_semitransparent; opaque_and_semitransparent = - salsa::plumbing::get_query_table::(db).get_or_insert( + ra_salsa::plumbing::get_query_table::(db).get_or_insert( (parent, call_id, transparency), |new_opaque_and_semitransparent| SyntaxContextData { outer_expn: call_id, @@ -200,7 +200,7 @@ pub fn marks_rev( pub(crate) fn dump_syntax_contexts(db: &dyn ExpandDatabase) -> String { use crate::db::{InternMacroCallLookupQuery, InternSyntaxContextLookupQuery}; - use base_db::salsa::debug::DebugQueryTable; + use base_db::ra_salsa::debug::DebugQueryTable; let mut s = String::from("Expansions:"); let mut entries = InternMacroCallLookupQuery.in_db(db).entries::>(); diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index 56cb5fd375c..5d5f72490d0 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -30,7 +30,7 @@ use triomphe::Arc; use std::hash::Hash; -use base_db::{salsa::InternValueTrivial, CrateId}; +use base_db::{ra_salsa::InternValueTrivial, CrateId}; use either::Either; use span::{ Edition, EditionedFileId, ErasedFileAstId, FileAstId, HirFileIdRepr, Span, SpanAnchor, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs index 968a828e9df..e41058aac2a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs @@ -1,6 +1,6 @@ //! Constant evaluation details -use base_db::{salsa::Cycle, CrateId}; +use base_db::{ra_salsa::Cycle, CrateId}; use chalk_ir::{cast::Cast, BoundVar, DebruijnIndex}; use hir_def::{ body::Body, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs index 5620d80adb5..3a3a05c369a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -5,7 +5,7 @@ use std::sync; use base_db::{ impl_intern_key, - salsa::{self, InternValueTrivial}, + ra_salsa::{self, InternValueTrivial}, CrateId, Upcast, }; use hir_def::{ @@ -30,22 +30,22 @@ use crate::{ }; use hir_expand::name::Name; -#[salsa::query_group(HirDatabaseStorage)] +#[ra_salsa::query_group(HirDatabaseStorage)] pub trait HirDatabase: DefDatabase + Upcast { - #[salsa::invoke(crate::infer::infer_query)] + #[ra_salsa::invoke(crate::infer::infer_query)] fn infer(&self, def: DefWithBodyId) -> Arc; // region:mir - #[salsa::invoke(crate::mir::mir_body_query)] - #[salsa::cycle(crate::mir::mir_body_recover)] + #[ra_salsa::invoke(crate::mir::mir_body_query)] + #[ra_salsa::cycle(crate::mir::mir_body_recover)] fn mir_body(&self, def: DefWithBodyId) -> Result, MirLowerError>; - #[salsa::invoke(crate::mir::mir_body_for_closure_query)] + #[ra_salsa::invoke(crate::mir::mir_body_for_closure_query)] fn mir_body_for_closure(&self, def: ClosureId) -> Result, MirLowerError>; - #[salsa::invoke(crate::mir::monomorphized_mir_body_query)] - #[salsa::cycle(crate::mir::monomorphized_mir_body_recover)] + #[ra_salsa::invoke(crate::mir::monomorphized_mir_body_query)] + #[ra_salsa::cycle(crate::mir::monomorphized_mir_body_recover)] fn monomorphized_mir_body( &self, def: DefWithBodyId, @@ -53,7 +53,7 @@ pub trait HirDatabase: DefDatabase + Upcast { env: Arc, ) -> Result, MirLowerError>; - #[salsa::invoke(crate::mir::monomorphized_mir_body_for_closure_query)] + #[ra_salsa::invoke(crate::mir::monomorphized_mir_body_for_closure_query)] fn monomorphized_mir_body_for_closure( &self, def: ClosureId, @@ -61,12 +61,12 @@ pub trait HirDatabase: DefDatabase + Upcast { env: Arc, ) -> Result, MirLowerError>; - #[salsa::invoke(crate::mir::borrowck_query)] - #[salsa::lru] + #[ra_salsa::invoke(crate::mir::borrowck_query)] + #[ra_salsa::lru] fn borrowck(&self, def: DefWithBodyId) -> Result, MirLowerError>; - #[salsa::invoke(crate::consteval::const_eval_query)] - #[salsa::cycle(crate::consteval::const_eval_recover)] + #[ra_salsa::invoke(crate::consteval::const_eval_query)] + #[ra_salsa::cycle(crate::consteval::const_eval_recover)] fn const_eval( &self, def: GeneralConstId, @@ -74,15 +74,15 @@ pub trait HirDatabase: DefDatabase + Upcast { trait_env: Option>, ) -> Result; - #[salsa::invoke(crate::consteval::const_eval_static_query)] - #[salsa::cycle(crate::consteval::const_eval_static_recover)] + #[ra_salsa::invoke(crate::consteval::const_eval_static_query)] + #[ra_salsa::cycle(crate::consteval::const_eval_static_recover)] fn const_eval_static(&self, def: StaticId) -> Result; - #[salsa::invoke(crate::consteval::const_eval_discriminant_variant)] - #[salsa::cycle(crate::consteval::const_eval_discriminant_recover)] + #[ra_salsa::invoke(crate::consteval::const_eval_discriminant_variant)] + #[ra_salsa::cycle(crate::consteval::const_eval_discriminant_recover)] fn const_eval_discriminant(&self, def: EnumVariantId) -> Result; - #[salsa::invoke(crate::method_resolution::lookup_impl_method_query)] + #[ra_salsa::invoke(crate::method_resolution::lookup_impl_method_query)] fn lookup_impl_method( &self, env: Arc, @@ -92,8 +92,8 @@ pub trait HirDatabase: DefDatabase + Upcast { // endregion:mir - #[salsa::invoke(crate::layout::layout_of_adt_query)] - #[salsa::cycle(crate::layout::layout_of_adt_recover)] + #[ra_salsa::invoke(crate::layout::layout_of_adt_query)] + #[ra_salsa::cycle(crate::layout::layout_of_adt_recover)] fn layout_of_adt( &self, def: AdtId, @@ -101,49 +101,49 @@ pub trait HirDatabase: DefDatabase + Upcast { env: Arc, ) -> Result, LayoutError>; - #[salsa::invoke(crate::layout::layout_of_ty_query)] - #[salsa::cycle(crate::layout::layout_of_ty_recover)] + #[ra_salsa::invoke(crate::layout::layout_of_ty_query)] + #[ra_salsa::cycle(crate::layout::layout_of_ty_recover)] fn layout_of_ty(&self, ty: Ty, env: Arc) -> Result, LayoutError>; - #[salsa::invoke(crate::layout::target_data_layout_query)] + #[ra_salsa::invoke(crate::layout::target_data_layout_query)] fn target_data_layout(&self, krate: CrateId) -> Result, Arc>; - #[salsa::invoke(crate::dyn_compatibility::dyn_compatibility_of_trait_query)] + #[ra_salsa::invoke(crate::dyn_compatibility::dyn_compatibility_of_trait_query)] fn dyn_compatibility_of_trait(&self, trait_: TraitId) -> Option; - #[salsa::invoke(crate::lower::ty_query)] - #[salsa::cycle(crate::lower::ty_recover)] + #[ra_salsa::invoke(crate::lower::ty_query)] + #[ra_salsa::cycle(crate::lower::ty_recover)] fn ty(&self, def: TyDefId) -> Binders; /// Returns the type of the value of the given constant, or `None` if the `ValueTyDefId` is /// a `StructId` or `EnumVariantId` with a record constructor. - #[salsa::invoke(crate::lower::value_ty_query)] + #[ra_salsa::invoke(crate::lower::value_ty_query)] fn value_ty(&self, def: ValueTyDefId) -> Option>; - #[salsa::invoke(crate::lower::impl_self_ty_query)] - #[salsa::cycle(crate::lower::impl_self_ty_recover)] + #[ra_salsa::invoke(crate::lower::impl_self_ty_query)] + #[ra_salsa::cycle(crate::lower::impl_self_ty_recover)] fn impl_self_ty(&self, def: ImplId) -> Binders; - #[salsa::invoke(crate::lower::const_param_ty_query)] + #[ra_salsa::invoke(crate::lower::const_param_ty_query)] fn const_param_ty(&self, def: ConstParamId) -> Ty; - #[salsa::invoke(crate::lower::impl_trait_query)] + #[ra_salsa::invoke(crate::lower::impl_trait_query)] fn impl_trait(&self, def: ImplId) -> Option>; - #[salsa::invoke(crate::lower::field_types_query)] + #[ra_salsa::invoke(crate::lower::field_types_query)] fn field_types(&self, var: VariantId) -> Arc>>; - #[salsa::invoke(crate::lower::callable_item_sig)] + #[ra_salsa::invoke(crate::lower::callable_item_sig)] fn callable_item_signature(&self, def: CallableDefId) -> PolyFnSig; - #[salsa::invoke(crate::lower::return_type_impl_traits)] + #[ra_salsa::invoke(crate::lower::return_type_impl_traits)] fn return_type_impl_traits(&self, def: FunctionId) -> Option>>; - #[salsa::invoke(crate::lower::type_alias_impl_traits)] + #[ra_salsa::invoke(crate::lower::type_alias_impl_traits)] fn type_alias_impl_traits(&self, def: TypeAliasId) -> Option>>; - #[salsa::invoke(crate::lower::generic_predicates_for_param_query)] - #[salsa::cycle(crate::lower::generic_predicates_for_param_recover)] + #[ra_salsa::invoke(crate::lower::generic_predicates_for_param_query)] + #[ra_salsa::cycle(crate::lower::generic_predicates_for_param_recover)] fn generic_predicates_for_param( &self, def: GenericDefId, @@ -151,118 +151,118 @@ pub trait HirDatabase: DefDatabase + Upcast { assoc_name: Option, ) -> GenericPredicates; - #[salsa::invoke(crate::lower::generic_predicates_query)] + #[ra_salsa::invoke(crate::lower::generic_predicates_query)] fn generic_predicates(&self, def: GenericDefId) -> GenericPredicates; - #[salsa::invoke(crate::lower::generic_predicates_without_parent_query)] + #[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_query)] fn generic_predicates_without_parent(&self, def: GenericDefId) -> GenericPredicates; - #[salsa::invoke(crate::lower::trait_environment_for_body_query)] - #[salsa::transparent] + #[ra_salsa::invoke(crate::lower::trait_environment_for_body_query)] + #[ra_salsa::transparent] fn trait_environment_for_body(&self, def: DefWithBodyId) -> Arc; - #[salsa::invoke(crate::lower::trait_environment_query)] + #[ra_salsa::invoke(crate::lower::trait_environment_query)] fn trait_environment(&self, def: GenericDefId) -> Arc; - #[salsa::invoke(crate::lower::generic_defaults_query)] - #[salsa::cycle(crate::lower::generic_defaults_recover)] + #[ra_salsa::invoke(crate::lower::generic_defaults_query)] + #[ra_salsa::cycle(crate::lower::generic_defaults_recover)] fn generic_defaults(&self, def: GenericDefId) -> GenericDefaults; - #[salsa::invoke(InherentImpls::inherent_impls_in_crate_query)] + #[ra_salsa::invoke(InherentImpls::inherent_impls_in_crate_query)] fn inherent_impls_in_crate(&self, krate: CrateId) -> Arc; - #[salsa::invoke(InherentImpls::inherent_impls_in_block_query)] + #[ra_salsa::invoke(InherentImpls::inherent_impls_in_block_query)] fn inherent_impls_in_block(&self, block: BlockId) -> Option>; /// Collects all crates in the dependency graph that have impls for the /// given fingerprint. This is only used for primitive types and types /// annotated with `rustc_has_incoherent_inherent_impls`; for other types /// we just look at the crate where the type is defined. - #[salsa::invoke(crate::method_resolution::incoherent_inherent_impl_crates)] + #[ra_salsa::invoke(crate::method_resolution::incoherent_inherent_impl_crates)] fn incoherent_inherent_impl_crates( &self, krate: CrateId, fp: TyFingerprint, ) -> SmallVec<[CrateId; 2]>; - #[salsa::invoke(TraitImpls::trait_impls_in_crate_query)] + #[ra_salsa::invoke(TraitImpls::trait_impls_in_crate_query)] fn trait_impls_in_crate(&self, krate: CrateId) -> Arc; - #[salsa::invoke(TraitImpls::trait_impls_in_block_query)] + #[ra_salsa::invoke(TraitImpls::trait_impls_in_block_query)] fn trait_impls_in_block(&self, block: BlockId) -> Option>; - #[salsa::invoke(TraitImpls::trait_impls_in_deps_query)] + #[ra_salsa::invoke(TraitImpls::trait_impls_in_deps_query)] fn trait_impls_in_deps(&self, krate: CrateId) -> Arc<[Arc]>; // Interned IDs for Chalk integration - #[salsa::interned] + #[ra_salsa::interned] fn intern_callable_def(&self, callable_def: CallableDefId) -> InternedCallableDefId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_type_or_const_param_id( &self, param_id: TypeOrConstParamId, ) -> InternedTypeOrConstParamId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_lifetime_param_id(&self, param_id: LifetimeParamId) -> InternedLifetimeParamId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_impl_trait_id(&self, id: ImplTraitId) -> InternedOpaqueTyId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_closure(&self, id: InternedClosure) -> InternedClosureId; - #[salsa::interned] + #[ra_salsa::interned] fn intern_coroutine(&self, id: InternedCoroutine) -> InternedCoroutineId; - #[salsa::invoke(chalk_db::associated_ty_data_query)] + #[ra_salsa::invoke(chalk_db::associated_ty_data_query)] fn associated_ty_data( &self, id: chalk_db::AssocTypeId, ) -> sync::Arc; - #[salsa::invoke(chalk_db::trait_datum_query)] + #[ra_salsa::invoke(chalk_db::trait_datum_query)] fn trait_datum( &self, krate: CrateId, trait_id: chalk_db::TraitId, ) -> sync::Arc; - #[salsa::invoke(chalk_db::adt_datum_query)] + #[ra_salsa::invoke(chalk_db::adt_datum_query)] fn adt_datum( &self, krate: CrateId, struct_id: chalk_db::AdtId, ) -> sync::Arc; - #[salsa::invoke(chalk_db::impl_datum_query)] + #[ra_salsa::invoke(chalk_db::impl_datum_query)] fn impl_datum( &self, krate: CrateId, impl_id: chalk_db::ImplId, ) -> sync::Arc; - #[salsa::invoke(chalk_db::fn_def_datum_query)] + #[ra_salsa::invoke(chalk_db::fn_def_datum_query)] fn fn_def_datum(&self, fn_def_id: FnDefId) -> sync::Arc; - #[salsa::invoke(chalk_db::fn_def_variance_query)] + #[ra_salsa::invoke(chalk_db::fn_def_variance_query)] fn fn_def_variance(&self, fn_def_id: FnDefId) -> chalk_db::Variances; - #[salsa::invoke(chalk_db::adt_variance_query)] + #[ra_salsa::invoke(chalk_db::adt_variance_query)] fn adt_variance(&self, adt_id: chalk_db::AdtId) -> chalk_db::Variances; - #[salsa::invoke(chalk_db::associated_ty_value_query)] + #[ra_salsa::invoke(chalk_db::associated_ty_value_query)] fn associated_ty_value( &self, krate: CrateId, id: chalk_db::AssociatedTyValueId, ) -> sync::Arc; - #[salsa::invoke(crate::traits::normalize_projection_query)] - #[salsa::transparent] + #[ra_salsa::invoke(crate::traits::normalize_projection_query)] + #[ra_salsa::transparent] fn normalize_projection( &self, projection: crate::ProjectionTy, env: Arc, ) -> Ty; - #[salsa::invoke(crate::traits::trait_solve_query)] + #[ra_salsa::invoke(crate::traits::trait_solve_query)] fn trait_solve( &self, krate: CrateId, @@ -270,7 +270,7 @@ pub trait HirDatabase: DefDatabase + Upcast { goal: crate::Canonical>, ) -> Option; - #[salsa::invoke(chalk_db::program_clauses_for_chalk_env_query)] + #[ra_salsa::invoke(chalk_db::program_clauses_for_chalk_env_query)] fn program_clauses_for_chalk_env( &self, krate: CrateId, @@ -285,23 +285,23 @@ fn hir_database_is_dyn_compatible() { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct InternedTypeOrConstParamId(salsa::InternId); +pub struct InternedTypeOrConstParamId(ra_salsa::InternId); impl_intern_key!(InternedTypeOrConstParamId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct InternedLifetimeParamId(salsa::InternId); +pub struct InternedLifetimeParamId(ra_salsa::InternId); impl_intern_key!(InternedLifetimeParamId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct InternedConstParamId(salsa::InternId); +pub struct InternedConstParamId(ra_salsa::InternId); impl_intern_key!(InternedConstParamId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct InternedOpaqueTyId(salsa::InternId); +pub struct InternedOpaqueTyId(ra_salsa::InternId); impl_intern_key!(InternedOpaqueTyId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct InternedClosureId(salsa::InternId); +pub struct InternedClosureId(ra_salsa::InternId); impl_intern_key!(InternedClosureId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -310,7 +310,7 @@ pub struct InternedClosure(pub DefWithBodyId, pub ExprId); impl InternValueTrivial for InternedClosure {} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct InternedCoroutineId(salsa::InternId); +pub struct InternedCoroutineId(ra_salsa::InternId); impl_intern_key!(InternedCoroutineId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -320,5 +320,5 @@ impl InternValueTrivial for InternedCoroutine {} /// This exists just for Chalk, because Chalk just has a single `FnDefId` where /// we have different IDs for struct and enum variant constructors. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct InternedCallableDefId(salsa::InternId); +pub struct InternedCallableDefId(ra_salsa::InternId); impl_intern_key!(InternedCallableDefId); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs index 7f994783c11..3dbefc5cec8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs @@ -8,7 +8,7 @@ use crate::{ ProgramClauseData, ProgramClauses, ProjectionTy, QuantifiedWhereClause, QuantifiedWhereClauses, Substitution, Ty, TyData, TyKind, VariableKind, VariableKinds, }; -use base_db::salsa::InternId; +use base_db::ra_salsa::InternId; use chalk_ir::{ProgramClauseImplication, SeparatorTraitRef, Variance}; use hir_def::TypeAliasId; use intern::{impl_internable, Interned}; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs index 4cdc0db46a1..bfbae2941da 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs @@ -2,7 +2,7 @@ use std::fmt; -use base_db::salsa::Cycle; +use base_db::ra_salsa::Cycle; use chalk_ir::{AdtId, FloatTy, IntTy, TyKind, UintTy}; use hir_def::{ layout::{ diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs index a060ebfe6be..c1a67fcc407 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs @@ -2,7 +2,7 @@ use std::{cmp, ops::Bound}; -use base_db::salsa::Cycle; +use base_db::ra_salsa::Cycle; use hir_def::{ data::adt::VariantData, layout::{Integer, ReprOptions, TargetDataLayout}, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index ef570a20556..649cf88bb8d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -56,7 +56,7 @@ use std::{ hash::{BuildHasherDefault, Hash}, }; -use base_db::salsa::InternValueTrivial; +use base_db::ra_salsa::InternValueTrivial; use chalk_ir::{ fold::{Shift, TypeFoldable}, interner::HasInterner, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index c6c2108e34a..c7ed68448bb 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -11,7 +11,7 @@ use std::{ ops::{self, Not as _}, }; -use base_db::{salsa::Cycle, CrateId}; +use base_db::{ra_salsa::Cycle, CrateId}; use chalk_ir::{ cast::Cast, fold::{Shift, TypeFoldable}, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mapping.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mapping.rs index c61d8277142..2f38e8fa14c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mapping.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mapping.rs @@ -5,7 +5,7 @@ use chalk_solve::rust_ir; -use base_db::salsa::{self, InternKey}; +use base_db::ra_salsa::{self, InternKey}; use hir_def::{LifetimeParamId, TraitId, TypeAliasId, TypeOrConstParamId}; use crate::{ @@ -116,24 +116,24 @@ impl From for chalk_ir::CoroutineId { } pub fn to_foreign_def_id(id: TypeAliasId) -> ForeignDefId { - chalk_ir::ForeignDefId(salsa::InternKey::as_intern_id(&id)) + chalk_ir::ForeignDefId(ra_salsa::InternKey::as_intern_id(&id)) } pub fn from_foreign_def_id(id: ForeignDefId) -> TypeAliasId { - salsa::InternKey::from_intern_id(id.0) + ra_salsa::InternKey::from_intern_id(id.0) } pub fn to_assoc_type_id(id: TypeAliasId) -> AssocTypeId { - chalk_ir::AssocTypeId(salsa::InternKey::as_intern_id(&id)) + chalk_ir::AssocTypeId(ra_salsa::InternKey::as_intern_id(&id)) } pub fn from_assoc_type_id(id: AssocTypeId) -> TypeAliasId { - salsa::InternKey::from_intern_id(id.0) + ra_salsa::InternKey::from_intern_id(id.0) } pub fn from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> TypeOrConstParamId { assert_eq!(idx.ui, chalk_ir::UniverseIndex::ROOT); - let interned_id = salsa::InternKey::from_intern_id(salsa::InternId::from(idx.idx)); + let interned_id = ra_salsa::InternKey::from_intern_id(ra_salsa::InternId::from(idx.idx)); db.lookup_intern_type_or_const_param_id(interned_id) } @@ -141,13 +141,13 @@ pub fn to_placeholder_idx(db: &dyn HirDatabase, id: TypeOrConstParamId) -> Place let interned_id = db.intern_type_or_const_param_id(id); PlaceholderIndex { ui: chalk_ir::UniverseIndex::ROOT, - idx: salsa::InternKey::as_intern_id(&interned_id).as_usize(), + idx: ra_salsa::InternKey::as_intern_id(&interned_id).as_usize(), } } pub fn lt_from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> LifetimeParamId { assert_eq!(idx.ui, chalk_ir::UniverseIndex::ROOT); - let interned_id = salsa::InternKey::from_intern_id(salsa::InternId::from(idx.idx)); + let interned_id = ra_salsa::InternKey::from_intern_id(ra_salsa::InternId::from(idx.idx)); db.lookup_intern_lifetime_param_id(interned_id) } @@ -155,14 +155,14 @@ pub fn lt_to_placeholder_idx(db: &dyn HirDatabase, id: LifetimeParamId) -> Place let interned_id = db.intern_lifetime_param_id(id); PlaceholderIndex { ui: chalk_ir::UniverseIndex::ROOT, - idx: salsa::InternKey::as_intern_id(&interned_id).as_usize(), + idx: ra_salsa::InternKey::as_intern_id(&interned_id).as_usize(), } } pub fn to_chalk_trait_id(id: TraitId) -> ChalkTraitId { - chalk_ir::TraitId(salsa::InternKey::as_intern_id(&id)) + chalk_ir::TraitId(ra_salsa::InternKey::as_intern_id(&id)) } pub fn from_chalk_trait_id(id: ChalkTraitId) -> TraitId { - salsa::InternKey::from_intern_id(id.0) + ra_salsa::InternKey::from_intern_id(id.0) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index a2cb122c543..16994cdd0c6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -2,7 +2,7 @@ use std::{fmt::Write, iter, mem}; -use base_db::salsa::Cycle; +use base_db::ra_salsa::Cycle; use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind}; use hir_def::{ body::Body, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs index 8f6582b7f80..4c6bc376e2b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs @@ -9,7 +9,7 @@ use std::mem; -use base_db::salsa::Cycle; +use base_db::ra_salsa::Cycle; use chalk_ir::{ fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable}, ConstData, DebruijnIndex, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs index 0efb9c52fb6..f37dd91d8e9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs @@ -3,7 +3,7 @@ use std::{fmt, panic, sync::Mutex}; use base_db::{ - salsa::{self, Durability}, + ra_salsa::{self, Durability}, AnchoredPath, CrateId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast, }; use hir_def::{db::DefDatabase, ModuleId}; @@ -14,7 +14,7 @@ use syntax::TextRange; use test_utils::extract_annotations; use triomphe::Arc; -#[salsa::database( +#[ra_salsa::database( base_db::SourceRootDatabaseStorage, base_db::SourceDatabaseStorage, hir_expand::db::ExpandDatabaseStorage, @@ -23,8 +23,8 @@ use triomphe::Arc; crate::db::HirDatabaseStorage )] pub(crate) struct TestDB { - storage: salsa::Storage, - events: Mutex>>, + storage: ra_salsa::Storage, + events: Mutex>>, } impl Default for TestDB { @@ -54,8 +54,8 @@ impl Upcast for TestDB { } } -impl salsa::Database for TestDB { - fn salsa_event(&self, event: salsa::Event) { +impl ra_salsa::Database for TestDB { + fn salsa_event(&self, event: ra_salsa::Event) { let mut events = self.events.lock().unwrap(); if let Some(events) = &mut *events { events.push(event); @@ -63,9 +63,9 @@ impl salsa::Database for TestDB { } } -impl salsa::ParallelDatabase for TestDB { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(TestDB { +impl ra_salsa::ParallelDatabase for TestDB { + fn snapshot(&self) -> ra_salsa::Snapshot { + ra_salsa::Snapshot::new(TestDB { storage: self.storage.snapshot(), events: Default::default(), }) @@ -128,7 +128,7 @@ impl TestDB { } impl TestDB { - pub(crate) fn log(&self, f: impl FnOnce()) -> Vec { + pub(crate) fn log(&self, f: impl FnOnce()) -> Vec { *self.events.lock().unwrap() = Some(Vec::new()); f(); self.events.lock().unwrap().take().unwrap() @@ -141,7 +141,7 @@ impl TestDB { .filter_map(|e| match e.kind { // This is pretty horrible, but `Debug` is the only way to inspect // QueryDescriptor at the moment. - salsa::EventKind::WillExecute { database_key } => { + ra_salsa::EventKind::WillExecute { database_key } => { Some(format!("{:?}", database_key.debug(self))) } _ => None, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs index 22cef3505bf..b63d632dd26 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs @@ -1,4 +1,4 @@ -use base_db::salsa::InternKey; +use base_db::ra_salsa::InternKey; use expect_test::{expect, Expect}; use hir_def::db::DefDatabase; use hir_expand::files::InFileWrapper; diff --git a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs index 74c8fc96d4a..7474d7bc54d 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs @@ -1,7 +1,7 @@ //! Applies changes to the IDE state transactionally. use base_db::{ - salsa::{ + ra_salsa::{ debug::{DebugQueryTable, TableEntry}, Database, Durability, Query, QueryTable, }, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs index a45ff9a9545..aed093f0ebf 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs @@ -48,7 +48,7 @@ pub use hir::ChangeWithProcMacros; use std::{fmt, mem::ManuallyDrop}; use base_db::{ - salsa::{self, Durability}, + ra_salsa::{self, Durability}, AnchoredPath, CrateId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast, DEFAULT_FILE_TEXT_LRU_CAP, }; @@ -74,7 +74,7 @@ pub type FxIndexMap = pub type FilePosition = FilePositionWrapper; pub type FileRange = FileRangeWrapper; -#[salsa::database( +#[ra_salsa::database( base_db::SourceRootDatabaseStorage, base_db::SourceDatabaseStorage, hir::db::ExpandDatabaseStorage, @@ -89,7 +89,7 @@ pub struct RootDatabase { // `&RootDatabase -> &dyn OtherDatabase` cast will instantiate its drop glue in the vtable, // which duplicates `Weak::drop` and `Arc::drop` tens of thousands of times, which makes // compile times of all `ide_*` and downstream crates suffer greatly. - storage: ManuallyDrop>, + storage: ManuallyDrop>, } impl Drop for RootDatabase { @@ -134,7 +134,7 @@ impl FileLoader for RootDatabase { } } -impl salsa::Database for RootDatabase {} +impl ra_salsa::Database for RootDatabase {} impl Default for RootDatabase { fn default() -> RootDatabase { @@ -144,7 +144,7 @@ impl Default for RootDatabase { impl RootDatabase { pub fn new(lru_capacity: Option) -> RootDatabase { - let mut db = RootDatabase { storage: ManuallyDrop::new(salsa::Storage::default()) }; + let mut db = RootDatabase { storage: ManuallyDrop::new(ra_salsa::Storage::default()) }; db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); db.set_proc_macros_with_durability(Default::default(), Durability::HIGH); db.set_local_roots_with_durability(Default::default(), Durability::HIGH); @@ -195,13 +195,15 @@ impl RootDatabase { } } -impl salsa::ParallelDatabase for RootDatabase { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(RootDatabase { storage: ManuallyDrop::new(self.storage.snapshot()) }) +impl ra_salsa::ParallelDatabase for RootDatabase { + fn snapshot(&self) -> ra_salsa::Snapshot { + ra_salsa::Snapshot::new(RootDatabase { + storage: ManuallyDrop::new(self.storage.snapshot()), + }) } } -#[salsa::query_group(LineIndexDatabaseStorage)] +#[ra_salsa::query_group(LineIndexDatabaseStorage)] pub trait LineIndexDatabase: base_db::SourceDatabase { fn line_index(&self, file_id: FileId) -> Arc; } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs b/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs index 19d8a15422e..0002fda0ba7 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs @@ -10,7 +10,7 @@ use hir::db::DefDatabase; use crate::{ base_db::{ - salsa::{Database, ParallelDatabase, Snapshot}, + ra_salsa::{Database, ParallelDatabase, Snapshot}, Cancelled, CrateId, SourceDatabase, SourceRootDatabase, }, symbol_index::SymbolsDatabase, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index 852ee595be4..c5215eb3e63 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -7,7 +7,7 @@ use std::mem; use std::{cell::LazyCell, cmp::Reverse}; -use base_db::{salsa::Database, SourceDatabase, SourceRootDatabase}; +use base_db::{ra_salsa::Database, SourceDatabase, SourceRootDatabase}; use either::Either; use hir::{ sym, Adt, AsAssocItem, DefWithBody, FileRange, FileRangeWrapper, HasAttrs, HasContainer, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs index 209b1477bac..94d354d28e5 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs @@ -28,7 +28,7 @@ use std::{ }; use base_db::{ - salsa::{self, ParallelDatabase}, + ra_salsa::{self, ParallelDatabase}, SourceRootDatabase, SourceRootId, Upcast, }; use fst::{raw::IndexedValue, Automaton, Streamer}; @@ -99,7 +99,7 @@ impl Query { } } -#[salsa::query_group(SymbolsDatabaseStorage)] +#[ra_salsa::query_group(SymbolsDatabaseStorage)] pub trait SymbolsDatabase: HirDatabase + SourceRootDatabase + Upcast { /// The symbol index for a given module. These modules should only be in source roots that /// are inside local_roots. @@ -108,18 +108,18 @@ pub trait SymbolsDatabase: HirDatabase + SourceRootDatabase + Upcast Arc; - #[salsa::transparent] + #[ra_salsa::transparent] /// The symbol indices of modules that make up a given crate. fn crate_symbols(&self, krate: Crate) -> Box<[Arc]>; /// The set of "local" (that is, from the current workspace) roots. /// Files in local roots are assumed to change frequently. - #[salsa::input] + #[ra_salsa::input] fn local_roots(&self) -> Arc>; /// The set of roots for crates.io libraries. /// Files in libraries are assumed to never change. - #[salsa::input] + #[ra_salsa::input] fn library_roots(&self) -> Arc>; } @@ -155,13 +155,13 @@ pub fn crate_symbols(db: &dyn SymbolsDatabase, krate: Crate) -> Box<[Arc(DB); -impl Snap> { +impl Snap> { fn new(db: &DB) -> Self { Self(db.snapshot()) } } -impl Clone for Snap> { - fn clone(&self) -> Snap> { +impl Clone for Snap> { + fn clone(&self) -> Snap> { Snap(self.0.snapshot()) } } diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs index 42930889d75..d783e195252 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs @@ -1,7 +1,7 @@ use expect_test::{expect, Expect}; use hir::{FilePosition, FileRange}; use ide_db::{ - base_db::{salsa::Durability, SourceDatabase}, + base_db::{ra_salsa::Durability, SourceDatabase}, EditionedFileId, FxHashSet, }; use test_utils::RangeOrOffset; diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 3a8f7bb7a1a..d7163d57d22 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -64,7 +64,7 @@ use fetch_crates::CrateInfo; use hir::{sym, ChangeWithProcMacros}; use ide_db::{ base_db::{ - salsa::{self, ParallelDatabase}, + ra_salsa::{self, ParallelDatabase}, CrateOrigin, CrateWorkspaceData, Env, FileLoader, FileSet, SourceDatabase, SourceRootDatabase, VfsPath, }, @@ -218,7 +218,7 @@ impl Default for AnalysisHost { /// `Analysis` are canceled (most method return `Err(Canceled)`). #[derive(Debug)] pub struct Analysis { - db: salsa::Snapshot, + db: ra_salsa::Snapshot, } // As a general design guideline, `Analysis` API are intended to be independent diff --git a/src/tools/rust-analyzer/crates/ide/src/ssr.rs b/src/tools/rust-analyzer/crates/ide/src/ssr.rs index 41cc9c067d3..6def28e0b74 100644 --- a/src/tools/rust-analyzer/crates/ide/src/ssr.rs +++ b/src/tools/rust-analyzer/crates/ide/src/ssr.rs @@ -59,7 +59,7 @@ mod tests { use expect_test::expect; use ide_assists::{Assist, AssistResolveStrategy}; use ide_db::{ - base_db::salsa::Durability, symbol_index::SymbolsDatabase, FileRange, FxHashSet, + base_db::ra_salsa::Durability, symbol_index::SymbolsDatabase, FileRange, FxHashSet, RootDatabase, }; use test_fixture::WithFixture; diff --git a/src/tools/rust-analyzer/crates/ide/src/status.rs b/src/tools/rust-analyzer/crates/ide/src/status.rs index 67d6932da96..9e823daa2be 100644 --- a/src/tools/rust-analyzer/crates/ide/src/status.rs +++ b/src/tools/rust-analyzer/crates/ide/src/status.rs @@ -6,7 +6,7 @@ use hir::{ }; use ide_db::{ base_db::{ - salsa::{ + ra_salsa::{ debug::{DebugQueryTable, TableEntry}, Query, QueryTable, }, diff --git a/src/tools/rust-analyzer/crates/ra-salsa/Cargo.toml b/src/tools/rust-analyzer/crates/ra-salsa/Cargo.toml new file mode 100644 index 00000000000..b81e780337f --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "salsa" +version = "0.0.0" +authors = ["Salsa developers"] +edition = "2021" +license = "Apache-2.0 OR MIT" +repository = "https://github.com/salsa-rs/salsa" +description = "A generic framework for on-demand, incrementalized computation (experimental)" + +rust-version.workspace = true + +[lib] +name = "ra_salsa" + +[dependencies] +indexmap = "2.1.0" +lock_api = "0.4" +tracing = "0.1" +parking_lot = "0.12.1" +rustc-hash = "1.0" +smallvec = "1.0.0" +oorandom = "11" +triomphe = "0.1.11" +itertools.workspace = true + +ra-salsa-macros = { version = "0.0.0", path = "ra-salsa-macros", package = "salsa-macros" } + +[dev-dependencies] +linked-hash-map = "0.5.6" +rand = "0.8.5" +expect-test = "1.4.0" +dissimilar = "1.0.7" + +[lints] +workspace = true diff --git a/src/tools/rust-analyzer/crates/ra-salsa/FAQ.md b/src/tools/rust-analyzer/crates/ra-salsa/FAQ.md new file mode 100644 index 00000000000..9c9f6f92da9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/FAQ.md @@ -0,0 +1,34 @@ +# Frequently asked questions + +## Why is it called salsa? + +I like salsa! Don't you?! Well, ok, there's a bit more to it. The +underlying algorithm for figuring out which bits of code need to be +re-executed after any given change is based on the algorithm used in +rustc. Michael Woerister and I first described the rustc algorithm in +terms of two colors, red and green, and hence we called it the +"red-green algorithm". This made me think of the New Mexico State +Question --- ["Red or green?"][nm] --- which refers to chile +(salsa). Although this version no longer uses colors (we borrowed +revision counters from Glimmer, instead), I still like the name. + +[nm]: https://www.sos.state.nm.us/about-new-mexico/state-question/ + +## What is the relationship between salsa and an Entity-Component System (ECS)? + +You may have noticed that Salsa "feels" a lot like an ECS in some +ways. That's true -- Salsa's queries are a bit like *components* (and +the keys to the queries are a bit like *entities*). But there is one +big difference: **ECS is -- at its heart -- a mutable system**. You +can get or set a component of some entity whenever you like. In +contrast, salsa's queries **define "derived values" via pure +computations**. + +Partly as a consequence, ECS doesn't handle incremental updates for +you. When you update some component of some entity, you have to ensure +that other entities' components are updated appropriately. + +Finally, ECS offers interesting metadata and "aspect-like" facilities, +such as iterating over all entities that share certain components. +Salsa has no analogue to that. + diff --git a/src/tools/rust-analyzer/crates/ra-salsa/LICENSE-APACHE b/src/tools/rust-analyzer/crates/ra-salsa/LICENSE-APACHE new file mode 100644 index 00000000000..1b5ec8b78e2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/src/tools/rust-analyzer/crates/ra-salsa/LICENSE-MIT b/src/tools/rust-analyzer/crates/ra-salsa/LICENSE-MIT new file mode 100644 index 00000000000..31aa79387f2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/tools/rust-analyzer/crates/ra-salsa/README.md b/src/tools/rust-analyzer/crates/ra-salsa/README.md new file mode 100644 index 00000000000..4a8d9f8c731 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/README.md @@ -0,0 +1,42 @@ +# salsa + +*A generic framework for on-demand, incrementalized computation.* + +## Obligatory warning + +This is a fork of https://github.com/salsa-rs/salsa/ adjusted to rust-analyzer's needs. + +## Credits + +This system is heavily inspired by [adapton](http://adapton.org/), [glimmer](https://github.com/glimmerjs/glimmer-vm), and rustc's query +system. So credit goes to Eduard-Mihai Burtescu, Matthew Hammer, +Yehuda Katz, and Michael Woerister. + +## Key idea + +The key idea of `salsa` is that you define your program as a set of +**queries**. Every query is used like function `K -> V` that maps from +some key of type `K` to a value of type `V`. Queries come in two basic +varieties: + +- **Inputs**: the base inputs to your system. You can change these + whenever you like. +- **Functions**: pure functions (no side effects) that transform your + inputs into other values. The results of queries is memoized to + avoid recomputing them a lot. When you make changes to the inputs, + we'll figure out (fairly intelligently) when we can re-use these + memoized values and when we have to recompute them. + +## Want to learn more? + +To learn more about Salsa, try one of the following: + +- read the [heavily commented `hello_world` example](https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs); +- check out the [Salsa book](https://salsa-rs.github.io/salsa); +- watch one of our [videos](https://salsa-rs.github.io/salsa/videos.html). + +## Getting in touch + +The bulk of the discussion happens in the [issues](https://github.com/salsa-rs/salsa/issues) +and [pull requests](https://github.com/salsa-rs/salsa/pulls), +but we have a [zulip chat](https://salsa.zulipchat.com/) as well. diff --git a/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/Cargo.toml b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/Cargo.toml new file mode 100644 index 00000000000..5613d75c752 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "salsa-macros" +version = "0.0.0" +authors = ["Salsa developers"] +edition = "2021" +license = "Apache-2.0 OR MIT" +repository = "https://github.com/salsa-rs/salsa" +description = "Procedural macros for the salsa crate" + +rust-version.workspace = true + +[lib] +proc-macro = true +name = "ra_salsa_macros" + +[dependencies] +heck = "0.4" +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits"] } + +[lints] +workspace = true diff --git a/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/LICENSE-APACHE b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/LICENSE-APACHE new file mode 100644 index 00000000000..0bf2cad6488 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE diff --git a/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/LICENSE-MIT b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/LICENSE-MIT new file mode 100644 index 00000000000..d99cce5f720 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT diff --git a/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/README.md b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/README.md new file mode 100644 index 00000000000..94389aee61a --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/README.md @@ -0,0 +1 @@ +../README.md diff --git a/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/database_storage.rs b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/database_storage.rs new file mode 100644 index 00000000000..63ab84a621e --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/database_storage.rs @@ -0,0 +1,243 @@ +//! Implementation for `[ra_salsa::database]` decorator. + +use heck::ToSnakeCase; +use proc_macro::TokenStream; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{Ident, ItemStruct, Path, Token}; + +type PunctuatedQueryGroups = Punctuated; + +pub(crate) fn database(args: TokenStream, input: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as QueryGroupList); + let input = syn::parse_macro_input!(input as ItemStruct); + + let query_groups = &args.query_groups; + let database_name = &input.ident; + let visibility = &input.vis; + let db_storage_field = quote! { storage }; + + let mut output = proc_macro2::TokenStream::new(); + output.extend(quote! { #input }); + + let query_group_names_snake: Vec<_> = query_groups + .iter() + .map(|query_group| { + let group_name = query_group.name(); + Ident::new(&group_name.to_string().to_snake_case(), group_name.span()) + }) + .collect(); + + let query_group_storage_names: Vec<_> = query_groups + .iter() + .map(|QueryGroup { group_path }| { + quote! { + <#group_path as ra_salsa::plumbing::QueryGroup>::GroupStorage + } + }) + .collect(); + + // For each query group `foo::MyGroup` create a link to its + // `foo::MyGroupGroupStorage` + let mut storage_fields = proc_macro2::TokenStream::new(); + let mut storage_initializers = proc_macro2::TokenStream::new(); + let mut has_group_impls = proc_macro2::TokenStream::new(); + for (((query_group, group_name_snake), group_storage), group_index) in query_groups + .iter() + .zip(&query_group_names_snake) + .zip(&query_group_storage_names) + .zip(0_u16..) + { + let group_path = &query_group.group_path; + + // rewrite the last identifier (`MyGroup`, above) to + // (e.g.) `MyGroupGroupStorage`. + storage_fields.extend(quote! { + #group_name_snake: #group_storage, + }); + + // rewrite the last identifier (`MyGroup`, above) to + // (e.g.) `MyGroupGroupStorage`. + storage_initializers.extend(quote! { + #group_name_snake: #group_storage::new(#group_index), + }); + + // ANCHOR:HasQueryGroup + has_group_impls.extend(quote! { + impl ra_salsa::plumbing::HasQueryGroup<#group_path> for #database_name { + fn group_storage(&self) -> &#group_storage { + &self.#db_storage_field.query_store().#group_name_snake + } + + fn group_storage_mut(&mut self) -> (&#group_storage, &mut ra_salsa::Runtime) { + let (query_store_mut, runtime) = self.#db_storage_field.query_store_mut(); + (&query_store_mut.#group_name_snake, runtime) + } + } + }); + // ANCHOR_END:HasQueryGroup + } + + // create group storage wrapper struct + output.extend(quote! { + #[doc(hidden)] + #visibility struct __SalsaDatabaseStorage { + #storage_fields + } + + impl Default for __SalsaDatabaseStorage { + fn default() -> Self { + Self { + #storage_initializers + } + } + } + }); + + // Create a tuple (D1, D2, ...) where Di is the data for a given query group. + let mut database_data = vec![]; + for QueryGroup { group_path } in query_groups { + database_data.push(quote! { + <#group_path as ra_salsa::plumbing::QueryGroup>::GroupData + }); + } + + // ANCHOR:DatabaseStorageTypes + output.extend(quote! { + impl ra_salsa::plumbing::DatabaseStorageTypes for #database_name { + type DatabaseStorage = __SalsaDatabaseStorage; + } + }); + // ANCHOR_END:DatabaseStorageTypes + + // ANCHOR:DatabaseOps + let mut fmt_ops = proc_macro2::TokenStream::new(); + let mut maybe_changed_ops = proc_macro2::TokenStream::new(); + let mut cycle_recovery_strategy_ops = proc_macro2::TokenStream::new(); + let mut for_each_ops = proc_macro2::TokenStream::new(); + for ((QueryGroup { group_path }, group_storage), group_index) in + query_groups.iter().zip(&query_group_storage_names).zip(0_u16..) + { + fmt_ops.extend(quote! { + #group_index => { + let storage: &#group_storage = + >::group_storage(self); + storage.fmt_index(self, input, fmt) + } + }); + maybe_changed_ops.extend(quote! { + #group_index => { + let storage: &#group_storage = + >::group_storage(self); + storage.maybe_changed_after(self, input, revision) + } + }); + cycle_recovery_strategy_ops.extend(quote! { + #group_index => { + let storage: &#group_storage = + >::group_storage(self); + storage.cycle_recovery_strategy(self, input) + } + }); + for_each_ops.extend(quote! { + let storage: &#group_storage = + >::group_storage(self); + storage.for_each_query(runtime, &mut op); + }); + } + output.extend(quote! { + impl ra_salsa::plumbing::DatabaseOps for #database_name { + fn ops_database(&self) -> &dyn ra_salsa::Database { + self + } + + fn ops_salsa_runtime(&self) -> &ra_salsa::Runtime { + self.#db_storage_field.salsa_runtime() + } + + fn synthetic_write(&mut self, durability: ra_salsa::Durability) { + self.#db_storage_field.salsa_runtime_mut().synthetic_write(durability) + } + + fn fmt_index( + &self, + input: ra_salsa::DatabaseKeyIndex, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + match input.group_index() { + #fmt_ops + i => panic!("ra_salsa: invalid group index {}", i) + } + } + + fn maybe_changed_after( + &self, + input: ra_salsa::DatabaseKeyIndex, + revision: ra_salsa::Revision + ) -> bool { + match input.group_index() { + #maybe_changed_ops + i => panic!("ra_salsa: invalid group index {}", i) + } + } + + fn cycle_recovery_strategy( + &self, + input: ra_salsa::DatabaseKeyIndex, + ) -> ra_salsa::plumbing::CycleRecoveryStrategy { + match input.group_index() { + #cycle_recovery_strategy_ops + i => panic!("ra_salsa: invalid group index {}", i) + } + } + + fn for_each_query( + &self, + mut op: &mut dyn FnMut(&dyn ra_salsa::plumbing::QueryStorageMassOps), + ) { + let runtime = ra_salsa::Database::salsa_runtime(self); + #for_each_ops + } + } + }); + // ANCHOR_END:DatabaseOps + + output.extend(has_group_impls); + + output.into() +} + +#[derive(Clone, Debug)] +struct QueryGroupList { + query_groups: PunctuatedQueryGroups, +} + +impl Parse for QueryGroupList { + fn parse(input: ParseStream<'_>) -> syn::Result { + let query_groups: PunctuatedQueryGroups = + input.parse_terminated(QueryGroup::parse, Token![,])?; + Ok(QueryGroupList { query_groups }) + } +} + +#[derive(Clone, Debug)] +struct QueryGroup { + group_path: Path, +} + +impl QueryGroup { + /// The name of the query group trait. + fn name(&self) -> Ident { + self.group_path.segments.last().unwrap().ident.clone() + } +} + +impl Parse for QueryGroup { + /// ```ignore + /// impl HelloWorldDatabase; + /// ``` + fn parse(input: ParseStream<'_>) -> syn::Result { + let group_path: Path = input.parse()?; + Ok(QueryGroup { group_path }) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/lib.rs b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/lib.rs new file mode 100644 index 00000000000..d3e17c5ebf1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/lib.rs @@ -0,0 +1,125 @@ +//! This crate provides salsa's macros and attributes. + +#![recursion_limit = "256"] + +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; + +mod database_storage; +mod parenthesized; +mod query_group; + +/// The decorator that defines a salsa "query group" trait. This is a +/// trait that defines everything that a block of queries need to +/// execute, as well as defining the queries themselves that are +/// exported for others to use. +/// +/// This macro declares the "prototype" for a group of queries. It will +/// expand into a trait and a set of structs, one per query. +/// +/// For each query, you give the name of the accessor method to invoke +/// the query (e.g., `my_query`, below), as well as its parameter +/// types and the output type. You also give the name for a query type +/// (e.g., `MyQuery`, below) that represents the query, and optionally +/// other details, such as its storage. +/// +/// # Examples +/// +/// The simplest example is something like this: +/// +/// ```ignore +/// #[salsa::query_group] +/// trait TypeckDatabase { +/// #[salsa::input] // see below for other legal attributes +/// fn my_query(&self, input: u32) -> u64; +/// +/// /// Queries can have any number of inputs (including zero); if there +/// /// is not exactly one input, then the key type will be +/// /// a tuple of the input types, so in this case `(u32, f32)`. +/// fn other_query(&self, input1: u32, input2: f32) -> u64; +/// } +/// ``` +/// +/// Here is a list of legal `salsa::XXX` attributes: +/// +/// - Storage attributes: control how the query data is stored and set. These +/// are described in detail in the section below. +/// - `#[salsa::input]` +/// - `#[salsa::memoized]` +/// - `#[salsa::dependencies]` +/// - Query execution: +/// - `#[salsa::invoke(path::to::my_fn)]` -- for a non-input, this +/// indicates the function to call when a query must be +/// recomputed. The default is to call a function in the same +/// module with the same name as the query. +/// - `#[query_type(MyQueryTypeName)]` specifies the name of the +/// dummy struct created for the query. Default is the name of the +/// query, in camel case, plus the word "Query" (e.g., +/// `MyQueryQuery` and `OtherQueryQuery` in the examples above). +/// +/// # Storage attributes +/// +/// Here are the possible storage values for each query. The default +/// is `storage memoized`. +/// +/// ## Input queries +/// +/// Specifying `storage input` will give you an **input +/// query**. Unlike derived queries, whose value is given by a +/// function, input queries are explicitly set by doing +/// `db.query(QueryType).set(key, value)` (where `QueryType` is the +/// `type` specified for the query). Accessing a value that has not +/// yet been set will panic. Each time you invoke `set`, we assume the +/// value has changed, and so we will potentially re-execute derived +/// queries that read (transitively) from this input. +/// +/// ## Derived queries +/// +/// Derived queries are specified by a function. +/// +/// - `#[salsa::memoized]` (the default) -- The result is memoized +/// between calls. If the inputs have changed, we will recompute +/// the value, but then compare against the old memoized value, +/// which can significantly reduce the amount of recomputation +/// required in new revisions. This does require that the value +/// implements `Eq`. +/// - `#[salsa::dependencies]` -- does not cache the value, so it will +/// be recomputed every time it is needed. We do track the inputs, however, +/// so if they have not changed, then things that rely on this query +/// may be known not to have changed. +/// +/// ## Attribute combinations +/// +/// Some attributes are mutually exclusive. For example, it is an error to add +/// multiple storage specifiers or to annotate a function to `invoke` on an +/// `input` query. +#[proc_macro_attribute] +pub fn query_group(args: TokenStream, input: TokenStream) -> TokenStream { + query_group::query_group(args, input) +} + +/// This attribute is placed on your database struct. It takes a list of the +/// query groups that your database supports. The format looks like so: +/// +/// ```rust,ignore +/// #[salsa::database(MyQueryGroup1, MyQueryGroup2)] +/// struct MyDatabase { +/// runtime: salsa::Runtime, // <-- your database will need this field, too +/// } +/// ``` +/// +/// Here, the struct `MyDatabase` would support the two query groups +/// `MyQueryGroup1` and `MyQueryGroup2`. In addition to the `database` +/// attribute, the struct needs to have a `runtime` field (of type +/// [`salsa::Runtime`]) and to implement the `salsa::Database` trait. +/// +/// See [the `hello_world` example][hw] for more details. +/// +/// [`salsa::Runtime`]: struct.Runtime.html +/// [hw]: https://github.com/salsa-rs/salsa/tree/master/examples/hello_world +#[proc_macro_attribute] +pub fn database(args: TokenStream, input: TokenStream) -> TokenStream { + database_storage::database(args, input) +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/parenthesized.rs b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/parenthesized.rs new file mode 100644 index 00000000000..5ecd1b8a058 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/parenthesized.rs @@ -0,0 +1,13 @@ +//! Parenthesis helper +pub(crate) struct Parenthesized(pub(crate) T); + +impl syn::parse::Parse for Parenthesized +where + T: syn::parse::Parse, +{ + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let content; + syn::parenthesized!(content in input); + content.parse::().map(Parenthesized) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/query_group.rs b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/query_group.rs new file mode 100644 index 00000000000..88db6093ee0 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/ra-salsa-macros/src/query_group.rs @@ -0,0 +1,753 @@ +//! Implementation for `[ra_salsa::query_group]` decorator. + +use crate::parenthesized::Parenthesized; +use heck::ToUpperCamelCase; +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::ToTokens; +use syn::{ + parse_macro_input, parse_quote, spanned::Spanned, Attribute, Error, FnArg, Ident, ItemTrait, + ReturnType, TraitItem, Type, +}; + +/// Implementation for `[ra_salsa::query_group]` decorator. +pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream { + let group_struct = parse_macro_input!(args as Ident); + let input: ItemTrait = parse_macro_input!(input as ItemTrait); + // println!("args: {:#?}", args); + // println!("input: {:#?}", input); + + let input_span = input.span(); + let (trait_attrs, salsa_attrs) = filter_attrs(input.attrs); + if !salsa_attrs.is_empty() { + return Error::new(input_span, format!("unsupported attributes: {salsa_attrs:?}")) + .to_compile_error() + .into(); + } + + let trait_vis = input.vis; + let trait_name = input.ident; + let _generics = input.generics.clone(); + let dyn_db = quote! { dyn #trait_name }; + + // Decompose the trait into the corresponding queries. + let mut queries = vec![]; + for item in input.items { + if let TraitItem::Fn(method) = item { + let query_name = method.sig.ident.to_string(); + + let mut storage = QueryStorage::Memoized; + let mut cycle = None; + let mut invoke = None; + + let mut query_type = + format_ident!("{}Query", query_name.to_string().to_upper_camel_case()); + let mut num_storages = 0; + + // Extract attributes. + let (attrs, salsa_attrs) = filter_attrs(method.attrs); + for SalsaAttr { name, tts, span } in salsa_attrs { + match name.as_str() { + "memoized" => { + storage = QueryStorage::Memoized; + num_storages += 1; + } + "dependencies" => { + storage = QueryStorage::LruDependencies; + num_storages += 1; + } + "lru" => { + storage = QueryStorage::LruMemoized; + num_storages += 1; + } + "input" => { + storage = QueryStorage::Input; + num_storages += 1; + } + "interned" => { + storage = QueryStorage::Interned; + num_storages += 1; + } + "cycle" => { + cycle = Some(parse_macro_input!(tts as Parenthesized).0); + } + "invoke" => { + invoke = Some(parse_macro_input!(tts as Parenthesized).0); + } + "query_type" => { + query_type = parse_macro_input!(tts as Parenthesized).0; + } + "transparent" => { + storage = QueryStorage::Transparent; + num_storages += 1; + } + _ => { + return Error::new(span, format!("unknown ra_salsa attribute `{name}`")) + .to_compile_error() + .into(); + } + } + } + + let sig_span = method.sig.span(); + // Check attribute combinations. + if num_storages > 1 { + return Error::new(sig_span, "multiple storage attributes specified") + .to_compile_error() + .into(); + } + match &invoke { + Some(invoke) if storage == QueryStorage::Input => { + return Error::new( + invoke.span(), + "#[ra_salsa::invoke] cannot be set on #[ra_salsa::input] queries", + ) + .to_compile_error() + .into(); + } + _ => {} + } + + // Extract keys. + let mut iter = method.sig.inputs.iter(); + let self_receiver = match iter.next() { + Some(FnArg::Receiver(sr)) if sr.mutability.is_none() => sr, + _ => { + return Error::new( + sig_span, + format!("first argument of query `{query_name}` must be `&self`"), + ) + .to_compile_error() + .into(); + } + }; + let mut keys: Vec<(Ident, Type)> = vec![]; + for (idx, arg) in iter.enumerate() { + match arg { + FnArg::Typed(syn::PatType { pat, ty, .. }) => keys.push(( + match pat.as_ref() { + syn::Pat::Ident(ident_pat) => ident_pat.ident.clone(), + _ => format_ident!("key{}", idx), + }, + Type::clone(ty), + )), + arg => { + return Error::new( + arg.span(), + format!("unsupported argument `{arg:?}` of `{query_name}`",), + ) + .to_compile_error() + .into(); + } + } + } + + // Extract value. + let value = match method.sig.output { + ReturnType::Type(_, ref ty) => ty.as_ref().clone(), + ref ret => { + return Error::new( + ret.span(), + format!("unsupported return type `{ret:?}` of `{query_name}`"), + ) + .to_compile_error() + .into(); + } + }; + + // For `#[ra_salsa::interned]` keys, we create a "lookup key" automatically. + // + // For a query like: + // + // fn foo(&self, x: Key1, y: Key2) -> u32 + // + // we would create + // + // fn lookup_foo(&self, x: u32) -> (Key1, Key2) + let lookup_query = if let QueryStorage::Interned = storage { + let lookup_query_type = + format_ident!("{}LookupQuery", query_name.to_string().to_upper_camel_case()); + let lookup_fn_name = format_ident!("lookup_{}", query_name); + let keys = keys.iter().map(|(_, ty)| ty); + let lookup_value: Type = parse_quote!((#(#keys),*)); + let lookup_keys = vec![(parse_quote! { key }, value.clone())]; + Some(Query { + query_type: lookup_query_type, + query_name: format!("{lookup_fn_name}"), + fn_name: lookup_fn_name, + receiver: self_receiver.clone(), + attrs: vec![], // FIXME -- some automatically generated docs on this method? + storage: QueryStorage::InternedLookup { intern_query_type: query_type.clone() }, + keys: lookup_keys, + value: lookup_value, + invoke: None, + cycle: cycle.clone(), + }) + } else { + None + }; + + queries.push(Query { + query_type, + query_name, + fn_name: method.sig.ident, + receiver: self_receiver.clone(), + attrs, + storage, + keys, + value, + invoke, + cycle, + }); + + queries.extend(lookup_query); + } + } + + let group_storage = format_ident!("{}GroupStorage__", trait_name, span = Span::call_site()); + + let mut query_fn_declarations = proc_macro2::TokenStream::new(); + let mut query_fn_definitions = proc_macro2::TokenStream::new(); + let mut storage_fields = proc_macro2::TokenStream::new(); + let mut queries_with_storage = vec![]; + for query in &queries { + #[allow(clippy::map_identity)] + // clippy is incorrect here, this is not the identity function due to match ergonomics + let (key_names, keys): (Vec<_>, Vec<_>) = query.keys.iter().map(|(a, b)| (a, b)).unzip(); + let value = &query.value; + let fn_name = &query.fn_name; + let qt = &query.query_type; + let attrs = &query.attrs; + let self_receiver = &query.receiver; + + query_fn_declarations.extend(quote! { + #(#attrs)* + fn #fn_name(#self_receiver, #(#key_names: #keys),*) -> #value; + }); + + // Special case: transparent queries don't create actual storage, + // just inline the definition + if let QueryStorage::Transparent = query.storage { + let invoke = query.invoke_tt(); + query_fn_definitions.extend(quote! { + fn #fn_name(&self, #(#key_names: #keys),*) -> #value { + #invoke(self, #(#key_names),*) + } + }); + continue; + } + + queries_with_storage.push(fn_name); + + let tracing = if let QueryStorage::Memoized | QueryStorage::LruMemoized = query.storage { + let s = format!("{trait_name}::{fn_name}"); + Some(quote! { + let _p = tracing::debug_span!(#s, #(#key_names = tracing::field::debug(&#key_names)),*).entered(); + }) + } else { + None + } + .into_iter(); + + query_fn_definitions.extend(quote! { + fn #fn_name(&self, #(#key_names: #keys),*) -> #value { + #(#tracing),* + // Create a shim to force the code to be monomorphized in the + // query crate. Our experiments revealed that this makes a big + // difference in total compilation time in rust-analyzer, though + // it's not totally obvious why that should be. + fn __shim(db: &(dyn #trait_name + '_), #(#key_names: #keys),*) -> #value { + ra_salsa::plumbing::get_query_table::<#qt>(db).get((#(#key_names),*)) + } + __shim(self, #(#key_names),*) + + } + }); + + // For input queries, we need `set_foo` etc + if let QueryStorage::Input = query.storage { + let set_fn_name = format_ident!("set_{}", fn_name); + let set_with_durability_fn_name = format_ident!("set_{}_with_durability", fn_name); + + let set_fn_docs = format!( + " + Set the value of the `{fn_name}` input. + + See `{fn_name}` for details. + + *Note:* Setting values will trigger cancellation + of any ongoing queries; this method blocks until + those queries have been cancelled. + " + ); + + let set_constant_fn_docs = format!( + " + Set the value of the `{fn_name}` input with a + specific durability instead of the default of + `Durability::LOW`. You can use `Durability::MAX` + to promise that its value will never change again. + + See `{fn_name}` for details. + + *Note:* Setting values will trigger cancellation + of any ongoing queries; this method blocks until + those queries have been cancelled. + " + ); + + query_fn_declarations.extend(quote! { + # [doc = #set_fn_docs] + fn #set_fn_name(&mut self, #(#key_names: #keys,)* value__: #value); + + + # [doc = #set_constant_fn_docs] + fn #set_with_durability_fn_name(&mut self, #(#key_names: #keys,)* value__: #value, durability__: ra_salsa::Durability); + }); + + query_fn_definitions.extend(quote! { + fn #set_fn_name(&mut self, #(#key_names: #keys,)* value__: #value) { + fn __shim(db: &mut dyn #trait_name, #(#key_names: #keys,)* value__: #value) { + ra_salsa::plumbing::get_query_table_mut::<#qt>(db).set((#(#key_names),*), value__) + } + __shim(self, #(#key_names,)* value__) + } + + fn #set_with_durability_fn_name(&mut self, #(#key_names: #keys,)* value__: #value, durability__: ra_salsa::Durability) { + fn __shim(db: &mut dyn #trait_name, #(#key_names: #keys,)* value__: #value, durability__: ra_salsa::Durability) { + ra_salsa::plumbing::get_query_table_mut::<#qt>(db).set_with_durability((#(#key_names),*), value__, durability__) + } + __shim(self, #(#key_names,)* value__ ,durability__) + } + }); + } + + // A field for the storage struct + storage_fields.extend(quote! { + #fn_name: std::sync::Arc<<#qt as ra_salsa::Query>::Storage>, + }); + } + + // Emit the trait itself. + let mut output = { + let bounds = &input.supertraits; + quote! { + #(#trait_attrs)* + #trait_vis trait #trait_name : + ra_salsa::Database + + ra_salsa::plumbing::HasQueryGroup<#group_struct> + + #bounds + { + #query_fn_declarations + } + } + }; + + // Emit the query group struct and impl of `QueryGroup`. + output.extend(quote! { + /// Representative struct for the query group. + #trait_vis struct #group_struct { } + + impl ra_salsa::plumbing::QueryGroup for #group_struct + { + type DynDb = #dyn_db; + type GroupStorage = #group_storage; + } + }); + + // Emit an impl of the trait + output.extend({ + let bounds = input.supertraits; + quote! { + impl #trait_name for DB + where + DB: #bounds, + DB: ra_salsa::Database, + DB: ra_salsa::plumbing::HasQueryGroup<#group_struct>, + { + #query_fn_definitions + } + } + }); + + let non_transparent_queries = + || queries.iter().filter(|q| !matches!(q.storage, QueryStorage::Transparent)); + + // Emit the query types. + for (query, query_index) in non_transparent_queries().zip(0_u16..) { + let fn_name = &query.fn_name; + let qt = &query.query_type; + + let storage = match &query.storage { + QueryStorage::Memoized => quote!(ra_salsa::plumbing::MemoizedStorage), + QueryStorage::LruMemoized => quote!(ra_salsa::plumbing::LruMemoizedStorage), + QueryStorage::LruDependencies => { + quote!(ra_salsa::plumbing::LruDependencyStorage) + } + QueryStorage::Input if query.keys.is_empty() => { + quote!(ra_salsa::plumbing::UnitInputStorage) + } + QueryStorage::Input => quote!(ra_salsa::plumbing::InputStorage), + QueryStorage::Interned => quote!(ra_salsa::plumbing::InternedStorage), + QueryStorage::InternedLookup { intern_query_type } => { + quote!(ra_salsa::plumbing::LookupInternedStorage) + } + QueryStorage::Transparent => panic!("should have been filtered"), + }; + let keys = query.keys.iter().map(|(_, ty)| ty); + let value = &query.value; + let query_name = &query.query_name; + + // Emit the query struct and implement the Query trait on it. + output.extend(quote! { + #[derive(Default, Debug)] + #trait_vis struct #qt; + }); + + output.extend(quote! { + impl #qt { + /// Get access to extra methods pertaining to this query. + /// You can also use it to invoke this query. + #trait_vis fn in_db(self, db: &#dyn_db) -> ra_salsa::QueryTable<'_, Self> + { + ra_salsa::plumbing::get_query_table::<#qt>(db) + } + } + }); + + output.extend(quote! { + impl #qt { + /// Like `in_db`, but gives access to methods for setting the + /// value of an input. Not applicable to derived queries. + /// + /// # Threads, cancellation, and blocking + /// + /// Mutating the value of a query cannot be done while there are + /// still other queries executing. If you are using your database + /// within a single thread, this is not a problem: you only have + /// `&self` access to the database, but this method requires `&mut + /// self`. + /// + /// However, if you have used `snapshot` to create other threads, + /// then attempts to `set` will **block the current thread** until + /// those snapshots are dropped (usually when those threads + /// complete). This also implies that if you create a snapshot but + /// do not send it to another thread, then invoking `set` will + /// deadlock. + /// + /// Before blocking, the thread that is attempting to `set` will + /// also set a cancellation flag. This will cause any query + /// invocations in other threads to unwind with a `Cancelled` + /// sentinel value and eventually let the `set` succeed once all + /// threads have unwound past the ra_salsa invocation. + /// + /// If your query implementations are performing expensive + /// operations without invoking another query, you can also use + /// the `Runtime::unwind_if_cancelled` method to check for an + /// ongoing cancellation and bring those operations to a close, + /// thus allowing the `set` to succeed. Otherwise, long-running + /// computations may lead to "starvation", meaning that the + /// thread attempting to `set` has to wait a long, long time. =) + #trait_vis fn in_db_mut(self, db: &mut #dyn_db) -> ra_salsa::QueryTableMut<'_, Self> + { + ra_salsa::plumbing::get_query_table_mut::<#qt>(db) + } + } + + impl<'d> ra_salsa::QueryDb<'d> for #qt + { + type DynDb = #dyn_db + 'd; + type Group = #group_struct; + type GroupStorage = #group_storage; + } + + // ANCHOR:Query_impl + impl ra_salsa::Query for #qt + { + type Key = (#(#keys),*); + type Value = #value; + type Storage = #storage; + + const QUERY_INDEX: u16 = #query_index; + + const QUERY_NAME: &'static str = #query_name; + + fn query_storage<'a>( + group_storage: &'a >::GroupStorage, + ) -> &'a std::sync::Arc { + &group_storage.#fn_name + } + + fn query_storage_mut<'a>( + group_storage: &'a >::GroupStorage, + ) -> &'a std::sync::Arc { + &group_storage.#fn_name + } + } + // ANCHOR_END:Query_impl + }); + + // Implement the QueryFunction trait for queries which need it. + if query.storage.needs_query_function() { + let span = query.fn_name.span(); + + let key_names: Vec<_> = query.keys.iter().map(|(pat, _)| pat).collect(); + let key_pattern = if query.keys.len() == 1 { + quote! { #(#key_names),* } + } else { + quote! { (#(#key_names),*) } + }; + let invoke = query.invoke_tt(); + + let recover = if let Some(cycle_recovery_fn) = &query.cycle { + quote! { + const CYCLE_STRATEGY: ra_salsa::plumbing::CycleRecoveryStrategy = + ra_salsa::plumbing::CycleRecoveryStrategy::Fallback; + fn cycle_fallback(db: &>::DynDb, cycle: &ra_salsa::Cycle, #key_pattern: &::Key) + -> ::Value { + #cycle_recovery_fn( + db, + cycle, + #(#key_names),* + ) + } + } + } else { + quote! { + const CYCLE_STRATEGY: ra_salsa::plumbing::CycleRecoveryStrategy = + ra_salsa::plumbing::CycleRecoveryStrategy::Panic; + } + }; + + output.extend(quote_spanned! {span=> + // ANCHOR:QueryFunction_impl + impl ra_salsa::plumbing::QueryFunction for #qt + { + fn execute(db: &>::DynDb, #key_pattern: ::Key) + -> ::Value { + #invoke(db, #(#key_names),*) + } + + #recover + } + // ANCHOR_END:QueryFunction_impl + }); + } + } + + let mut fmt_ops = proc_macro2::TokenStream::new(); + for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) { + fmt_ops.extend(quote! { + #query_index => { + ra_salsa::plumbing::QueryStorageOps::fmt_index( + &*self.#fn_name, db, input.key_index(), fmt, + ) + } + }); + } + + let mut maybe_changed_ops = proc_macro2::TokenStream::new(); + for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) { + maybe_changed_ops.extend(quote! { + #query_index => { + ra_salsa::plumbing::QueryStorageOps::maybe_changed_after( + &*self.#fn_name, db, input.key_index(), revision + ) + } + }); + } + + let mut cycle_recovery_strategy_ops = proc_macro2::TokenStream::new(); + for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) { + cycle_recovery_strategy_ops.extend(quote! { + #query_index => { + ra_salsa::plumbing::QueryStorageOps::cycle_recovery_strategy( + &*self.#fn_name + ) + } + }); + } + + let mut for_each_ops = proc_macro2::TokenStream::new(); + for Query { fn_name, .. } in non_transparent_queries() { + for_each_ops.extend(quote! { + op(&*self.#fn_name); + }); + } + + // Emit query group storage struct + output.extend(quote! { + #trait_vis struct #group_storage { + #storage_fields + } + + // ANCHOR:group_storage_new + impl #group_storage { + #trait_vis fn new(group_index: u16) -> Self { + #group_storage { + #( + #queries_with_storage: + std::sync::Arc::new(ra_salsa::plumbing::QueryStorageOps::new(group_index)), + )* + } + } + } + // ANCHOR_END:group_storage_new + + // ANCHOR:group_storage_methods + impl #group_storage { + #trait_vis fn fmt_index( + &self, + db: &(#dyn_db + '_), + input: ra_salsa::DatabaseKeyIndex, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + match input.query_index() { + #fmt_ops + i => panic!("ra_salsa: impossible query index {}", i), + } + } + + #trait_vis fn maybe_changed_after( + &self, + db: &(#dyn_db + '_), + input: ra_salsa::DatabaseKeyIndex, + revision: ra_salsa::Revision, + ) -> bool { + match input.query_index() { + #maybe_changed_ops + i => panic!("ra_salsa: impossible query index {}", i), + } + } + + #trait_vis fn cycle_recovery_strategy( + &self, + db: &(#dyn_db + '_), + input: ra_salsa::DatabaseKeyIndex, + ) -> ra_salsa::plumbing::CycleRecoveryStrategy { + match input.query_index() { + #cycle_recovery_strategy_ops + i => panic!("ra_salsa: impossible query index {}", i), + } + } + + #trait_vis fn for_each_query( + &self, + _runtime: &ra_salsa::Runtime, + mut op: &mut dyn FnMut(&dyn ra_salsa::plumbing::QueryStorageMassOps), + ) { + #for_each_ops + } + } + // ANCHOR_END:group_storage_methods + }); + output.into() +} + +struct SalsaAttr { + name: String, + tts: TokenStream, + span: Span, +} + +impl std::fmt::Debug for SalsaAttr { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(fmt, "{:?}", self.name) + } +} + +impl TryFrom for SalsaAttr { + type Error = syn::Attribute; + + fn try_from(attr: syn::Attribute) -> Result { + if is_not_salsa_attr_path(attr.path()) { + return Err(attr); + } + + let span = attr.span(); + let name = attr.path().segments[1].ident.to_string(); + let tts = match attr.meta { + syn::Meta::Path(path) => path.into_token_stream(), + syn::Meta::List(ref list) => { + let tts = list + .into_token_stream() + .into_iter() + .skip(attr.path().to_token_stream().into_iter().count()); + proc_macro2::TokenStream::from_iter(tts) + } + syn::Meta::NameValue(nv) => nv.into_token_stream(), + } + .into(); + + Ok(SalsaAttr { name, tts, span }) + } +} + +fn is_not_salsa_attr_path(path: &syn::Path) -> bool { + path.segments.first().map(|s| s.ident != "ra_salsa").unwrap_or(true) || path.segments.len() != 2 +} + +fn filter_attrs(attrs: Vec) -> (Vec, Vec) { + let mut other = vec![]; + let mut ra_salsa = vec![]; + // Leave non-ra_salsa attributes untouched. These are + // attributes that don't start with `ra_salsa::` or don't have + // exactly two segments in their path. + // Keep the ra_salsa attributes around. + for attr in attrs { + match SalsaAttr::try_from(attr) { + Ok(it) => ra_salsa.push(it), + Err(it) => other.push(it), + } + } + (other, ra_salsa) +} + +#[derive(Debug)] +struct Query { + fn_name: Ident, + receiver: syn::Receiver, + query_name: String, + attrs: Vec, + query_type: Ident, + storage: QueryStorage, + keys: Vec<(Ident, syn::Type)>, + value: syn::Type, + invoke: Option, + cycle: Option, +} + +impl Query { + fn invoke_tt(&self) -> proc_macro2::TokenStream { + match &self.invoke { + Some(i) => i.into_token_stream(), + None => self.fn_name.clone().into_token_stream(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum QueryStorage { + Memoized, + LruDependencies, + LruMemoized, + Input, + Interned, + InternedLookup { intern_query_type: Ident }, + Transparent, +} + +impl QueryStorage { + /// Do we need a `QueryFunction` impl for this type of query? + fn needs_query_function(&self) -> bool { + match self { + QueryStorage::Input + | QueryStorage::Interned + | QueryStorage::InternedLookup { .. } + | QueryStorage::Transparent => false, + QueryStorage::Memoized | QueryStorage::LruMemoized | QueryStorage::LruDependencies => { + true + } + } + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/debug.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/debug.rs new file mode 100644 index 00000000000..5f113541f04 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/debug.rs @@ -0,0 +1,65 @@ +//! Debugging APIs: these are meant for use when unit-testing or +//! debugging your application but aren't ordinarily needed. + +use crate::durability::Durability; +use crate::plumbing::QueryStorageOps; +use crate::Query; +use crate::QueryTable; + +/// Additional methods on queries that can be used to "peek into" +/// their current state. These methods are meant for debugging and +/// observing the effects of garbage collection etc. +pub trait DebugQueryTable { + /// Key of this query. + type Key; + + /// Value of this query. + type Value; + + /// Returns a lower bound on the durability for the given key. + /// This is typically the minimum durability of all values that + /// the query accessed, but we may return a lower durability in + /// some cases. + fn durability(&self, key: Self::Key) -> Durability; + + /// Get the (current) set of the entries in the query table. + fn entries(&self) -> C + where + C: FromIterator>; +} + +/// An entry from a query table, for debugging and inspecting the table state. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub struct TableEntry { + /// key of the query + pub key: K, + /// value of the query, if it is stored + pub value: Option, +} + +impl TableEntry { + pub(crate) fn new(key: K, value: Option) -> TableEntry { + TableEntry { key, value } + } +} + +impl DebugQueryTable for QueryTable<'_, Q> +where + Q: Query, + Q::Storage: QueryStorageOps, +{ + type Key = Q::Key; + type Value = Q::Value; + + fn durability(&self, key: Q::Key) -> Durability { + self.storage.durability(self.db, &key) + } + + fn entries(&self) -> C + where + C: FromIterator>, + { + self.storage.entries(self.db) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/derived.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/derived.rs new file mode 100644 index 00000000000..8b2fdd6b19c --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/derived.rs @@ -0,0 +1,163 @@ +use crate::debug::TableEntry; +use crate::durability::Durability; +use crate::hash::FxIndexMap; +use crate::plumbing::DerivedQueryStorageOps; +use crate::plumbing::QueryFunction; +use crate::plumbing::QueryStorageMassOps; +use crate::plumbing::QueryStorageOps; +use crate::runtime::StampedValue; +use crate::Runtime; +use crate::{Database, DatabaseKeyIndex, QueryDb, Revision}; +use parking_lot::RwLock; +use std::borrow::Borrow; +use std::hash::Hash; +use triomphe::Arc; + +mod slot; +use slot::Slot; + +/// Memoized queries store the result plus a list of the other queries +/// that they invoked. This means we can avoid recomputing them when +/// none of those inputs have changed. +pub type MemoizedStorage = DerivedStorage; + +/// Handles storage where the value is 'derived' by executing a +/// function (in contrast to "inputs"). +pub struct DerivedStorage +where + Q: QueryFunction, +{ + group_index: u16, + slot_map: RwLock>>>, +} + +impl std::panic::RefUnwindSafe for DerivedStorage +where + Q: QueryFunction, + + Q::Key: std::panic::RefUnwindSafe, + Q::Value: std::panic::RefUnwindSafe, +{ +} + +impl DerivedStorage +where + Q: QueryFunction, + Q::Value: Eq, +{ + fn slot(&self, key: &Q::Key) -> Arc> { + if let Some(v) = self.slot_map.read().get(key) { + return v.clone(); + } + + let mut write = self.slot_map.write(); + let entry = write.entry(key.clone()); + let key_index = entry.index() as u32; + let database_key_index = DatabaseKeyIndex { + group_index: self.group_index, + query_index: Q::QUERY_INDEX, + key_index, + }; + entry.or_insert_with(|| Arc::new(Slot::new(database_key_index))).clone() + } +} + +impl QueryStorageOps for DerivedStorage +where + Q: QueryFunction, + Q::Value: Eq, +{ + const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = Q::CYCLE_STRATEGY; + + fn new(group_index: u16) -> Self { + DerivedStorage { group_index, slot_map: RwLock::new(FxIndexMap::default()) } + } + + fn fmt_index( + &self, + _db: &>::DynDb, + index: u32, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + let slot_map = self.slot_map.read(); + let key = slot_map.get_index(index as usize).unwrap().0; + write!(fmt, "{}::{}({:?})", std::any::type_name::(), Q::QUERY_NAME, key) + } + + fn maybe_changed_after( + &self, + db: &>::DynDb, + index: u32, + revision: Revision, + ) -> bool { + debug_assert!(revision < db.salsa_runtime().current_revision()); + let (key, slot) = { + let read = self.slot_map.read(); + let Some((key, slot)) = read.get_index(index as usize) else { + return false; + }; + (key.clone(), slot.clone()) + }; + slot.maybe_changed_after(db, revision, &key) + } + + fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { + db.unwind_if_cancelled(); + + let slot = self.slot(key); + let StampedValue { value, durability, changed_at } = slot.read(db, key); + + db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( + slot.database_key_index(), + durability, + changed_at, + ); + + value + } + + fn durability(&self, db: &>::DynDb, key: &Q::Key) -> Durability { + self.slot_map.read().get(key).map_or(Durability::LOW, |slot| slot.durability(db)) + } + + fn entries(&self, _db: &>::DynDb) -> C + where + C: std::iter::FromIterator>, + { + let slot_map = self.slot_map.read(); + slot_map.iter().filter_map(|(key, slot)| slot.as_table_entry(key)).collect() + } +} + +impl QueryStorageMassOps for DerivedStorage +where + Q: QueryFunction, +{ + fn purge(&self) { + *self.slot_map.write() = Default::default(); + } +} + +impl DerivedQueryStorageOps for DerivedStorage +where + Q: QueryFunction, + Q::Value: Eq, +{ + fn invalidate(&self, runtime: &mut Runtime, key: &S) + where + S: Eq + Hash, + Q::Key: Borrow, + { + runtime.with_incremented_revision(|new_revision| { + let map_read = self.slot_map.read(); + + if let Some(slot) = map_read.get(key) { + if let Some(durability) = slot.invalidate(new_revision) { + return Some(durability); + } + } + + None + }) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/derived/slot.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/derived/slot.rs new file mode 100644 index 00000000000..de7a3976074 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/derived/slot.rs @@ -0,0 +1,775 @@ +use crate::debug::TableEntry; +use crate::durability::Durability; +use crate::plumbing::{DatabaseOps, QueryFunction}; +use crate::revision::Revision; +use crate::runtime::local_state::ActiveQueryGuard; +use crate::runtime::local_state::QueryRevisions; +use crate::runtime::Runtime; +use crate::runtime::RuntimeId; +use crate::runtime::StampedValue; +use crate::runtime::WaitResult; +use crate::Cycle; +use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb}; +use parking_lot::{RawRwLock, RwLock}; +use std::ops::Deref; +use std::sync::atomic::{AtomicBool, Ordering}; +use tracing::{debug, info}; + +pub(super) struct Slot +where + Q: QueryFunction, +{ + key_index: u32, + // FIXME: Yeet this + group_index: u16, + state: RwLock>, +} + +/// Defines the "current state" of query's memoized results. +enum QueryState +where + Q: QueryFunction, +{ + NotComputed, + + /// The runtime with the given id is currently computing the + /// result of this query. + InProgress { + id: RuntimeId, + + /// Set to true if any other queries are blocked, + /// waiting for this query to complete. + anyone_waiting: AtomicBool, + }, + + /// We have computed the query already, and here is the result. + Memoized(Memo), +} + +struct Memo { + /// The result of the query, if we decide to memoize it. + value: V, + + /// Last revision when this memo was verified; this begins + /// as the current revision. + pub(crate) verified_at: Revision, + + /// Revision information + revisions: QueryRevisions, +} + +/// Return value of `probe` helper. +enum ProbeState { + /// Another thread was active but has completed. + /// Try again! + Retry, + + /// No entry for this key at all. + NotComputed(G), + + /// There is an entry, but its contents have not been + /// verified in this revision. + Stale(G), + + /// There is an entry which has been verified, + /// and it has the following value-- or, we blocked + /// on another thread, and that resulted in a cycle. + UpToDate(V), +} + +/// Return value of `maybe_changed_after_probe` helper. +enum MaybeChangedSinceProbeState { + /// Another thread was active but has completed. + /// Try again! + Retry, + + /// Value may have changed in the given revision. + ChangedAt(Revision), + + /// There is a stale cache entry that has not been + /// verified in this revision, so we can't say. + Stale(G), +} + +impl Slot +where + Q: QueryFunction, + Q::Value: Eq, +{ + pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self { + Self { + key_index: database_key_index.key_index, + group_index: database_key_index.group_index, + state: RwLock::new(QueryState::NotComputed), + } + } + + pub(super) fn database_key_index(&self) -> DatabaseKeyIndex { + DatabaseKeyIndex { + group_index: self.group_index, + query_index: Q::QUERY_INDEX, + key_index: self.key_index, + } + } + + pub(super) fn read( + &self, + db: &>::DynDb, + key: &Q::Key, + ) -> StampedValue { + let runtime = db.salsa_runtime(); + + // NB: We don't need to worry about people modifying the + // revision out from under our feet. Either `db` is a frozen + // database, in which case there is a lock, or the mutator + // thread is the current thread, and it will be prevented from + // doing any `set` invocations while the query function runs. + let revision_now = runtime.current_revision(); + + info!("{:?}: invoked at {:?}", self, revision_now,); + + // First, do a check with a read-lock. + loop { + match self.probe(db, self.state.read(), runtime, revision_now) { + ProbeState::UpToDate(v) => return v, + ProbeState::Stale(..) | ProbeState::NotComputed(..) => break, + ProbeState::Retry => continue, + } + } + + self.read_upgrade(db, key, revision_now) + } + + /// Second phase of a read operation: acquires an upgradable-read + /// and -- if needed -- validates whether inputs have changed, + /// recomputes value, etc. This is invoked after our initial probe + /// shows a potentially out of date value. + fn read_upgrade( + &self, + db: &>::DynDb, + key: &Q::Key, + revision_now: Revision, + ) -> StampedValue { + let runtime = db.salsa_runtime(); + + debug!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,); + + // Check with an upgradable read to see if there is a value + // already. (This permits other readers but prevents anyone + // else from running `read_upgrade` at the same time.) + let mut old_memo = loop { + match self.probe(db, self.state.upgradable_read(), runtime, revision_now) { + ProbeState::UpToDate(v) => return v, + ProbeState::Stale(state) | ProbeState::NotComputed(state) => { + type RwLockUpgradableReadGuard<'a, T> = + lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>; + + let mut state = RwLockUpgradableReadGuard::upgrade(state); + match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) { + QueryState::Memoized(old_memo) => break Some(old_memo), + QueryState::InProgress { .. } => unreachable!(), + QueryState::NotComputed => break None, + } + } + ProbeState::Retry => continue, + } + }; + + let panic_guard = PanicGuard::new(self, runtime); + let active_query = runtime.push_query(self.database_key_index()); + + // If we have an old-value, it *may* now be stale, since there + // has been a new revision since the last time we checked. So, + // first things first, let's walk over each of our previous + // inputs and check whether they are out of date. + if let Some(memo) = &mut old_memo { + if let Some(value) = memo.verify_value(db.ops_database(), revision_now, &active_query) { + info!("{:?}: validated old memoized value", self,); + + db.salsa_event(Event { + runtime_id: runtime.id(), + kind: EventKind::DidValidateMemoizedValue { + database_key: self.database_key_index(), + }, + }); + + panic_guard.proceed(old_memo); + + return value; + } + } + + self.execute(db, runtime, revision_now, active_query, panic_guard, old_memo, key) + } + + fn execute( + &self, + db: &>::DynDb, + runtime: &Runtime, + revision_now: Revision, + active_query: ActiveQueryGuard<'_>, + panic_guard: PanicGuard<'_, Q>, + old_memo: Option>, + key: &Q::Key, + ) -> StampedValue { + tracing::info!("{:?}: executing query", self.database_key_index().debug(db)); + + db.salsa_event(Event { + runtime_id: db.salsa_runtime().id(), + kind: EventKind::WillExecute { database_key: self.database_key_index() }, + }); + + // Query was not previously executed, or value is potentially + // stale, or value is absent. Let's execute! + let value = match Cycle::catch(|| Q::execute(db, key.clone())) { + Ok(v) => v, + Err(cycle) => { + tracing::debug!( + "{:?}: caught cycle {:?}, have strategy {:?}", + self.database_key_index().debug(db), + cycle, + Q::CYCLE_STRATEGY, + ); + match Q::CYCLE_STRATEGY { + crate::plumbing::CycleRecoveryStrategy::Panic => { + panic_guard.proceed(None); + cycle.throw() + } + crate::plumbing::CycleRecoveryStrategy::Fallback => { + if let Some(c) = active_query.take_cycle() { + assert!(c.is(&cycle)); + Q::cycle_fallback(db, &cycle, key) + } else { + // we are not a participant in this cycle + debug_assert!(!cycle + .participant_keys() + .any(|k| k == self.database_key_index())); + cycle.throw() + } + } + } + } + }; + + let mut revisions = active_query.pop(); + + // We assume that query is side-effect free -- that is, does + // not mutate the "inputs" to the query system. Sanity check + // that assumption here, at least to the best of our ability. + assert_eq!( + runtime.current_revision(), + revision_now, + "revision altered during query execution", + ); + + // If the new value is equal to the old one, then it didn't + // really change, even if some of its inputs have. So we can + // "backdate" its `changed_at` revision to be the same as the + // old value. + if let Some(old_memo) = &old_memo { + // Careful: if the value became less durable than it + // used to be, that is a "breaking change" that our + // consumers must be aware of. Becoming *more* durable + // is not. See the test `constant_to_non_constant`. + if revisions.durability >= old_memo.revisions.durability && old_memo.value == value { + debug!( + "read_upgrade({:?}): value is equal, back-dating to {:?}", + self, old_memo.revisions.changed_at, + ); + + assert!(old_memo.revisions.changed_at <= revisions.changed_at); + revisions.changed_at = old_memo.revisions.changed_at; + } + } + + let new_value = StampedValue { + value, + durability: revisions.durability, + changed_at: revisions.changed_at, + }; + + let memo_value = new_value.value.clone(); + + debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,); + + panic_guard.proceed(Some(Memo { value: memo_value, verified_at: revision_now, revisions })); + + new_value + } + + /// Helper for `read` that does a shallow check (not recursive) if we have an up-to-date value. + /// + /// Invoked with the guard `state` corresponding to the `QueryState` of some `Slot` (the guard + /// can be either read or write). Returns a suitable `ProbeState`: + /// + /// - `ProbeState::UpToDate(r)` if the table has an up-to-date value (or we blocked on another + /// thread that produced such a value). + /// - `ProbeState::StaleOrAbsent(g)` if either (a) there is no memo for this key, (b) the memo + /// has no value; or (c) the memo has not been verified at the current revision. + /// + /// Note that in case `ProbeState::UpToDate`, the lock will have been released. + fn probe( + &self, + db: &>::DynDb, + state: StateGuard, + runtime: &Runtime, + revision_now: Revision, + ) -> ProbeState, StateGuard> + where + StateGuard: Deref>, + { + match &*state { + QueryState::NotComputed => ProbeState::NotComputed(state), + + QueryState::InProgress { id, anyone_waiting } => { + let other_id = *id; + + // NB: `Ordering::Relaxed` is sufficient here, + // as there are no loads that are "gated" on this + // value. Everything that is written is also protected + // by a lock that must be acquired. The role of this + // boolean is to decide *whether* to acquire the lock, + // not to gate future atomic reads. + anyone_waiting.store(true, Ordering::Relaxed); + + self.block_on_or_unwind(db, runtime, other_id, state); + + // Other thread completely normally, so our value may be available now. + ProbeState::Retry + } + + QueryState::Memoized(memo) => { + debug!( + "{:?}: found memoized value, verified_at={:?}, changed_at={:?}", + self, memo.verified_at, memo.revisions.changed_at, + ); + + if memo.verified_at < revision_now { + return ProbeState::Stale(state); + } + + let value = &memo.value; + let value = StampedValue { + durability: memo.revisions.durability, + changed_at: memo.revisions.changed_at, + value: value.clone(), + }; + + info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at); + + ProbeState::UpToDate(value) + } + } + } + + pub(super) fn durability(&self, db: &>::DynDb) -> Durability { + match &*self.state.read() { + QueryState::NotComputed => Durability::LOW, + QueryState::InProgress { .. } => panic!("query in progress"), + QueryState::Memoized(memo) => { + if memo.check_durability(db.salsa_runtime()) { + memo.revisions.durability + } else { + Durability::LOW + } + } + } + } + + pub(super) fn as_table_entry(&self, key: &Q::Key) -> Option> { + match &*self.state.read() { + QueryState::NotComputed => None, + QueryState::InProgress { .. } => Some(TableEntry::new(key.clone(), None)), + QueryState::Memoized(memo) => { + Some(TableEntry::new(key.clone(), Some(memo.value.clone()))) + } + } + } + + pub(super) fn invalidate(&self, new_revision: Revision) -> Option { + tracing::debug!("Slot::invalidate(new_revision = {:?})", new_revision); + match &mut *self.state.write() { + QueryState::Memoized(memo) => { + memo.revisions.untracked = true; + memo.revisions.inputs = None; + memo.revisions.changed_at = new_revision; + Some(memo.revisions.durability) + } + QueryState::NotComputed => None, + QueryState::InProgress { .. } => unreachable!(), + } + } + + pub(super) fn maybe_changed_after( + &self, + db: &>::DynDb, + revision: Revision, + key: &Q::Key, + ) -> bool { + let runtime = db.salsa_runtime(); + let revision_now = runtime.current_revision(); + + db.unwind_if_cancelled(); + + debug!( + "maybe_changed_after({:?}) called with revision={:?}, revision_now={:?}", + self, revision, revision_now, + ); + + // Do an initial probe with just the read-lock. + // + // If we find that a cache entry for the value is present + // but hasn't been verified in this revision, we'll have to + // do more. + loop { + match self.maybe_changed_after_probe(db, self.state.read(), runtime, revision_now) { + MaybeChangedSinceProbeState::Retry => continue, + MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision, + MaybeChangedSinceProbeState::Stale(state) => { + drop(state); + return self.maybe_changed_after_upgrade(db, revision, key); + } + } + } + } + + fn maybe_changed_after_probe( + &self, + db: &>::DynDb, + state: StateGuard, + runtime: &Runtime, + revision_now: Revision, + ) -> MaybeChangedSinceProbeState + where + StateGuard: Deref>, + { + match self.probe(db, state, runtime, revision_now) { + ProbeState::Retry => MaybeChangedSinceProbeState::Retry, + + ProbeState::Stale(state) => MaybeChangedSinceProbeState::Stale(state), + + // If we know when value last changed, we can return right away. + // Note that we don't need the actual value to be available. + ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => { + MaybeChangedSinceProbeState::ChangedAt(changed_at) + } + + // If we have nothing cached, then value may have changed. + ProbeState::NotComputed(_) => MaybeChangedSinceProbeState::ChangedAt(revision_now), + } + } + + fn maybe_changed_after_upgrade( + &self, + db: &>::DynDb, + revision: Revision, + key: &Q::Key, + ) -> bool { + let runtime = db.salsa_runtime(); + let revision_now = runtime.current_revision(); + + // Get an upgradable read lock, which permits other reads but no writers. + // Probe again. If the value is stale (needs to be verified), then upgrade + // to a write lock and swap it with InProgress while we work. + let mut old_memo = match self.maybe_changed_after_probe( + db, + self.state.upgradable_read(), + runtime, + revision_now, + ) { + MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision, + + // If another thread was active, then the cache line is going to be + // either verified or cleared out. Just recurse to figure out which. + // Note that we don't need an upgradable read. + MaybeChangedSinceProbeState::Retry => { + return self.maybe_changed_after(db, revision, key) + } + + MaybeChangedSinceProbeState::Stale(state) => { + type RwLockUpgradableReadGuard<'a, T> = + lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>; + + let mut state = RwLockUpgradableReadGuard::upgrade(state); + match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) { + QueryState::Memoized(old_memo) => old_memo, + QueryState::NotComputed | QueryState::InProgress { .. } => unreachable!(), + } + } + }; + + let panic_guard = PanicGuard::new(self, runtime); + let active_query = runtime.push_query(self.database_key_index()); + + if old_memo.verify_revisions(db.ops_database(), revision_now, &active_query) { + let maybe_changed = old_memo.revisions.changed_at > revision; + panic_guard.proceed(Some(old_memo)); + maybe_changed + } else { + // We found that this memoized value may have changed + // but we have an old value. We can re-run the code and + // actually *check* if it has changed. + let StampedValue { changed_at, .. } = self.execute( + db, + runtime, + revision_now, + active_query, + panic_guard, + Some(old_memo), + key, + ); + changed_at > revision + } + } + + /// Helper: see [`Runtime::try_block_on_or_unwind`]. + fn block_on_or_unwind( + &self, + db: &>::DynDb, + runtime: &Runtime, + other_id: RuntimeId, + mutex_guard: MutexGuard, + ) { + runtime.block_on_or_unwind( + db.ops_database(), + self.database_key_index(), + other_id, + mutex_guard, + ) + } +} + +impl QueryState +where + Q: QueryFunction, +{ + fn in_progress(id: RuntimeId) -> Self { + QueryState::InProgress { id, anyone_waiting: Default::default() } + } +} + +struct PanicGuard<'me, Q> +where + Q: QueryFunction, + Q::Value: Eq, +{ + slot: &'me Slot, + runtime: &'me Runtime, +} + +impl<'me, Q> PanicGuard<'me, Q> +where + Q: QueryFunction, + Q::Value: Eq, +{ + fn new(slot: &'me Slot, runtime: &'me Runtime) -> Self { + Self { slot, runtime } + } + + /// Indicates that we have concluded normally (without panicking). + /// If `opt_memo` is some, then this memo is installed as the new + /// memoized value. If `opt_memo` is `None`, then the slot is cleared + /// and has no value. + fn proceed(mut self, opt_memo: Option>) { + self.overwrite_placeholder(WaitResult::Completed, opt_memo); + std::mem::forget(self) + } + + /// Overwrites the `InProgress` placeholder for `key` that we + /// inserted; if others were blocked, waiting for us to finish, + /// then notify them. + fn overwrite_placeholder(&mut self, wait_result: WaitResult, opt_memo: Option>) { + let old_value = { + let mut write = self.slot.state.write(); + match opt_memo { + // Replace the `InProgress` marker that we installed with the new + // memo, thus releasing our unique access to this key. + Some(memo) => std::mem::replace(&mut *write, QueryState::Memoized(memo)), + + // We had installed an `InProgress` marker, but we panicked before + // it could be removed. At this point, we therefore "own" unique + // access to our slot, so we can just remove the key. + None => std::mem::replace(&mut *write, QueryState::NotComputed), + } + }; + + match old_value { + QueryState::InProgress { id, anyone_waiting } => { + assert_eq!(id, self.runtime.id()); + + // NB: As noted on the `store`, `Ordering::Relaxed` is + // sufficient here. This boolean signals us on whether to + // acquire a mutex; the mutex will guarantee that all writes + // we are interested in are visible. + if anyone_waiting.load(Ordering::Relaxed) { + self.runtime + .unblock_queries_blocked_on(self.slot.database_key_index(), wait_result); + } + } + _ => panic!( + "\ +Unexpected panic during query evaluation, aborting the process. + +Please report this bug to https://github.com/salsa-rs/salsa/issues." + ), + } + } +} + +impl<'me, Q> Drop for PanicGuard<'me, Q> +where + Q: QueryFunction, + Q::Value: Eq, +{ + fn drop(&mut self) { + if std::thread::panicking() { + // We panicked before we could proceed and need to remove `key`. + self.overwrite_placeholder(WaitResult::Panicked, None) + } else { + // If no panic occurred, then panic guard ought to be + // "forgotten" and so this Drop code should never run. + panic!(".forget() was not called") + } + } +} + +impl Memo +where + V: Clone, +{ + /// Determines whether the value stored in this memo (if any) is still + /// valid in the current revision. If so, returns a stamped value. + /// + /// If needed, this will walk each dependency and + /// recursively invoke `maybe_changed_after`, which may in turn + /// re-execute the dependency. This can cause cycles to occur, + /// so the current query must be pushed onto the + /// stack to permit cycle detection and recovery: therefore, + /// takes the `active_query` argument as evidence. + fn verify_value( + &mut self, + db: &dyn Database, + revision_now: Revision, + active_query: &ActiveQueryGuard<'_>, + ) -> Option> { + if self.verify_revisions(db, revision_now, active_query) { + Some(StampedValue { + durability: self.revisions.durability, + changed_at: self.revisions.changed_at, + value: self.value.clone(), + }) + } else { + None + } + } + + /// Determines whether the value represented by this memo is still + /// valid in the current revision; note that the value itself is + /// not needed for this check. If needed, this will walk each + /// dependency and recursively invoke `maybe_changed_after`, which + /// may in turn re-execute the dependency. This can cause cycles to occur, + /// so the current query must be pushed onto the + /// stack to permit cycle detection and recovery: therefore, + /// takes the `active_query` argument as evidence. + fn verify_revisions( + &mut self, + db: &dyn Database, + revision_now: Revision, + _active_query: &ActiveQueryGuard<'_>, + ) -> bool { + assert!(self.verified_at != revision_now); + let verified_at = self.verified_at; + + debug!( + "verify_revisions: verified_at={:?}, revision_now={:?}, inputs={:#?}", + verified_at, revision_now, self.revisions.inputs + ); + + if self.check_durability(db.salsa_runtime()) { + return self.mark_value_as_verified(revision_now); + } + + match &self.revisions.inputs { + // We can't validate values that had untracked inputs; just have to + // re-execute. + None if self.revisions.untracked => return false, + None => {} + + // Check whether any of our inputs changed since the + // **last point where we were verified** (not since we + // last changed). This is important: if we have + // memoized values, then an input may have changed in + // revision R2, but we found that *our* value was the + // same regardless, so our change date is still + // R1. But our *verification* date will be R2, and we + // are only interested in finding out whether the + // input changed *again*. + Some(inputs) => { + let changed_input = + inputs.slice.iter().find(|&&input| db.maybe_changed_after(input, verified_at)); + if let Some(input) = changed_input { + debug!("validate_memoized_value: `{:?}` may have changed", input); + + return false; + } + } + }; + + self.mark_value_as_verified(revision_now) + } + + /// True if this memo is known not to have changed based on its durability. + fn check_durability(&self, runtime: &Runtime) -> bool { + let last_changed = runtime.last_changed_revision(self.revisions.durability); + debug!( + "check_durability(last_changed={:?} <= verified_at={:?}) = {:?}", + last_changed, + self.verified_at, + last_changed <= self.verified_at, + ); + last_changed <= self.verified_at + } + + fn mark_value_as_verified(&mut self, revision_now: Revision) -> bool { + self.verified_at = revision_now; + true + } +} + +impl std::fmt::Debug for Slot +where + Q: QueryFunction, +{ + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(fmt, "{:?}", Q::default()) + } +} + +/// Check that `Slot: Send + Sync` as long as +/// `DB::DatabaseData: Send + Sync`, which in turn implies that +/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. +#[allow(dead_code)] +fn check_send_sync() +where + Q: QueryFunction, + + Q::Key: Send + Sync, + Q::Value: Send + Sync, +{ + fn is_send_sync() {} + is_send_sync::>(); +} + +/// Check that `Slot: 'static` as long as +/// `DB::DatabaseData: 'static`, which in turn implies that +/// `Q::Key: 'static`, `Q::Value: 'static`. +#[allow(dead_code)] +fn check_static() +where + Q: QueryFunction + 'static, + Q::Key: 'static, + Q::Value: 'static, +{ + fn is_static() {} + is_static::>(); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/derived_lru.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/derived_lru.rs new file mode 100644 index 00000000000..bdb448e2412 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/derived_lru.rs @@ -0,0 +1,233 @@ +use crate::debug::TableEntry; +use crate::durability::Durability; +use crate::hash::FxIndexMap; +use crate::lru::Lru; +use crate::plumbing::DerivedQueryStorageOps; +use crate::plumbing::LruQueryStorageOps; +use crate::plumbing::QueryFunction; +use crate::plumbing::QueryStorageMassOps; +use crate::plumbing::QueryStorageOps; +use crate::runtime::StampedValue; +use crate::Runtime; +use crate::{Database, DatabaseKeyIndex, QueryDb, Revision}; +use parking_lot::RwLock; +use std::borrow::Borrow; +use std::hash::Hash; +use std::marker::PhantomData; +use triomphe::Arc; + +mod slot; +use slot::Slot; + +/// Memoized queries store the result plus a list of the other queries +/// that they invoked. This means we can avoid recomputing them when +/// none of those inputs have changed. +pub type MemoizedStorage = DerivedStorage; + +/// "Dependency" queries just track their dependencies and not the +/// actual value (which they produce on demand). This lessens the +/// storage requirements. +pub type DependencyStorage = DerivedStorage; + +/// Handles storage where the value is 'derived' by executing a +/// function (in contrast to "inputs"). +pub struct DerivedStorage +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + group_index: u16, + lru_list: Lru>, + slot_map: RwLock>>>, + policy: PhantomData, +} + +impl std::panic::RefUnwindSafe for DerivedStorage +where + Q: QueryFunction, + MP: MemoizationPolicy, + Q::Key: std::panic::RefUnwindSafe, + Q::Value: std::panic::RefUnwindSafe, +{ +} + +pub trait MemoizationPolicy: Send + Sync +where + Q: QueryFunction, +{ + fn should_memoize_value(key: &Q::Key) -> bool; + + fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool; +} + +pub enum AlwaysMemoizeValue {} +impl MemoizationPolicy for AlwaysMemoizeValue +where + Q: QueryFunction, + Q::Value: Eq, +{ + fn should_memoize_value(_key: &Q::Key) -> bool { + true + } + + fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool { + old_value == new_value + } +} + +pub enum NeverMemoizeValue {} +impl MemoizationPolicy for NeverMemoizeValue +where + Q: QueryFunction, +{ + fn should_memoize_value(_key: &Q::Key) -> bool { + false + } + + fn memoized_value_eq(_old_value: &Q::Value, _new_value: &Q::Value) -> bool { + panic!("cannot reach since we never memoize") + } +} + +impl DerivedStorage +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + fn slot(&self, key: &Q::Key) -> Arc> { + if let Some(v) = self.slot_map.read().get(key) { + return v.clone(); + } + + let mut write = self.slot_map.write(); + let entry = write.entry(key.clone()); + let key_index = entry.index() as u32; + let database_key_index = DatabaseKeyIndex { + group_index: self.group_index, + query_index: Q::QUERY_INDEX, + key_index, + }; + entry.or_insert_with(|| Arc::new(Slot::new(database_key_index))).clone() + } +} + +impl QueryStorageOps for DerivedStorage +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = Q::CYCLE_STRATEGY; + + fn new(group_index: u16) -> Self { + DerivedStorage { + group_index, + slot_map: RwLock::new(FxIndexMap::default()), + lru_list: Default::default(), + policy: PhantomData, + } + } + + fn fmt_index( + &self, + _db: &>::DynDb, + index: u32, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + let slot_map = self.slot_map.read(); + let key = slot_map.get_index(index as usize).unwrap().0; + write!(fmt, "{}::{}({:?})", std::any::type_name::(), Q::QUERY_NAME, key) + } + + fn maybe_changed_after( + &self, + db: &>::DynDb, + index: u32, + revision: Revision, + ) -> bool { + debug_assert!(revision < db.salsa_runtime().current_revision()); + let (key, slot) = { + let read = self.slot_map.read(); + let Some((key, slot)) = read.get_index(index as usize) else { + return false; + }; + (key.clone(), slot.clone()) + }; + slot.maybe_changed_after(db, revision, &key) + } + + fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { + db.unwind_if_cancelled(); + + let slot = self.slot(key); + let StampedValue { value, durability, changed_at } = slot.read(db, key); + + if let Some(evicted) = self.lru_list.record_use(&slot) { + evicted.evict(); + } + + db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( + slot.database_key_index(), + durability, + changed_at, + ); + + value + } + + fn durability(&self, db: &>::DynDb, key: &Q::Key) -> Durability { + self.slot(key).durability(db) + } + + fn entries(&self, _db: &>::DynDb) -> C + where + C: std::iter::FromIterator>, + { + let slot_map = self.slot_map.read(); + slot_map.iter().filter_map(|(key, slot)| slot.as_table_entry(key)).collect() + } +} + +impl QueryStorageMassOps for DerivedStorage +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + fn purge(&self) { + self.lru_list.purge(); + *self.slot_map.write() = Default::default(); + } +} + +impl LruQueryStorageOps for DerivedStorage +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + fn set_lru_capacity(&self, new_capacity: u16) { + self.lru_list.set_lru_capacity(new_capacity); + } +} + +impl DerivedQueryStorageOps for DerivedStorage +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + fn invalidate(&self, runtime: &mut Runtime, key: &S) + where + S: Eq + Hash, + Q::Key: Borrow, + { + runtime.with_incremented_revision(|new_revision| { + let map_read = self.slot_map.read(); + + if let Some(slot) = map_read.get(key) { + if let Some(durability) = slot.invalidate(new_revision) { + return Some(durability); + } + } + + None + }) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/derived_lru/slot.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/derived_lru/slot.rs new file mode 100644 index 00000000000..d0e4b5422b5 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/derived_lru/slot.rs @@ -0,0 +1,845 @@ +use crate::debug::TableEntry; +use crate::derived_lru::MemoizationPolicy; +use crate::durability::Durability; +use crate::lru::LruIndex; +use crate::lru::LruNode; +use crate::plumbing::{DatabaseOps, QueryFunction}; +use crate::revision::Revision; +use crate::runtime::local_state::ActiveQueryGuard; +use crate::runtime::local_state::QueryRevisions; +use crate::runtime::Runtime; +use crate::runtime::RuntimeId; +use crate::runtime::StampedValue; +use crate::runtime::WaitResult; +use crate::Cycle; +use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb}; +use parking_lot::{RawRwLock, RwLock}; +use std::marker::PhantomData; +use std::ops::Deref; +use std::sync::atomic::{AtomicBool, Ordering}; +use tracing::{debug, info}; + +pub(super) struct Slot +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + key_index: u32, + group_index: u16, + state: RwLock>, + lru_index: LruIndex, + policy: PhantomData, +} + +/// Defines the "current state" of query's memoized results. +enum QueryState +where + Q: QueryFunction, +{ + NotComputed, + + /// The runtime with the given id is currently computing the + /// result of this query. + InProgress { + id: RuntimeId, + + /// Set to true if any other queries are blocked, + /// waiting for this query to complete. + anyone_waiting: AtomicBool, + }, + + /// We have computed the query already, and here is the result. + Memoized(Memo), +} + +struct Memo { + /// The result of the query, if we decide to memoize it. + value: Option, + + /// Last revision when this memo was verified; this begins + /// as the current revision. + pub(crate) verified_at: Revision, + + /// Revision information + revisions: QueryRevisions, +} + +/// Return value of `probe` helper. +enum ProbeState { + /// Another thread was active but has completed. + /// Try again! + Retry, + + /// No entry for this key at all. + NotComputed(G), + + /// There is an entry, but its contents have not been + /// verified in this revision. + Stale(G), + + /// There is an entry, and it has been verified + /// in this revision, but it has no cached + /// value. The `Revision` is the revision where the + /// value last changed (if we were to recompute it). + NoValue(G, Revision), + + /// There is an entry which has been verified, + /// and it has the following value-- or, we blocked + /// on another thread, and that resulted in a cycle. + UpToDate(V), +} + +/// Return value of `maybe_changed_after_probe` helper. +enum MaybeChangedSinceProbeState { + /// Another thread was active but has completed. + /// Try again! + Retry, + + /// Value may have changed in the given revision. + ChangedAt(Revision), + + /// There is a stale cache entry that has not been + /// verified in this revision, so we can't say. + Stale(G), +} + +impl Slot +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self { + Self { + key_index: database_key_index.key_index, + group_index: database_key_index.group_index, + state: RwLock::new(QueryState::NotComputed), + lru_index: LruIndex::default(), + policy: PhantomData, + } + } + + pub(super) fn database_key_index(&self) -> DatabaseKeyIndex { + DatabaseKeyIndex { + group_index: self.group_index, + query_index: Q::QUERY_INDEX, + key_index: self.key_index, + } + } + + pub(super) fn read( + &self, + db: &>::DynDb, + key: &Q::Key, + ) -> StampedValue { + let runtime = db.salsa_runtime(); + + // NB: We don't need to worry about people modifying the + // revision out from under our feet. Either `db` is a frozen + // database, in which case there is a lock, or the mutator + // thread is the current thread, and it will be prevented from + // doing any `set` invocations while the query function runs. + let revision_now = runtime.current_revision(); + + info!("{:?}: invoked at {:?}", self, revision_now,); + + // First, do a check with a read-lock. + loop { + match self.probe(db, self.state.read(), runtime, revision_now) { + ProbeState::UpToDate(v) => return v, + ProbeState::Stale(..) | ProbeState::NoValue(..) | ProbeState::NotComputed(..) => { + break + } + ProbeState::Retry => continue, + } + } + + self.read_upgrade(db, key, revision_now) + } + + /// Second phase of a read operation: acquires an upgradable-read + /// and -- if needed -- validates whether inputs have changed, + /// recomputes value, etc. This is invoked after our initial probe + /// shows a potentially out of date value. + fn read_upgrade( + &self, + db: &>::DynDb, + key: &Q::Key, + revision_now: Revision, + ) -> StampedValue { + let runtime = db.salsa_runtime(); + + debug!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,); + + // Check with an upgradable read to see if there is a value + // already. (This permits other readers but prevents anyone + // else from running `read_upgrade` at the same time.) + let mut old_memo = loop { + match self.probe(db, self.state.upgradable_read(), runtime, revision_now) { + ProbeState::UpToDate(v) => return v, + ProbeState::Stale(state) + | ProbeState::NotComputed(state) + | ProbeState::NoValue(state, _) => { + type RwLockUpgradableReadGuard<'a, T> = + lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>; + + let mut state = RwLockUpgradableReadGuard::upgrade(state); + match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) { + QueryState::Memoized(old_memo) => break Some(old_memo), + QueryState::InProgress { .. } => unreachable!(), + QueryState::NotComputed => break None, + } + } + ProbeState::Retry => continue, + } + }; + + let panic_guard = PanicGuard::new(self, runtime); + let active_query = runtime.push_query(self.database_key_index()); + + // If we have an old-value, it *may* now be stale, since there + // has been a new revision since the last time we checked. So, + // first things first, let's walk over each of our previous + // inputs and check whether they are out of date. + if let Some(memo) = &mut old_memo { + if let Some(value) = memo.verify_value(db.ops_database(), revision_now, &active_query) { + info!("{:?}: validated old memoized value", self,); + + db.salsa_event(Event { + runtime_id: runtime.id(), + kind: EventKind::DidValidateMemoizedValue { + database_key: self.database_key_index(), + }, + }); + + panic_guard.proceed(old_memo); + + return value; + } + } + + self.execute(db, runtime, revision_now, active_query, panic_guard, old_memo, key) + } + + fn execute( + &self, + db: &>::DynDb, + runtime: &Runtime, + revision_now: Revision, + active_query: ActiveQueryGuard<'_>, + panic_guard: PanicGuard<'_, Q, MP>, + old_memo: Option>, + key: &Q::Key, + ) -> StampedValue { + tracing::info!("{:?}: executing query", self.database_key_index().debug(db)); + + db.salsa_event(Event { + runtime_id: db.salsa_runtime().id(), + kind: EventKind::WillExecute { database_key: self.database_key_index() }, + }); + + // Query was not previously executed, or value is potentially + // stale, or value is absent. Let's execute! + let value = match Cycle::catch(|| Q::execute(db, key.clone())) { + Ok(v) => v, + Err(cycle) => { + tracing::debug!( + "{:?}: caught cycle {:?}, have strategy {:?}", + self.database_key_index().debug(db), + cycle, + Q::CYCLE_STRATEGY, + ); + match Q::CYCLE_STRATEGY { + crate::plumbing::CycleRecoveryStrategy::Panic => { + panic_guard.proceed(None); + cycle.throw() + } + crate::plumbing::CycleRecoveryStrategy::Fallback => { + if let Some(c) = active_query.take_cycle() { + assert!(c.is(&cycle)); + Q::cycle_fallback(db, &cycle, key) + } else { + // we are not a participant in this cycle + debug_assert!(!cycle + .participant_keys() + .any(|k| k == self.database_key_index())); + cycle.throw() + } + } + } + } + }; + + let mut revisions = active_query.pop(); + + // We assume that query is side-effect free -- that is, does + // not mutate the "inputs" to the query system. Sanity check + // that assumption here, at least to the best of our ability. + assert_eq!( + runtime.current_revision(), + revision_now, + "revision altered during query execution", + ); + + // If the new value is equal to the old one, then it didn't + // really change, even if some of its inputs have. So we can + // "backdate" its `changed_at` revision to be the same as the + // old value. + if let Some(old_memo) = &old_memo { + if let Some(old_value) = &old_memo.value { + // Careful: if the value became less durable than it + // used to be, that is a "breaking change" that our + // consumers must be aware of. Becoming *more* durable + // is not. See the test `constant_to_non_constant`. + if revisions.durability >= old_memo.revisions.durability + && MP::memoized_value_eq(old_value, &value) + { + debug!( + "read_upgrade({:?}): value is equal, back-dating to {:?}", + self, old_memo.revisions.changed_at, + ); + + assert!(old_memo.revisions.changed_at <= revisions.changed_at); + revisions.changed_at = old_memo.revisions.changed_at; + } + } + } + + let new_value = StampedValue { + value, + durability: revisions.durability, + changed_at: revisions.changed_at, + }; + + let memo_value = + if self.should_memoize_value(key) { Some(new_value.value.clone()) } else { None }; + + debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,); + + panic_guard.proceed(Some(Memo { value: memo_value, verified_at: revision_now, revisions })); + + new_value + } + + /// Helper for `read` that does a shallow check (not recursive) if we have an up-to-date value. + /// + /// Invoked with the guard `state` corresponding to the `QueryState` of some `Slot` (the guard + /// can be either read or write). Returns a suitable `ProbeState`: + /// + /// - `ProbeState::UpToDate(r)` if the table has an up-to-date value (or we blocked on another + /// thread that produced such a value). + /// - `ProbeState::StaleOrAbsent(g)` if either (a) there is no memo for this key, (b) the memo + /// has no value; or (c) the memo has not been verified at the current revision. + /// + /// Note that in case `ProbeState::UpToDate`, the lock will have been released. + fn probe( + &self, + db: &>::DynDb, + state: StateGuard, + runtime: &Runtime, + revision_now: Revision, + ) -> ProbeState, StateGuard> + where + StateGuard: Deref>, + { + match &*state { + QueryState::NotComputed => ProbeState::NotComputed(state), + + QueryState::InProgress { id, anyone_waiting } => { + let other_id = *id; + + // NB: `Ordering::Relaxed` is sufficient here, + // as there are no loads that are "gated" on this + // value. Everything that is written is also protected + // by a lock that must be acquired. The role of this + // boolean is to decide *whether* to acquire the lock, + // not to gate future atomic reads. + anyone_waiting.store(true, Ordering::Relaxed); + + self.block_on_or_unwind(db, runtime, other_id, state); + + // Other thread completely normally, so our value may be available now. + ProbeState::Retry + } + + QueryState::Memoized(memo) => { + debug!( + "{:?}: found memoized value, verified_at={:?}, changed_at={:?}", + self, memo.verified_at, memo.revisions.changed_at, + ); + + if memo.verified_at < revision_now { + return ProbeState::Stale(state); + } + + if let Some(value) = &memo.value { + let value = StampedValue { + durability: memo.revisions.durability, + changed_at: memo.revisions.changed_at, + value: value.clone(), + }; + + info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at); + + ProbeState::UpToDate(value) + } else { + let changed_at = memo.revisions.changed_at; + ProbeState::NoValue(state, changed_at) + } + } + } + } + + pub(super) fn durability(&self, db: &>::DynDb) -> Durability { + match &*self.state.read() { + QueryState::NotComputed => Durability::LOW, + QueryState::InProgress { .. } => panic!("query in progress"), + QueryState::Memoized(memo) => { + if memo.check_durability(db.salsa_runtime()) { + memo.revisions.durability + } else { + Durability::LOW + } + } + } + } + + pub(super) fn as_table_entry(&self, key: &Q::Key) -> Option> { + match &*self.state.read() { + QueryState::NotComputed => None, + QueryState::InProgress { .. } => Some(TableEntry::new(key.clone(), None)), + QueryState::Memoized(memo) => Some(TableEntry::new(key.clone(), memo.value.clone())), + } + } + + pub(super) fn evict(&self) { + let mut state = self.state.write(); + if let QueryState::Memoized(memo) = &mut *state { + // Evicting a value with an untracked input could + // lead to inconsistencies. Note that we can't check + // `has_untracked_input` when we add the value to the cache, + // because inputs can become untracked in the next revision. + if memo.has_untracked_input() { + return; + } + memo.value = None; + } + } + + pub(super) fn invalidate(&self, new_revision: Revision) -> Option { + tracing::debug!("Slot::invalidate(new_revision = {:?})", new_revision); + match &mut *self.state.write() { + QueryState::Memoized(memo) => { + memo.revisions.untracked = true; + memo.revisions.inputs = None; + memo.revisions.changed_at = new_revision; + Some(memo.revisions.durability) + } + QueryState::NotComputed => None, + QueryState::InProgress { .. } => unreachable!(), + } + } + + pub(super) fn maybe_changed_after( + &self, + db: &>::DynDb, + revision: Revision, + key: &Q::Key, + ) -> bool { + let runtime = db.salsa_runtime(); + let revision_now = runtime.current_revision(); + + db.unwind_if_cancelled(); + + debug!( + "maybe_changed_after({:?}) called with revision={:?}, revision_now={:?}", + self, revision, revision_now, + ); + + // Do an initial probe with just the read-lock. + // + // If we find that a cache entry for the value is present + // but hasn't been verified in this revision, we'll have to + // do more. + loop { + match self.maybe_changed_after_probe(db, self.state.read(), runtime, revision_now) { + MaybeChangedSinceProbeState::Retry => continue, + MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision, + MaybeChangedSinceProbeState::Stale(state) => { + drop(state); + return self.maybe_changed_after_upgrade(db, revision, key); + } + } + } + } + + fn maybe_changed_after_probe( + &self, + db: &>::DynDb, + state: StateGuard, + runtime: &Runtime, + revision_now: Revision, + ) -> MaybeChangedSinceProbeState + where + StateGuard: Deref>, + { + match self.probe(db, state, runtime, revision_now) { + ProbeState::Retry => MaybeChangedSinceProbeState::Retry, + + ProbeState::Stale(state) => MaybeChangedSinceProbeState::Stale(state), + + // If we know when value last changed, we can return right away. + // Note that we don't need the actual value to be available. + ProbeState::NoValue(_, changed_at) + | ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => { + MaybeChangedSinceProbeState::ChangedAt(changed_at) + } + + // If we have nothing cached, then value may have changed. + ProbeState::NotComputed(_) => MaybeChangedSinceProbeState::ChangedAt(revision_now), + } + } + + fn maybe_changed_after_upgrade( + &self, + db: &>::DynDb, + revision: Revision, + key: &Q::Key, + ) -> bool { + let runtime = db.salsa_runtime(); + let revision_now = runtime.current_revision(); + + // Get an upgradable read lock, which permits other reads but no writers. + // Probe again. If the value is stale (needs to be verified), then upgrade + // to a write lock and swap it with InProgress while we work. + let mut old_memo = match self.maybe_changed_after_probe( + db, + self.state.upgradable_read(), + runtime, + revision_now, + ) { + MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision, + + // If another thread was active, then the cache line is going to be + // either verified or cleared out. Just recurse to figure out which. + // Note that we don't need an upgradable read. + MaybeChangedSinceProbeState::Retry => { + return self.maybe_changed_after(db, revision, key) + } + + MaybeChangedSinceProbeState::Stale(state) => { + type RwLockUpgradableReadGuard<'a, T> = + lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>; + + let mut state = RwLockUpgradableReadGuard::upgrade(state); + match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) { + QueryState::Memoized(old_memo) => old_memo, + QueryState::NotComputed | QueryState::InProgress { .. } => unreachable!(), + } + } + }; + + let panic_guard = PanicGuard::new(self, runtime); + let active_query = runtime.push_query(self.database_key_index()); + + if old_memo.verify_revisions(db.ops_database(), revision_now, &active_query) { + let maybe_changed = old_memo.revisions.changed_at > revision; + panic_guard.proceed(Some(old_memo)); + maybe_changed + } else if old_memo.value.is_some() { + // We found that this memoized value may have changed + // but we have an old value. We can re-run the code and + // actually *check* if it has changed. + let StampedValue { changed_at, .. } = self.execute( + db, + runtime, + revision_now, + active_query, + panic_guard, + Some(old_memo), + key, + ); + changed_at > revision + } else { + // We found that inputs to this memoized value may have chanced + // but we don't have an old value to compare against or re-use. + // No choice but to drop the memo and say that its value may have changed. + panic_guard.proceed(None); + true + } + } + + /// Helper: see [`Runtime::try_block_on_or_unwind`]. + fn block_on_or_unwind( + &self, + db: &>::DynDb, + runtime: &Runtime, + other_id: RuntimeId, + mutex_guard: MutexGuard, + ) { + runtime.block_on_or_unwind( + db.ops_database(), + self.database_key_index(), + other_id, + mutex_guard, + ) + } + + fn should_memoize_value(&self, key: &Q::Key) -> bool { + MP::should_memoize_value(key) + } +} + +impl QueryState +where + Q: QueryFunction, +{ + fn in_progress(id: RuntimeId) -> Self { + QueryState::InProgress { id, anyone_waiting: Default::default() } + } +} + +struct PanicGuard<'me, Q, MP> +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + slot: &'me Slot, + runtime: &'me Runtime, +} + +impl<'me, Q, MP> PanicGuard<'me, Q, MP> +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + fn new(slot: &'me Slot, runtime: &'me Runtime) -> Self { + Self { slot, runtime } + } + + /// Indicates that we have concluded normally (without panicking). + /// If `opt_memo` is some, then this memo is installed as the new + /// memoized value. If `opt_memo` is `None`, then the slot is cleared + /// and has no value. + fn proceed(mut self, opt_memo: Option>) { + self.overwrite_placeholder(WaitResult::Completed, opt_memo); + std::mem::forget(self) + } + + /// Overwrites the `InProgress` placeholder for `key` that we + /// inserted; if others were blocked, waiting for us to finish, + /// then notify them. + fn overwrite_placeholder(&mut self, wait_result: WaitResult, opt_memo: Option>) { + let old_value = { + let mut write = self.slot.state.write(); + match opt_memo { + // Replace the `InProgress` marker that we installed with the new + // memo, thus releasing our unique access to this key. + Some(memo) => std::mem::replace(&mut *write, QueryState::Memoized(memo)), + + // We had installed an `InProgress` marker, but we panicked before + // it could be removed. At this point, we therefore "own" unique + // access to our slot, so we can just remove the key. + None => std::mem::replace(&mut *write, QueryState::NotComputed), + } + }; + + match old_value { + QueryState::InProgress { id, anyone_waiting } => { + assert_eq!(id, self.runtime.id()); + + // NB: As noted on the `store`, `Ordering::Relaxed` is + // sufficient here. This boolean signals us on whether to + // acquire a mutex; the mutex will guarantee that all writes + // we are interested in are visible. + if anyone_waiting.load(Ordering::Relaxed) { + self.runtime + .unblock_queries_blocked_on(self.slot.database_key_index(), wait_result); + } + } + _ => panic!( + "\ +Unexpected panic during query evaluation, aborting the process. + +Please report this bug to https://github.com/salsa-rs/salsa/issues." + ), + } + } +} + +impl<'me, Q, MP> Drop for PanicGuard<'me, Q, MP> +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + fn drop(&mut self) { + if std::thread::panicking() { + // We panicked before we could proceed and need to remove `key`. + self.overwrite_placeholder(WaitResult::Panicked, None) + } else { + // If no panic occurred, then panic guard ought to be + // "forgotten" and so this Drop code should never run. + panic!(".forget() was not called") + } + } +} + +impl Memo +where + V: Clone, +{ + /// Determines whether the value stored in this memo (if any) is still + /// valid in the current revision. If so, returns a stamped value. + /// + /// If needed, this will walk each dependency and + /// recursively invoke `maybe_changed_after`, which may in turn + /// re-execute the dependency. This can cause cycles to occur, + /// so the current query must be pushed onto the + /// stack to permit cycle detection and recovery: therefore, + /// takes the `active_query` argument as evidence. + fn verify_value( + &mut self, + db: &dyn Database, + revision_now: Revision, + active_query: &ActiveQueryGuard<'_>, + ) -> Option> { + // If we don't have a memoized value, nothing to validate. + if self.value.is_none() { + return None; + } + if self.verify_revisions(db, revision_now, active_query) { + self.value.clone().map(|value| StampedValue { + durability: self.revisions.durability, + changed_at: self.revisions.changed_at, + value, + }) + } else { + None + } + } + + /// Determines whether the value represented by this memo is still + /// valid in the current revision; note that the value itself is + /// not needed for this check. If needed, this will walk each + /// dependency and recursively invoke `maybe_changed_after`, which + /// may in turn re-execute the dependency. This can cause cycles to occur, + /// so the current query must be pushed onto the + /// stack to permit cycle detection and recovery: therefore, + /// takes the `active_query` argument as evidence. + fn verify_revisions( + &mut self, + db: &dyn Database, + revision_now: Revision, + _active_query: &ActiveQueryGuard<'_>, + ) -> bool { + assert!(self.verified_at != revision_now); + let verified_at = self.verified_at; + + debug!( + "verify_revisions: verified_at={:?}, revision_now={:?}, inputs={:#?}", + verified_at, revision_now, self.revisions.inputs + ); + + if self.check_durability(db.salsa_runtime()) { + return self.mark_value_as_verified(revision_now); + } + + match &self.revisions.inputs { + // We can't validate values that had untracked inputs; just have to + // re-execute. + None if self.revisions.untracked => return false, + None => {} + + // Check whether any of our inputs changed since the + // **last point where we were verified** (not since we + // last changed). This is important: if we have + // memoized values, then an input may have changed in + // revision R2, but we found that *our* value was the + // same regardless, so our change date is still + // R1. But our *verification* date will be R2, and we + // are only interested in finding out whether the + // input changed *again*. + Some(inputs) => { + let changed_input = + inputs.slice.iter().find(|&&input| db.maybe_changed_after(input, verified_at)); + if let Some(input) = changed_input { + debug!("validate_memoized_value: `{:?}` may have changed", input); + + return false; + } + } + }; + + self.mark_value_as_verified(revision_now) + } + + /// True if this memo is known not to have changed based on its durability. + fn check_durability(&self, runtime: &Runtime) -> bool { + let last_changed = runtime.last_changed_revision(self.revisions.durability); + debug!( + "check_durability(last_changed={:?} <= verified_at={:?}) = {:?}", + last_changed, + self.verified_at, + last_changed <= self.verified_at, + ); + last_changed <= self.verified_at + } + + fn mark_value_as_verified(&mut self, revision_now: Revision) -> bool { + self.verified_at = revision_now; + true + } + + fn has_untracked_input(&self) -> bool { + self.revisions.untracked + } +} + +impl std::fmt::Debug for Slot +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(fmt, "{:?}", Q::default()) + } +} + +impl LruNode for Slot +where + Q: QueryFunction, + MP: MemoizationPolicy, +{ + fn lru_index(&self) -> &LruIndex { + &self.lru_index + } +} + +/// Check that `Slot: Send + Sync` as long as +/// `DB::DatabaseData: Send + Sync`, which in turn implies that +/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. +#[allow(dead_code)] +fn check_send_sync() +where + Q: QueryFunction, + MP: MemoizationPolicy, + Q::Key: Send + Sync, + Q::Value: Send + Sync, +{ + fn is_send_sync() {} + is_send_sync::>(); +} + +/// Check that `Slot: 'static` as long as +/// `DB::DatabaseData: 'static`, which in turn implies that +/// `Q::Key: 'static`, `Q::Value: 'static`. +#[allow(dead_code)] +fn check_static() +where + Q: QueryFunction + 'static, + MP: MemoizationPolicy + 'static, + Q::Key: 'static, + Q::Value: 'static, +{ + fn is_static() {} + is_static::>(); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/durability.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/durability.rs new file mode 100644 index 00000000000..7b8e6840fc9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/durability.rs @@ -0,0 +1,49 @@ +/// Describes how likely a value is to change -- how "durable" it is. +/// By default, inputs have `Durability::LOW` and interned values have +/// `Durability::HIGH`. But inputs can be explicitly set with other +/// durabilities. +/// +/// We use durabilities to optimize the work of "revalidating" a query +/// after some input has changed. Ordinarily, in a new revision, +/// queries have to trace all their inputs back to the base inputs to +/// determine if any of those inputs have changed. But if we know that +/// the only changes were to inputs of low durability (the common +/// case), and we know that the query only used inputs of medium +/// durability or higher, then we can skip that enumeration. +/// +/// Typically, one assigns low durabilites to inputs that the user is +/// frequently editing. Medium or high durabilities are used for +/// configuration, the source from library crates, or other things +/// that are unlikely to be edited. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Durability(u8); + +impl Durability { + /// Low durability: things that change frequently. + /// + /// Example: part of the crate being edited + pub const LOW: Durability = Durability(0); + + /// Medium durability: things that change sometimes, but rarely. + /// + /// Example: a Cargo.toml file + pub const MEDIUM: Durability = Durability(1); + + /// High durability: things that are not expected to change under + /// common usage. + /// + /// Example: the standard library or something from crates.io + pub const HIGH: Durability = Durability(2); + + /// The maximum possible durability; equivalent to HIGH but + /// "conceptually" distinct (i.e., if we add more durability + /// levels, this could change). + pub(crate) const MAX: Durability = Self::HIGH; + + /// Number of durability levels. + pub(crate) const LEN: usize = Self::MAX.index() + 1; + + pub(crate) const fn index(self) -> usize { + self.0 as usize + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/hash.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/hash.rs new file mode 100644 index 00000000000..3b2d7df3fbe --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/hash.rs @@ -0,0 +1,3 @@ +pub(crate) type FxHasher = std::hash::BuildHasherDefault; +pub(crate) type FxIndexSet = indexmap::IndexSet; +pub(crate) type FxIndexMap = indexmap::IndexMap; diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/input.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/input.rs new file mode 100644 index 00000000000..f04f48e3bab --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/input.rs @@ -0,0 +1,371 @@ +use crate::debug::TableEntry; +use crate::durability::Durability; +use crate::hash::FxIndexMap; +use crate::plumbing::CycleRecoveryStrategy; +use crate::plumbing::InputQueryStorageOps; +use crate::plumbing::QueryStorageMassOps; +use crate::plumbing::QueryStorageOps; +use crate::revision::Revision; +use crate::runtime::StampedValue; +use crate::Database; +use crate::Query; +use crate::Runtime; +use crate::{DatabaseKeyIndex, QueryDb}; +use indexmap::map::Entry; +use parking_lot::RwLock; +use std::iter; +use tracing::debug; + +/// Input queries store the result plus a list of the other queries +/// that they invoked. This means we can avoid recomputing them when +/// none of those inputs have changed. +pub struct InputStorage +where + Q: Query, +{ + group_index: u16, + slots: RwLock>>, +} + +struct Slot { + key_index: u32, + stamped_value: RwLock>, +} + +impl std::panic::RefUnwindSafe for InputStorage +where + Q: Query, + Q::Key: std::panic::RefUnwindSafe, + Q::Value: std::panic::RefUnwindSafe, +{ +} + +impl QueryStorageOps for InputStorage +where + Q: Query, +{ + const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; + + fn new(group_index: u16) -> Self { + InputStorage { group_index, slots: Default::default() } + } + + fn fmt_index( + &self, + _db: &>::DynDb, + index: u32, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + let slot_map = self.slots.read(); + let key = slot_map.get_index(index as usize).unwrap().0; + write!(fmt, "{}({:?})", Q::QUERY_NAME, key) + } + + fn maybe_changed_after( + &self, + db: &>::DynDb, + index: u32, + revision: Revision, + ) -> bool { + debug_assert!(revision < db.salsa_runtime().current_revision()); + let slots = &self.slots.read(); + let Some((_, slot)) = slots.get_index(index as usize) else { + return true; + }; + + debug!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,); + + let changed_at = slot.stamped_value.read().changed_at; + + debug!("maybe_changed_after: changed_at = {:?}", changed_at); + + changed_at > revision + } + + fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { + db.unwind_if_cancelled(); + + let slots = &self.slots.read(); + let slot = slots + .get(key) + .unwrap_or_else(|| panic!("no value set for {:?}({:?})", Q::default(), key)); + + let StampedValue { value, durability, changed_at } = slot.stamped_value.read().clone(); + + db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( + DatabaseKeyIndex { + group_index: self.group_index, + query_index: Q::QUERY_INDEX, + key_index: slot.key_index, + }, + durability, + changed_at, + ); + + value + } + + fn durability(&self, _db: &>::DynDb, key: &Q::Key) -> Durability { + match self.slots.read().get(key) { + Some(slot) => slot.stamped_value.read().durability, + None => panic!("no value set for {:?}({:?})", Q::default(), key), + } + } + + fn entries(&self, _db: &>::DynDb) -> C + where + C: std::iter::FromIterator>, + { + let slots = self.slots.read(); + slots + .iter() + .map(|(key, slot)| { + TableEntry::new(key.clone(), Some(slot.stamped_value.read().value.clone())) + }) + .collect() + } +} + +impl QueryStorageMassOps for InputStorage +where + Q: Query, +{ + fn purge(&self) { + *self.slots.write() = Default::default(); + } +} + +impl InputQueryStorageOps for InputStorage +where + Q: Query, +{ + fn set(&self, runtime: &mut Runtime, key: &Q::Key, value: Q::Value, durability: Durability) { + tracing::debug!("{:?}({:?}) = {:?} ({:?})", Q::default(), key, value, durability); + + // The value is changing, so we need a new revision (*). We also + // need to update the 'last changed' revision by invoking + // `guard.mark_durability_as_changed`. + // + // CAREFUL: This will block until the global revision lock can + // be acquired. If there are still queries executing, they may + // need to read from this input. Therefore, we wait to acquire + // the lock on `map` until we also hold the global query write + // lock. + // + // (*) Technically, since you can't presently access an input + // for a non-existent key, and you can't enumerate the set of + // keys, we only need a new revision if the key used to + // exist. But we may add such methods in the future and this + // case doesn't generally seem worth optimizing for. + runtime.with_incremented_revision(|next_revision| { + let mut slots = self.slots.write(); + + // Do this *after* we acquire the lock, so that we are not + // racing with somebody else to modify this same cell. + // (Otherwise, someone else might write a *newer* revision + // into the same cell while we block on the lock.) + let stamped_value = StampedValue { value, durability, changed_at: next_revision }; + + match slots.entry(key.clone()) { + Entry::Occupied(entry) => { + let mut slot_stamped_value = entry.get().stamped_value.write(); + let old_durability = slot_stamped_value.durability; + *slot_stamped_value = stamped_value; + Some(old_durability) + } + + Entry::Vacant(entry) => { + let key_index = entry.index() as u32; + entry.insert(Slot { key_index, stamped_value: RwLock::new(stamped_value) }); + None + } + } + }); + } +} + +/// Same as `InputStorage`, but optimized for queries that take no inputs. +pub struct UnitInputStorage +where + Q: Query, +{ + slot: UnitSlot, +} + +struct UnitSlot { + database_key_index: DatabaseKeyIndex, + stamped_value: RwLock>>, +} + +impl std::panic::RefUnwindSafe for UnitInputStorage +where + Q: Query, + Q::Key: std::panic::RefUnwindSafe, + Q::Value: std::panic::RefUnwindSafe, +{ +} + +impl QueryStorageOps for UnitInputStorage +where + Q: Query, +{ + const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; + + fn new(group_index: u16) -> Self { + let database_key_index = + DatabaseKeyIndex { group_index, query_index: Q::QUERY_INDEX, key_index: 0 }; + UnitInputStorage { slot: UnitSlot { database_key_index, stamped_value: RwLock::new(None) } } + } + + fn fmt_index( + &self, + _db: &>::DynDb, + _index: u32, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(fmt, "{}", Q::QUERY_NAME) + } + + fn maybe_changed_after( + &self, + db: &>::DynDb, + _index: u32, + revision: Revision, + ) -> bool { + debug_assert!(revision < db.salsa_runtime().current_revision()); + + debug!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,); + + let Some(value) = &*self.slot.stamped_value.read() else { + return true; + }; + let changed_at = value.changed_at; + + debug!("maybe_changed_after: changed_at = {:?}", changed_at); + + changed_at > revision + } + + fn fetch(&self, db: &>::DynDb, &(): &Q::Key) -> Q::Value { + db.unwind_if_cancelled(); + + let StampedValue { value, durability, changed_at } = self + .slot + .stamped_value + .read() + .clone() + .unwrap_or_else(|| panic!("no value set for {:?}", Q::default())); + + db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( + self.slot.database_key_index, + durability, + changed_at, + ); + + value + } + + fn durability(&self, _db: &>::DynDb, &(): &Q::Key) -> Durability { + match &*self.slot.stamped_value.read() { + Some(stamped_value) => stamped_value.durability, + None => panic!("no value set for {:?}", Q::default(),), + } + } + + fn entries(&self, _db: &>::DynDb) -> C + where + C: std::iter::FromIterator>, + { + iter::once(TableEntry::new( + (), + self.slot.stamped_value.read().as_ref().map(|it| it.value.clone()), + )) + .collect() + } +} + +impl QueryStorageMassOps for UnitInputStorage +where + Q: Query, +{ + fn purge(&self) { + *self.slot.stamped_value.write() = Default::default(); + } +} + +impl InputQueryStorageOps for UnitInputStorage +where + Q: Query, +{ + fn set(&self, runtime: &mut Runtime, (): &Q::Key, value: Q::Value, durability: Durability) { + tracing::debug!("{:?} = {:?} ({:?})", Q::default(), value, durability); + + // The value is changing, so we need a new revision (*). We also + // need to update the 'last changed' revision by invoking + // `guard.mark_durability_as_changed`. + // + // CAREFUL: This will block until the global revision lock can + // be acquired. If there are still queries executing, they may + // need to read from this input. Therefore, we wait to acquire + // the lock on `map` until we also hold the global query write + // lock. + // + // (*) Technically, since you can't presently access an input + // for a non-existent key, and you can't enumerate the set of + // keys, we only need a new revision if the key used to + // exist. But we may add such methods in the future and this + // case doesn't generally seem worth optimizing for. + runtime.with_incremented_revision(|next_revision| { + let mut stamped_value_slot = self.slot.stamped_value.write(); + + // Do this *after* we acquire the lock, so that we are not + // racing with somebody else to modify this same cell. + // (Otherwise, someone else might write a *newer* revision + // into the same cell while we block on the lock.) + let stamped_value = StampedValue { value, durability, changed_at: next_revision }; + + match &mut *stamped_value_slot { + Some(slot_stamped_value) => { + let old_durability = slot_stamped_value.durability; + *slot_stamped_value = stamped_value; + Some(old_durability) + } + + stamped_value_slot @ None => { + *stamped_value_slot = Some(stamped_value); + None + } + } + }); + } +} + +/// Check that `Slot: Send + Sync` as long as +/// `DB::DatabaseData: Send + Sync`, which in turn implies that +/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. +#[allow(dead_code)] +fn check_send_sync() +where + Q: Query, + Q::Key: Send + Sync, + Q::Value: Send + Sync, +{ + fn is_send_sync() {} + is_send_sync::>(); + is_send_sync::>(); +} + +/// Check that `Slot: 'static` as long as +/// `DB::DatabaseData: 'static`, which in turn implies that +/// `Q::Key: 'static`, `Q::Value: 'static`. +#[allow(dead_code)] +fn check_static() +where + Q: Query + 'static, + Q::Key: 'static, + Q::Value: 'static, +{ + fn is_static() {} + is_static::>(); + is_static::>(); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/intern_id.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/intern_id.rs new file mode 100644 index 00000000000..35b495998e1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/intern_id.rs @@ -0,0 +1,131 @@ +use std::fmt; +use std::num::NonZeroU32; + +/// The "raw-id" is used for interned keys in salsa -- it is basically +/// a newtype'd u32. Typically, it is wrapped in a type of your own +/// devising. For more information about interned keys, see [the +/// interned key RFC][rfc]. +/// +/// # Creating a `InternId` +// +/// InternId values can be constructed using the `From` impls, +/// which are implemented for `u32` and `usize`: +/// +/// ``` +/// # use ra_salsa::InternId; +/// let intern_id1 = InternId::from(22_u32); +/// let intern_id2 = InternId::from(22_usize); +/// assert_eq!(intern_id1, intern_id2); +/// ``` +/// +/// # Converting to a u32 or usize +/// +/// Normally, there should be no need to access the underlying integer +/// in a `InternId`. But if you do need to do so, you can convert to a +/// `usize` using the `as_u32` or `as_usize` methods or the `From` impls. +/// +/// ``` +/// # use ra_salsa::InternId;; +/// let intern_id = InternId::from(22_u32); +/// let value = u32::from(intern_id); +/// assert_eq!(value, 22); +/// ``` +/// +/// ## Illegal values +/// +/// Be warned, however, that `InternId` values cannot be created from +/// *arbitrary* values -- in particular large values greater than +/// `InternId::MAX` will panic. Those large values are reserved so that +/// the Rust compiler can use them as sentinel values, which means +/// that (for example) `Option` is represented in a single +/// word. +/// +/// ```should_panic +/// # use ra_salsa::InternId;; +/// InternId::from(InternId::MAX); +/// ``` +/// +/// [rfc]: https://github.com/salsa-rs/salsa-rfcs/pull/2 +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InternId { + value: NonZeroU32, +} + +impl InternId { + /// The maximum allowed `InternId`. This value can grow between + /// releases without affecting semver. + pub const MAX: u32 = 0xFFFF_FF00; + + /// Creates a new InternId. + /// + /// # Safety + /// + /// `value` must be less than `MAX` + pub const unsafe fn new_unchecked(value: u32) -> Self { + debug_assert!(value < InternId::MAX); + let value = unsafe { NonZeroU32::new_unchecked(value + 1) }; + InternId { value } + } + + /// Convert this raw-id into a u32 value. + /// + /// ``` + /// # use ra_salsa::InternId; + /// let intern_id = InternId::from(22_u32); + /// let value = intern_id.as_usize(); + /// assert_eq!(value, 22); + /// ``` + pub fn as_u32(self) -> u32 { + self.value.get() - 1 + } + + /// Convert this raw-id into a usize value. + /// + /// ``` + /// # use ra_salsa::InternId; + /// let intern_id = InternId::from(22_u32); + /// let value = intern_id.as_usize(); + /// assert_eq!(value, 22); + /// ``` + pub fn as_usize(self) -> usize { + self.as_u32() as usize + } +} + +impl From for u32 { + fn from(raw: InternId) -> u32 { + raw.as_u32() + } +} + +impl From for usize { + fn from(raw: InternId) -> usize { + raw.as_usize() + } +} + +impl From for InternId { + fn from(id: u32) -> InternId { + assert!(id < InternId::MAX); + unsafe { InternId::new_unchecked(id) } + } +} + +impl From for InternId { + fn from(id: usize) -> InternId { + assert!(id < (InternId::MAX as usize)); + unsafe { InternId::new_unchecked(id as u32) } + } +} + +impl fmt::Debug for InternId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_usize().fmt(f) + } +} + +impl fmt::Display for InternId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_usize().fmt(f) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/interned.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/interned.rs new file mode 100644 index 00000000000..359662ec6b2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/interned.rs @@ -0,0 +1,510 @@ +use crate::debug::TableEntry; +use crate::durability::Durability; +use crate::intern_id::InternId; +use crate::plumbing::CycleRecoveryStrategy; +use crate::plumbing::HasQueryGroup; +use crate::plumbing::QueryStorageMassOps; +use crate::plumbing::QueryStorageOps; +use crate::revision::Revision; +use crate::Query; +use crate::QueryTable; +use crate::{Database, DatabaseKeyIndex, QueryDb}; +use parking_lot::RwLock; +use rustc_hash::FxHashMap; +use std::collections::hash_map::Entry; +use std::fmt::Debug; +use std::hash::Hash; +use triomphe::Arc; + +const INTERN_DURABILITY: Durability = Durability::HIGH; + +/// Handles storage where the value is 'derived' by executing a +/// function (in contrast to "inputs"). +pub struct InternedStorage +where + Q: Query, + Q::Key: InternValue, + Q::Value: InternKey, +{ + group_index: u16, + tables: RwLock, Q::Key>>, +} + +/// Storage for the looking up interned things. +pub struct LookupInternedStorage +where + Q: Query, + Q::Key: InternKey, + Q::Value: InternValue, +{ + phantom: std::marker::PhantomData<(Q::Key, IQ)>, +} + +struct InternTables { + /// Map from the key to the corresponding intern-index. + map: FxHashMap, + + /// For each valid intern-index, stores the interned value. + values: Vec>>, +} + +/// Trait implemented for the "key" that results from a +/// `#[salsa::intern]` query. This is basically meant to be a +/// "newtype"'d `u32`. +pub trait InternKey { + /// Create an instance of the intern-key from a `u32` value. + fn from_intern_id(v: InternId) -> Self; + + /// Extract the `u32` with which the intern-key was created. + fn as_intern_id(&self) -> InternId; +} + +impl InternKey for InternId { + fn from_intern_id(v: InternId) -> InternId { + v + } + + fn as_intern_id(&self) -> InternId { + *self + } +} + +/// Trait implemented for the "value" that is being interned. +pub trait InternValue { + /// They key used to intern this value by. + type Key: Eq + Hash + Debug + Clone; + /// Maps the value to a key that will be used to intern it. + fn into_key(&self) -> Self::Key; + /// Calls the given function with the key that was used to intern this value. + /// + /// This is mainly used to prevent frequent cloning of the key when doing a lookup. + #[inline] + fn with_key T, T>(&self, f: F) -> T { + f(&self.into_key()) + } +} + +impl + InternValue for (A, B) +{ + type Key = Self; + #[inline] + fn into_key(&self) -> Self::Key { + self.clone() + } + #[inline] + fn with_key T, T>(&self, f: F) -> T { + f(self) + } +} + +pub trait InternValueTrivial +where + Self: Eq + Hash + Debug + Clone, +{ +} + +/// Implement [`InternValue`] trivially, that is without actually mapping at all. +impl InternValue for V { + type Key = Self; + #[inline] + fn into_key(&self) -> Self::Key { + self.clone() + } + #[inline] + fn with_key T, T>(&self, f: F) -> T { + f(self) + } +} + +impl InternValueTrivial for String {} + +#[derive(Debug)] +struct Slot { + /// key index for this slot. + key_index: u32, + + /// Value that was interned. + value: V, + + /// When was this intern'd? + /// + /// (This informs the "changed-at" result) + interned_at: Revision, +} + +impl std::panic::RefUnwindSafe for InternedStorage +where + Q: Query, + Q::Key: InternValue, + Q::Key: std::panic::RefUnwindSafe, + Q::Value: InternKey, + Q::Value: std::panic::RefUnwindSafe, +{ +} + +impl InternTables { + /// Returns the slot for the given key. + fn slot_for_key(&self, key: &K) -> Option<(Arc>, InternId)> { + let &index = self.map.get(key)?; + Some((self.slot_for_index(index), index)) + } + + /// Returns the slot at the given index. + fn slot_for_index(&self, index: InternId) -> Arc> { + let slot = &self.values[index.as_usize()]; + slot.clone() + } +} + +impl Default for InternTables +where + K: Eq + Hash, +{ + fn default() -> Self { + Self { map: Default::default(), values: Default::default() } + } +} + +type MappedKey = <::Key as InternValue>::Key; + +impl InternedStorage +where + Q: Query, + Q::Key: InternValue, + Q::Value: InternKey, +{ + /// Creates a new slot. + fn intern_index( + &self, + db: &>::DynDb, + mapped_key: MappedKey, + insert: impl FnOnce(Q::Value) -> Q::Key, + ) -> (Arc>, InternId) { + let revision_now = db.salsa_runtime().current_revision(); + + let mut tables = self.tables.write(); + let tables = &mut *tables; + let entry = match tables.map.entry(mapped_key) { + Entry::Vacant(entry) => entry, + Entry::Occupied(entry) => { + // Somebody inserted this key while we were waiting + // for the write lock. In this case, we don't need to + // update the `accessed_at` field because they should + // have already done so! + let index = *entry.get(); + let slot = &tables.values[index.as_usize()]; + return (slot.clone(), index); + } + }; + + let create_slot = |index: InternId| { + Arc::new(Slot { + key_index: index.as_u32(), + value: insert(Q::Value::from_intern_id(index)), + interned_at: revision_now, + }) + }; + + let index = InternId::from(tables.values.len()); + let slot = create_slot(index); + tables.values.push(slot.clone()); + entry.insert(index); + + (slot, index) + } + + fn intern_check(&self, key: &MappedKey) -> Option<(Arc>, InternId)> { + self.tables.read().slot_for_key(key) + } + + /// Given an index, lookup and clone its value, updating the + /// `accessed_at` time if necessary. + fn lookup_value(&self, index: InternId) -> Arc> { + self.tables.read().slot_for_index(index) + } + + fn fetch_or_insert( + &self, + db: &>::DynDb, + key: MappedKey, + insert: impl FnOnce(Q::Value) -> Q::Key, + ) -> Q::Value { + db.unwind_if_cancelled(); + let (slot, index) = match self.intern_check(&key) { + Some(i) => i, + None => self.intern_index(db, key, insert), + }; + let changed_at = slot.interned_at; + db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( + DatabaseKeyIndex { + group_index: self.group_index, + query_index: Q::QUERY_INDEX, + key_index: slot.key_index, + }, + INTERN_DURABILITY, + changed_at, + ); + ::from_intern_id(index) + } +} + +impl QueryStorageOps for InternedStorage +where + Q: Query, + Q::Key: InternValue, + Q::Value: InternKey, +{ + const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; + + fn new(group_index: u16) -> Self { + InternedStorage { group_index, tables: RwLock::new(InternTables::default()) } + } + + fn fmt_index( + &self, + _db: &>::DynDb, + index: u32, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + let intern_id = InternId::from(index); + let slot = self.lookup_value(intern_id); + write!(fmt, "{}({:?})", Q::QUERY_NAME, slot.value) + } + + fn maybe_changed_after( + &self, + db: &>::DynDb, + input: u32, + revision: Revision, + ) -> bool { + debug_assert!(revision < db.salsa_runtime().current_revision()); + let intern_id = InternId::from(input); + let slot = self.lookup_value(intern_id); + slot.maybe_changed_after(revision) + } + + fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { + db.unwind_if_cancelled(); + + let (slot, index) = match key.with_key(|key| self.intern_check(key)) { + Some(i) => i, + None => self.intern_index(db, key.into_key(), |_| key.clone()), + }; + let changed_at = slot.interned_at; + db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( + DatabaseKeyIndex { + group_index: self.group_index, + query_index: Q::QUERY_INDEX, + key_index: slot.key_index, + }, + INTERN_DURABILITY, + changed_at, + ); + ::from_intern_id(index) + } + + fn durability(&self, _db: &>::DynDb, _key: &Q::Key) -> Durability { + INTERN_DURABILITY + } + + fn entries(&self, _db: &>::DynDb) -> C + where + C: std::iter::FromIterator>, + { + let tables = self.tables.read(); + tables + .map + .values() + .map(|index| { + TableEntry::new( + tables.values[index.as_usize()].value.clone(), + Some(::from_intern_id(*index)), + ) + }) + .collect() + } +} + +impl QueryStorageMassOps for InternedStorage +where + Q: Query, + Q::Key: InternValue, + Q::Value: InternKey, +{ + fn purge(&self) { + *self.tables.write() = Default::default(); + } +} + +// Workaround for +// ``` +// IQ: for<'d> QueryDb< +// 'd, +// DynDb = >::DynDb, +// Group = >::Group, +// GroupStorage = >::GroupStorage, +// >, +// ``` +// not working to make rustc know DynDb, Group and GroupStorage being the same in `Q` and `IQ` +#[doc(hidden)] +pub trait EqualDynDb<'d, IQ>: QueryDb<'d> +where + IQ: QueryDb<'d>, +{ + fn convert_db(d: &Self::DynDb) -> &IQ::DynDb; + fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage; +} + +impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q +where + Q: QueryDb<'d, DynDb = IQ::DynDb, Group = IQ::Group, GroupStorage = IQ::GroupStorage>, + Q::DynDb: HasQueryGroup, + IQ: QueryDb<'d>, +{ + fn convert_db(d: &Self::DynDb) -> &IQ::DynDb { + d + } + fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage { + d + } +} + +impl QueryStorageOps for LookupInternedStorage +where + Q: Query, + Q::Key: InternKey, + Q::Value: InternValue, + IQ: Query>, + for<'d> Q: EqualDynDb<'d, IQ>, +{ + const CYCLE_STRATEGY: CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; + + fn new(_group_index: u16) -> Self { + LookupInternedStorage { phantom: std::marker::PhantomData } + } + + fn fmt_index( + &self, + db: &>::DynDb, + index: u32, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + let group_storage = + <>::DynDb as HasQueryGroup>::group_storage(db); + let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); + interned_storage.fmt_index(Q::convert_db(db), index, fmt) + } + + fn maybe_changed_after( + &self, + db: &>::DynDb, + input: u32, + revision: Revision, + ) -> bool { + let group_storage = + <>::DynDb as HasQueryGroup>::group_storage(db); + let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); + interned_storage.maybe_changed_after(Q::convert_db(db), input, revision) + } + + fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { + let index = key.as_intern_id(); + let group_storage = + <>::DynDb as HasQueryGroup>::group_storage(db); + let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); + let slot = interned_storage.lookup_value(index); + let value = slot.value.clone(); + let interned_at = slot.interned_at; + db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( + DatabaseKeyIndex { + group_index: interned_storage.group_index, + query_index: Q::QUERY_INDEX, + key_index: slot.key_index, + }, + INTERN_DURABILITY, + interned_at, + ); + value + } + + fn durability(&self, _db: &>::DynDb, _key: &Q::Key) -> Durability { + INTERN_DURABILITY + } + + fn entries(&self, db: &>::DynDb) -> C + where + C: std::iter::FromIterator>, + { + let group_storage = + <>::DynDb as HasQueryGroup>::group_storage(db); + let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); + let tables = interned_storage.tables.read(); + tables + .map + .values() + .map(|index| { + TableEntry::new( + ::from_intern_id(*index), + Some(tables.values[index.as_usize()].value.clone()), + ) + }) + .collect() + } +} + +impl QueryStorageMassOps for LookupInternedStorage +where + Q: Query, + Q::Key: InternKey, + Q::Value: InternValue, + IQ: Query, +{ + fn purge(&self) {} +} + +impl Slot { + fn maybe_changed_after(&self, revision: Revision) -> bool { + self.interned_at > revision + } +} + +/// Check that `Slot: Send + Sync` as long as +/// `DB::DatabaseData: Send + Sync`, which in turn implies that +/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. +#[allow(dead_code)] +fn check_send_sync() +where + K: Send + Sync, +{ + fn is_send_sync() {} + is_send_sync::>(); +} + +/// Check that `Slot: 'static` as long as +/// `DB::DatabaseData: 'static`, which in turn implies that +/// `Q::Key: 'static`, `Q::Value: 'static`. +#[allow(dead_code)] +fn check_static() +where + K: 'static, +{ + fn is_static() {} + is_static::>(); +} + +impl<'me, Q> QueryTable<'me, Q> +where + Q: Query>, + Q::Key: InternValue, + Q::Value: InternKey, +{ + /// Fetches the intern id for the given key or inserts it if it does not exist. + pub fn get_or_insert( + &self, + key: MappedKey, + insert: impl FnOnce(Q::Value) -> Q::Key, + ) -> Q::Value { + self.storage.fetch_or_insert(self.db, key, insert) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/lib.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/lib.rs new file mode 100644 index 00000000000..1b327773ec6 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/lib.rs @@ -0,0 +1,750 @@ +#![allow(clippy::type_complexity)] +#![allow(clippy::question_mark)] +#![allow(missing_docs)] +#![warn(rust_2018_idioms)] + +//! The salsa crate is a crate for incremental recomputation. It +//! permits you to define a "database" of queries with both inputs and +//! values derived from those inputs; as you set the inputs, you can +//! re-execute the derived queries and it will try to re-use results +//! from previous invocations as appropriate. + +mod derived; +mod derived_lru; +mod durability; +mod hash; +mod input; +mod intern_id; +mod interned; +mod lru; +mod revision; +mod runtime; +mod storage; + +pub mod debug; +/// Items in this module are public for implementation reasons, +/// and are exempt from the SemVer guarantees. +#[doc(hidden)] +pub mod plumbing; + +use crate::plumbing::CycleRecoveryStrategy; +use crate::plumbing::DerivedQueryStorageOps; +use crate::plumbing::InputQueryStorageOps; +use crate::plumbing::LruQueryStorageOps; +use crate::plumbing::QueryStorageMassOps; +use crate::plumbing::QueryStorageOps; +pub use crate::revision::Revision; +use std::fmt::{self, Debug}; +use std::hash::Hash; +use std::panic::AssertUnwindSafe; +use std::panic::{self, UnwindSafe}; + +pub use crate::durability::Durability; +pub use crate::intern_id::InternId; +pub use crate::interned::{InternKey, InternValue, InternValueTrivial}; +pub use crate::runtime::Runtime; +pub use crate::runtime::RuntimeId; +pub use crate::storage::Storage; + +/// The base trait which your "query context" must implement. Gives +/// access to the salsa runtime, which you must embed into your query +/// context (along with whatever other state you may require). +pub trait Database: plumbing::DatabaseOps { + /// This function is invoked at key points in the salsa + /// runtime. It permits the database to be customized and to + /// inject logging or other custom behavior. + fn salsa_event(&self, event_fn: Event) { + _ = event_fn; + } + + /// Starts unwinding the stack if the current revision is cancelled. + /// + /// This method can be called by query implementations that perform + /// potentially expensive computations, in order to speed up propagation of + /// cancellation. + /// + /// Cancellation will automatically be triggered by salsa on any query + /// invocation. + /// + /// This method should not be overridden by `Database` implementors. A + /// `salsa_event` is emitted when this method is called, so that should be + /// used instead. + #[inline] + fn unwind_if_cancelled(&self) { + let runtime = self.salsa_runtime(); + self.salsa_event(Event { + runtime_id: runtime.id(), + kind: EventKind::WillCheckCancellation, + }); + + let current_revision = runtime.current_revision(); + let pending_revision = runtime.pending_revision(); + tracing::debug!( + "unwind_if_cancelled: current_revision={:?}, pending_revision={:?}", + current_revision, + pending_revision + ); + if pending_revision > current_revision { + runtime.unwind_cancelled(); + } + } + + /// Gives access to the underlying salsa runtime. + /// + /// This method should not be overridden by `Database` implementors. + fn salsa_runtime(&self) -> &Runtime { + self.ops_salsa_runtime() + } + + /// A "synthetic write" causes the system to act *as though* some + /// input of durability `durability` has changed. This is mostly + /// useful for profiling scenarios. + /// + /// **WARNING:** Just like an ordinary write, this method triggers + /// cancellation. If you invoke it while a snapshot exists, it + /// will block until that snapshot is dropped -- if that snapshot + /// is owned by the current thread, this could trigger deadlock. + fn synthetic_write(&mut self, durability: Durability) { + plumbing::DatabaseOps::synthetic_write(self, durability) + } +} + +/// The `Event` struct identifies various notable things that can +/// occur during salsa execution. Instances of this struct are given +/// to `salsa_event`. +pub struct Event { + /// The id of the snapshot that triggered the event. Usually + /// 1-to-1 with a thread, as well. + pub runtime_id: RuntimeId, + + /// What sort of event was it. + pub kind: EventKind, +} + +impl Event { + /// Returns a type that gives a user-readable debug output. + /// Use like `println!("{:?}", index.debug(db))`. + pub fn debug<'me, D>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me + where + D: ?Sized + plumbing::DatabaseOps, + { + EventDebug { event: self, db } + } +} + +impl fmt::Debug for Event { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("Event") + .field("runtime_id", &self.runtime_id) + .field("kind", &self.kind) + .finish() + } +} + +struct EventDebug<'me, D: ?Sized> +where + D: plumbing::DatabaseOps, +{ + event: &'me Event, + db: &'me D, +} + +impl<'me, D: ?Sized> fmt::Debug for EventDebug<'me, D> +where + D: plumbing::DatabaseOps, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("Event") + .field("runtime_id", &self.event.runtime_id) + .field("kind", &self.event.kind.debug(self.db)) + .finish() + } +} + +/// An enum identifying the various kinds of events that can occur. +pub enum EventKind { + /// Occurs when we found that all inputs to a memoized value are + /// up-to-date and hence the value can be re-used without + /// executing the closure. + /// + /// Executes before the "re-used" value is returned. + DidValidateMemoizedValue { + /// The database-key for the affected value. Implements `Debug`. + database_key: DatabaseKeyIndex, + }, + + /// Indicates that another thread (with id `other_runtime_id`) is processing the + /// given query (`database_key`), so we will block until they + /// finish. + /// + /// Executes after we have registered with the other thread but + /// before they have answered us. + /// + /// (NB: you can find the `id` of the current thread via the + /// `salsa_runtime`) + WillBlockOn { + /// The id of the runtime we will block on. + other_runtime_id: RuntimeId, + + /// The database-key for the affected value. Implements `Debug`. + database_key: DatabaseKeyIndex, + }, + + /// Indicates that the function for this query will be executed. + /// This is either because it has never executed before or because + /// its inputs may be out of date. + WillExecute { + /// The database-key for the affected value. Implements `Debug`. + database_key: DatabaseKeyIndex, + }, + + /// Indicates that `unwind_if_cancelled` was called and salsa will check if + /// the current revision has been cancelled. + WillCheckCancellation, +} + +impl EventKind { + /// Returns a type that gives a user-readable debug output. + /// Use like `println!("{:?}", index.debug(db))`. + pub fn debug<'me, D>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me + where + D: ?Sized + plumbing::DatabaseOps, + { + EventKindDebug { kind: self, db } + } +} + +impl fmt::Debug for EventKind { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EventKind::DidValidateMemoizedValue { database_key } => fmt + .debug_struct("DidValidateMemoizedValue") + .field("database_key", database_key) + .finish(), + EventKind::WillBlockOn { other_runtime_id, database_key } => fmt + .debug_struct("WillBlockOn") + .field("other_runtime_id", other_runtime_id) + .field("database_key", database_key) + .finish(), + EventKind::WillExecute { database_key } => { + fmt.debug_struct("WillExecute").field("database_key", database_key).finish() + } + EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(), + } + } +} + +struct EventKindDebug<'me, D: ?Sized> +where + D: plumbing::DatabaseOps, +{ + kind: &'me EventKind, + db: &'me D, +} + +impl<'me, D: ?Sized> fmt::Debug for EventKindDebug<'me, D> +where + D: plumbing::DatabaseOps, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + EventKind::DidValidateMemoizedValue { database_key } => fmt + .debug_struct("DidValidateMemoizedValue") + .field("database_key", &database_key.debug(self.db)) + .finish(), + EventKind::WillBlockOn { other_runtime_id, database_key } => fmt + .debug_struct("WillBlockOn") + .field("other_runtime_id", &other_runtime_id) + .field("database_key", &database_key.debug(self.db)) + .finish(), + EventKind::WillExecute { database_key } => fmt + .debug_struct("WillExecute") + .field("database_key", &database_key.debug(self.db)) + .finish(), + EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(), + } + } +} + +/// Indicates a database that also supports parallel query +/// evaluation. All of Salsa's base query support is capable of +/// parallel execution, but for it to work, your query key/value types +/// must also be `Send`, as must any additional data in your database. +pub trait ParallelDatabase: Database + Send { + /// Creates a second handle to the database that holds the + /// database fixed at a particular revision. So long as this + /// "frozen" handle exists, any attempt to [`set`] an input will + /// block. + /// + /// [`set`]: struct.QueryTable.html#method.set + /// + /// This is the method you are meant to use most of the time in a + /// parallel setting where modifications may arise asynchronously + /// (e.g., a language server). In this context, it is common to + /// wish to "fork off" a snapshot of the database performing some + /// series of queries in parallel and arranging the results. Using + /// this method for that purpose ensures that those queries will + /// see a consistent view of the database (it is also advisable + /// for those queries to use the [`Database::unwind_if_cancelled`] + /// method to check for cancellation). + /// + /// # Panics + /// + /// It is not permitted to create a snapshot from inside of a + /// query. Attepting to do so will panic. + /// + /// # Deadlock warning + /// + /// The intended pattern for snapshots is that, once created, they + /// are sent to another thread and used from there. As such, the + /// `snapshot` acquires a "read lock" on the database -- + /// therefore, so long as the `snapshot` is not dropped, any + /// attempt to `set` a value in the database will block. If the + /// `snapshot` is owned by the same thread that is attempting to + /// `set`, this will cause a problem. + /// + /// # How to implement this + /// + /// Typically, this method will create a second copy of your + /// database type (`MyDatabaseType`, in the example below), + /// cloning over each of the fields from `self` into this new + /// copy. For the field that stores the salsa runtime, you should + /// use [the `Runtime::snapshot` method][rfm] to create a snapshot of the + /// runtime. Finally, package up the result using `Snapshot::new`, + /// which is a simple wrapper type that only gives `&self` access + /// to the database within (thus preventing the use of methods + /// that may mutate the inputs): + /// + /// [rfm]: struct.Runtime.html#method.snapshot + /// + /// ```rust,ignore + /// impl ParallelDatabase for MyDatabaseType { + /// fn snapshot(&self) -> Snapshot { + /// Snapshot::new( + /// MyDatabaseType { + /// runtime: self.runtime.snapshot(self), + /// other_field: self.other_field.clone(), + /// } + /// ) + /// } + /// } + /// ``` + fn snapshot(&self) -> Snapshot; +} + +/// Simple wrapper struct that takes ownership of a database `DB` and +/// only gives `&self` access to it. See [the `snapshot` method][fm] +/// for more details. +/// +/// [fm]: trait.ParallelDatabase.html#method.snapshot +#[derive(Debug)] +pub struct Snapshot +where + DB: ParallelDatabase, +{ + db: DB, +} + +impl Snapshot +where + DB: ParallelDatabase, +{ + /// Creates a `Snapshot` that wraps the given database handle + /// `db`. From this point forward, only shared references to `db` + /// will be possible. + pub fn new(db: DB) -> Self { + Snapshot { db } + } +} + +impl std::ops::Deref for Snapshot +where + DB: ParallelDatabase, +{ + type Target = DB; + + fn deref(&self) -> &DB { + &self.db + } +} + +/// An integer that uniquely identifies a particular query instance within the +/// database. Used to track dependencies between queries. Fully ordered and +/// equatable but those orderings are arbitrary, and meant to be used only for +/// inserting into maps and the like. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct DatabaseKeyIndex { + group_index: u16, + query_index: u16, + key_index: u32, +} + +impl DatabaseKeyIndex { + /// Returns the index of the query group containing this key. + #[inline] + pub fn group_index(self) -> u16 { + self.group_index + } + + /// Returns the index of the query within its query group. + #[inline] + pub fn query_index(self) -> u16 { + self.query_index + } + + /// Returns the index of this particular query key within the query. + #[inline] + pub fn key_index(self) -> u32 { + self.key_index + } + + /// Returns a type that gives a user-readable debug output. + /// Use like `println!("{:?}", index.debug(db))`. + pub fn debug(self, db: &D) -> impl std::fmt::Debug + '_ + where + D: ?Sized + plumbing::DatabaseOps, + { + DatabaseKeyIndexDebug { index: self, db } + } +} + +/// Helper type for `DatabaseKeyIndex::debug` +struct DatabaseKeyIndexDebug<'me, D: ?Sized> +where + D: plumbing::DatabaseOps, +{ + index: DatabaseKeyIndex, + db: &'me D, +} + +impl std::fmt::Debug for DatabaseKeyIndexDebug<'_, D> +where + D: plumbing::DatabaseOps, +{ + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.db.fmt_index(self.index, fmt) + } +} + +/// Trait implements by all of the "special types" associated with +/// each of your queries. +/// +/// Base trait of `Query` that has a lifetime parameter to allow the `DynDb` to be non-'static. +pub trait QueryDb<'d>: Sized { + /// Dyn version of the associated trait for this query group. + type DynDb: ?Sized + Database + HasQueryGroup + 'd; + + /// Associate query group struct. + type Group: plumbing::QueryGroup; + + /// Generated struct that contains storage for all queries in a group. + type GroupStorage; +} + +/// Trait implements by all of the "special types" associated with +/// each of your queries. +pub trait Query: Debug + Default + Sized + for<'d> QueryDb<'d> { + /// Type that you give as a parameter -- for queries with zero + /// or more than one input, this will be a tuple. + type Key: Clone + Debug + Hash + Eq; + + /// What value does the query return? + type Value: Clone + Debug; + + /// Internal struct storing the values for the query. + // type Storage: plumbing::QueryStorageOps; + type Storage; + + /// A unique index identifying this query within the group. + const QUERY_INDEX: u16; + + /// Name of the query method (e.g., `foo`) + const QUERY_NAME: &'static str; + + /// Extract storage for this query from the storage for its group. + fn query_storage<'a>( + group_storage: &'a >::GroupStorage, + ) -> &'a std::sync::Arc; + + /// Extract storage for this query from the storage for its group. + fn query_storage_mut<'a>( + group_storage: &'a >::GroupStorage, + ) -> &'a std::sync::Arc; +} + +/// Return value from [the `query` method] on `Database`. +/// Gives access to various less common operations on queries. +/// +/// [the `query` method]: trait.Database.html#method.query +pub struct QueryTable<'me, Q> +where + Q: Query, +{ + db: &'me >::DynDb, + storage: &'me Q::Storage, +} + +impl<'me, Q> QueryTable<'me, Q> +where + Q: Query, + Q::Storage: QueryStorageOps, +{ + /// Constructs a new `QueryTable`. + pub fn new(db: &'me >::DynDb, storage: &'me Q::Storage) -> Self { + Self { db, storage } + } + + /// Execute the query on a given input. Usually it's easier to + /// invoke the trait method directly. Note that for variadic + /// queries (those with no inputs, or those with more than one + /// input) the key will be a tuple. + pub fn get(&self, key: Q::Key) -> Q::Value { + self.storage.fetch(self.db, &key) + } + + /// Completely clears the storage for this query. + /// + /// This method breaks internal invariants of salsa, so any further queries + /// might return nonsense results. It is useful only in very specific + /// circumstances -- for example, when one wants to observe which values + /// dropped together with the table + pub fn purge(&self) + where + Q::Storage: plumbing::QueryStorageMassOps, + { + self.storage.purge(); + } + + pub fn storage(&self) -> &::Storage { + self.storage + } +} + +/// Return value from [the `query_mut` method] on `Database`. +/// Gives access to the `set` method, notably, that is used to +/// set the value of an input query. +/// +/// [the `query_mut` method]: trait.Database.html#method.query_mut +pub struct QueryTableMut<'me, Q> +where + Q: Query + 'me, +{ + runtime: &'me mut Runtime, + storage: &'me Q::Storage, +} + +impl<'me, Q> QueryTableMut<'me, Q> +where + Q: Query, +{ + /// Constructs a new `QueryTableMut`. + pub fn new(runtime: &'me mut Runtime, storage: &'me Q::Storage) -> Self { + Self { runtime, storage } + } + + /// Assign a value to an "input query". Must be used outside of + /// an active query computation. + /// + /// If you are using `snapshot`, see the notes on blocking + /// and cancellation on [the `query_mut` method]. + /// + /// [the `query_mut` method]: trait.Database.html#method.query_mut + pub fn set(&mut self, key: Q::Key, value: Q::Value) + where + Q::Storage: plumbing::InputQueryStorageOps, + { + self.set_with_durability(key, value, Durability::LOW); + } + + /// Assign a value to an "input query", with the additional + /// promise that this value will **never change**. Must be used + /// outside of an active query computation. + /// + /// If you are using `snapshot`, see the notes on blocking + /// and cancellation on [the `query_mut` method]. + /// + /// [the `query_mut` method]: trait.Database.html#method.query_mut + pub fn set_with_durability(&mut self, key: Q::Key, value: Q::Value, durability: Durability) + where + Q::Storage: plumbing::InputQueryStorageOps, + { + self.storage.set(self.runtime, &key, value, durability); + } + + /// Sets the size of LRU cache of values for this query table. + /// + /// That is, at most `cap` values will be preset in the table at the same + /// time. This helps with keeping maximum memory usage under control, at the + /// cost of potential extra recalculations of evicted values. + /// + /// If `cap` is zero, all values are preserved, this is the default. + pub fn set_lru_capacity(&self, cap: u16) + where + Q::Storage: plumbing::LruQueryStorageOps, + { + self.storage.set_lru_capacity(cap); + } + + /// Marks the computed value as outdated. + /// + /// This causes salsa to re-execute the query function on the next access to + /// the query, even if all dependencies are up to date. + /// + /// This is most commonly used as part of the [on-demand input + /// pattern](https://salsa-rs.github.io/salsa/common_patterns/on_demand_inputs.html). + pub fn invalidate(&mut self, key: &Q::Key) + where + Q::Storage: plumbing::DerivedQueryStorageOps, + { + self.storage.invalidate(self.runtime, key) + } +} + +/// A panic payload indicating that execution of a salsa query was cancelled. +/// +/// This can occur for a few reasons: +/// * +/// * +/// * +#[derive(Debug)] +#[non_exhaustive] +pub enum Cancelled { + /// The query was operating on revision R, but there is a pending write to move to revision R+1. + #[non_exhaustive] + PendingWrite, + + /// The query was blocked on another thread, and that thread panicked. + #[non_exhaustive] + PropagatedPanic, +} + +impl Cancelled { + fn throw(self) -> ! { + // We use resume and not panic here to avoid running the panic + // hook (that is, to avoid collecting and printing backtrace). + std::panic::resume_unwind(Box::new(self)); + } + + /// Runs `f`, and catches any salsa cancellation. + pub fn catch(f: F) -> Result + where + F: FnOnce() -> T + UnwindSafe, + { + match panic::catch_unwind(f) { + Ok(t) => Ok(t), + Err(payload) => match payload.downcast() { + Ok(cancelled) => Err(*cancelled), + Err(payload) => panic::resume_unwind(payload), + }, + } + } +} + +impl std::fmt::Display for Cancelled { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let why = match self { + Cancelled::PendingWrite => "pending write", + Cancelled::PropagatedPanic => "propagated panic", + }; + f.write_str("cancelled because of ")?; + f.write_str(why) + } +} + +impl std::error::Error for Cancelled {} + +/// Captures the participants of a cycle that occurred when executing a query. +/// +/// This type is meant to be used to help give meaningful error messages to the +/// user or to help salsa developers figure out why their program is resulting +/// in a computation cycle. +/// +/// It is used in a few ways: +/// +/// * During [cycle recovery](https://https://salsa-rs.github.io/salsa/cycles/fallback.html), +/// where it is given to the fallback function. +/// * As the panic value when an unexpected cycle (i.e., a cycle where one or more participants +/// lacks cycle recovery information) occurs. +/// +/// You can read more about cycle handling in +/// the [salsa book](https://https://salsa-rs.github.io/salsa/cycles.html). +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Cycle { + participants: plumbing::CycleParticipants, +} + +impl Cycle { + pub(crate) fn new(participants: plumbing::CycleParticipants) -> Self { + Self { participants } + } + + /// True if two `Cycle` values represent the same cycle. + pub(crate) fn is(&self, cycle: &Cycle) -> bool { + triomphe::Arc::ptr_eq(&self.participants, &cycle.participants) + } + + pub(crate) fn throw(self) -> ! { + tracing::debug!("throwing cycle {:?}", self); + std::panic::resume_unwind(Box::new(self)) + } + + pub(crate) fn catch(execute: impl FnOnce() -> T) -> Result { + match std::panic::catch_unwind(AssertUnwindSafe(execute)) { + Ok(v) => Ok(v), + Err(err) => match err.downcast::() { + Ok(cycle) => Err(*cycle), + Err(other) => std::panic::resume_unwind(other), + }, + } + } + + /// Iterate over the [`DatabaseKeyIndex`] for each query participating + /// in the cycle. The start point of this iteration within the cycle + /// is arbitrary but deterministic, but the ordering is otherwise determined + /// by the execution. + pub fn participant_keys(&self) -> impl Iterator + '_ { + self.participants.iter().copied() + } + + /// Returns a vector with the debug information for + /// all the participants in the cycle. + pub fn all_participants(&self, db: &DB) -> Vec { + self.participant_keys().map(|d| format!("{:?}", d.debug(db))).collect() + } + + /// Returns a vector with the debug information for + /// those participants in the cycle that lacked recovery + /// information. + pub fn unexpected_participants(&self, db: &DB) -> Vec { + self.participant_keys() + .filter(|&d| db.cycle_recovery_strategy(d) == CycleRecoveryStrategy::Panic) + .map(|d| format!("{:?}", d.debug(db))) + .collect() + } + + /// Returns a "debug" view onto this strict that can be used to print out information. + pub fn debug<'me, DB: ?Sized + Database>(&'me self, db: &'me DB) -> impl std::fmt::Debug + 'me { + struct UnexpectedCycleDebug<'me> { + c: &'me Cycle, + db: &'me dyn Database, + } + + impl<'me> std::fmt::Debug for UnexpectedCycleDebug<'me> { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("UnexpectedCycle") + .field("all_participants", &self.c.all_participants(self.db)) + .field("unexpected_participants", &self.c.unexpected_participants(self.db)) + .finish() + } + } + + UnexpectedCycleDebug { c: self, db: db.ops_database() } + } +} + +// Re-export the procedural macros. +#[allow(unused_imports)] +#[macro_use] +extern crate ra_salsa_macros; +use plumbing::HasQueryGroup; +pub use ra_salsa_macros::*; diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/lru.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/lru.rs new file mode 100644 index 00000000000..a6f96beeab1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/lru.rs @@ -0,0 +1,325 @@ +use oorandom::Rand64; +use parking_lot::Mutex; +use std::fmt::Debug; +use std::sync::atomic::AtomicU16; +use std::sync::atomic::Ordering; +use triomphe::Arc; + +/// A simple and approximate concurrent lru list. +/// +/// We assume but do not verify that each node is only used with one +/// list. If this is not the case, it is not *unsafe*, but panics and +/// weird results will ensue. +/// +/// Each "node" in the list is of type `Node` and must implement +/// `LruNode`, which is a trait that gives access to a field that +/// stores the index in the list. This index gives us a rough idea of +/// how recently the node has been used. +#[derive(Debug)] +pub(crate) struct Lru +where + Node: LruNode, +{ + green_zone: AtomicU16, + data: Mutex>, +} + +#[derive(Debug)] +struct LruData { + end_red_zone: u16, + end_yellow_zone: u16, + end_green_zone: u16, + rng: Rand64, + entries: Vec>, +} + +pub(crate) trait LruNode: Sized + Debug { + fn lru_index(&self) -> &LruIndex; +} + +#[derive(Debug)] +pub(crate) struct LruIndex { + /// Index in the appropriate LRU list, or std::u16::MAX if not a + /// member. + index: AtomicU16, +} + +impl Default for Lru +where + Node: LruNode, +{ + fn default() -> Self { + Lru::new() + } +} + +// We always use a fixed seed for our randomness so that we have +// predictable results. +const LRU_SEED: &str = "Hello, Rustaceans"; + +impl Lru +where + Node: LruNode, +{ + /// Creates a new LRU list where LRU caching is disabled. + pub(crate) fn new() -> Self { + Self::with_seed(LRU_SEED) + } + + #[cfg_attr(not(test), allow(dead_code))] + fn with_seed(seed: &str) -> Self { + Lru { green_zone: AtomicU16::new(0), data: Mutex::new(LruData::with_seed(seed)) } + } + + /// Adjust the total number of nodes permitted to have a value at + /// once. If `len` is zero, this disables LRU caching completely. + pub(crate) fn set_lru_capacity(&self, len: u16) { + let mut data = self.data.lock(); + + // We require each zone to have at least 1 slot. Therefore, + // the length cannot be just 1 or 2. + if len == 0 { + self.green_zone.store(0, Ordering::Release); + data.resize(0, 0, 0); + } else { + let len = std::cmp::max(len, 3); + + // Top 10% is the green zone. This must be at least length 1. + let green_zone = std::cmp::max(len / 10, 1); + + // Next 20% is the yellow zone. + let yellow_zone = std::cmp::max(len / 5, 1); + + // Remaining 70% is the red zone. + let red_zone = len - yellow_zone - green_zone; + + // We need quick access to the green zone. + self.green_zone.store(green_zone, Ordering::Release); + + // Resize existing array. + data.resize(green_zone, yellow_zone, red_zone); + } + } + + /// Records that `node` was used. This may displace an old node (if the LRU limits are + pub(crate) fn record_use(&self, node: &Arc) -> Option> { + tracing::debug!("record_use(node={:?})", node); + + // Load green zone length and check if the LRU cache is even enabled. + let green_zone = self.green_zone.load(Ordering::Acquire); + tracing::debug!("record_use: green_zone={}", green_zone); + if green_zone == 0 { + return None; + } + + // Find current index of list (if any) and the current length + // of our green zone. + let index = node.lru_index().load(); + tracing::debug!("record_use: index={}", index); + + // Already a member of the list, and in the green zone -- nothing to do! + if index < green_zone { + return None; + } + + self.data.lock().record_use(node) + } + + pub(crate) fn purge(&self) { + self.green_zone.store(0, Ordering::SeqCst); + *self.data.lock() = LruData::with_seed(LRU_SEED); + } +} + +impl LruData +where + Node: LruNode, +{ + fn with_seed(seed_str: &str) -> Self { + Self::with_rng(rng_with_seed(seed_str)) + } + + fn with_rng(rng: Rand64) -> Self { + LruData { end_yellow_zone: 0, end_green_zone: 0, end_red_zone: 0, entries: Vec::new(), rng } + } + + fn green_zone(&self) -> std::ops::Range { + 0..self.end_green_zone + } + + fn yellow_zone(&self) -> std::ops::Range { + self.end_green_zone..self.end_yellow_zone + } + + fn red_zone(&self) -> std::ops::Range { + self.end_yellow_zone..self.end_red_zone + } + + fn resize(&mut self, len_green_zone: u16, len_yellow_zone: u16, len_red_zone: u16) { + self.end_green_zone = len_green_zone; + self.end_yellow_zone = self.end_green_zone + len_yellow_zone; + self.end_red_zone = self.end_yellow_zone + len_red_zone; + let entries = + std::mem::replace(&mut self.entries, Vec::with_capacity(self.end_red_zone as usize)); + + tracing::debug!("green_zone = {:?}", self.green_zone()); + tracing::debug!("yellow_zone = {:?}", self.yellow_zone()); + tracing::debug!("red_zone = {:?}", self.red_zone()); + + // We expect to resize when the LRU cache is basically empty. + // So just forget all the old LRU indices to start. + for entry in entries { + entry.lru_index().clear(); + } + } + + /// Records that a node was used. If it is already a member of the + /// LRU list, it is promoted to the green zone (unless it's + /// already there). Otherwise, it is added to the list first and + /// *then* promoted to the green zone. Adding a new node to the + /// list may displace an old member of the red zone, in which case + /// that is returned. + fn record_use(&mut self, node: &Arc) -> Option> { + tracing::debug!("record_use(node={:?})", node); + + // NB: When this is invoked, we have typically already loaded + // the LRU index (to check if it is in green zone). But that + // check was done outside the lock and -- for all we know -- + // the index may have changed since. So we always reload. + let index = node.lru_index().load(); + + if index < self.end_green_zone { + None + } else if index < self.end_yellow_zone { + self.promote_yellow_to_green(node, index); + None + } else if index < self.end_red_zone { + self.promote_red_to_green(node, index); + None + } else { + self.insert_new(node) + } + } + + /// Inserts a node that is not yet a member of the LRU list. If + /// the list is at capacity, this can displace an existing member. + fn insert_new(&mut self, node: &Arc) -> Option> { + debug_assert!(!node.lru_index().is_in_lru()); + + // Easy case: we still have capacity. Push it, and then promote + // it up to the appropriate zone. + let len = self.entries.len() as u16; + if len < self.end_red_zone { + self.entries.push(node.clone()); + node.lru_index().store(len); + tracing::debug!("inserted node {:?} at {}", node, len); + return self.record_use(node); + } + + // Harder case: no capacity. Create some by evicting somebody from red + // zone and then promoting. + let victim_index = self.pick_index(self.red_zone()); + let victim_node = std::mem::replace(&mut self.entries[victim_index as usize], node.clone()); + tracing::debug!("evicting red node {:?} from {}", victim_node, victim_index); + victim_node.lru_index().clear(); + self.promote_red_to_green(node, victim_index); + Some(victim_node) + } + + /// Promotes the node `node`, stored at `red_index` (in the red + /// zone), into a green index, demoting yellow/green nodes at + /// random. + /// + /// NB: It is not required that `node.lru_index()` is up-to-date + /// when entering this method. + fn promote_red_to_green(&mut self, node: &Arc, red_index: u16) { + debug_assert!(self.red_zone().contains(&red_index)); + + // Pick a yellow at random and switch places with it. + // + // Subtle: we do not update `node.lru_index` *yet* -- we're + // going to invoke `self.promote_yellow` next, and it will get + // updated then. + let yellow_index = self.pick_index(self.yellow_zone()); + tracing::debug!( + "demoting yellow node {:?} from {} to red at {}", + self.entries[yellow_index as usize], + yellow_index, + red_index, + ); + self.entries.swap(yellow_index as usize, red_index as usize); + self.entries[red_index as usize].lru_index().store(red_index); + + // Now move ourselves up into the green zone. + self.promote_yellow_to_green(node, yellow_index); + } + + /// Promotes the node `node`, stored at `yellow_index` (in the + /// yellow zone), into a green index, demoting a green node at + /// random to replace it. + /// + /// NB: It is not required that `node.lru_index()` is up-to-date + /// when entering this method. + fn promote_yellow_to_green(&mut self, node: &Arc, yellow_index: u16) { + debug_assert!(self.yellow_zone().contains(&yellow_index)); + + // Pick a yellow at random and switch places with it. + let green_index = self.pick_index(self.green_zone()); + tracing::debug!( + "demoting green node {:?} from {} to yellow at {}", + self.entries[green_index as usize], + green_index, + yellow_index + ); + self.entries.swap(green_index as usize, yellow_index as usize); + self.entries[yellow_index as usize].lru_index().store(yellow_index); + node.lru_index().store(green_index); + + tracing::debug!("promoted {:?} to green index {}", node, green_index); + } + + fn pick_index(&mut self, zone: std::ops::Range) -> u16 { + let end_index = std::cmp::min(zone.end, self.entries.len() as u16); + self.rng.rand_range(zone.start as u64..end_index as u64) as u16 + } +} + +impl Default for LruIndex { + fn default() -> Self { + Self { index: AtomicU16::new(u16::MAX) } + } +} + +impl LruIndex { + fn load(&self) -> u16 { + self.index.load(Ordering::Acquire) // see note on ordering below + } + + fn store(&self, value: u16) { + self.index.store(value, Ordering::Release) // see note on ordering below + } + + fn clear(&self) { + self.store(u16::MAX); + } + + fn is_in_lru(&self) -> bool { + self.load() != u16::MAX + } +} + +fn rng_with_seed(seed_str: &str) -> Rand64 { + let mut seed: [u8; 16] = [0; 16]; + for (i, &b) in seed_str.as_bytes().iter().take(16).enumerate() { + seed[i] = b; + } + Rand64::new(u128::from_le_bytes(seed)) +} + +// A note on ordering: +// +// I chose to use AcqRel for the ordering but I don't think it's +// strictly needed. All writes occur under a lock, so they should be +// ordered w/r/t one another. As for the reads, they can occur +// outside the lock, but they don't themselves enable dependent reads +// -- if the reads are out of bounds, we would acquire a lock. diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/plumbing.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/plumbing.rs new file mode 100644 index 00000000000..e96b9daa979 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/plumbing.rs @@ -0,0 +1,245 @@ +#![allow(missing_docs)] + +use crate::debug::TableEntry; +use crate::durability::Durability; +use crate::Cycle; +use crate::Database; +use crate::Query; +use crate::QueryTable; +use crate::QueryTableMut; +use std::borrow::Borrow; +use std::fmt::Debug; +use std::hash::Hash; +use triomphe::Arc; + +pub use crate::derived::MemoizedStorage; +pub use crate::derived_lru::DependencyStorage as LruDependencyStorage; +pub use crate::derived_lru::MemoizedStorage as LruMemoizedStorage; +pub use crate::input::{InputStorage, UnitInputStorage}; +pub use crate::interned::InternedStorage; +pub use crate::interned::LookupInternedStorage; +pub use crate::{revision::Revision, DatabaseKeyIndex, QueryDb, Runtime}; + +/// Defines various associated types. An impl of this +/// should be generated for your query-context type automatically by +/// the `database_storage` macro, so you shouldn't need to mess +/// with this trait directly. +pub trait DatabaseStorageTypes: Database { + /// Defines the "storage type", where all the query data is kept. + /// This type is defined by the `database_storage` macro. + type DatabaseStorage: Default; +} + +/// Internal operations that the runtime uses to operate on the database. +pub trait DatabaseOps { + /// Upcast this type to a `dyn Database`. + fn ops_database(&self) -> &dyn Database; + + /// Gives access to the underlying salsa runtime. + fn ops_salsa_runtime(&self) -> &Runtime; + + /// A "synthetic write" causes the system to act *as though* some + /// input of durability `durability` has changed. This is mostly + /// useful for profiling scenarios. + /// + /// **WARNING:** Just like an ordinary write, this method triggers + /// cancellation. If you invoke it while a snapshot exists, it + /// will block until that snapshot is dropped -- if that snapshot + /// is owned by the current thread, this could trigger deadlock. + fn synthetic_write(&mut self, durability: Durability); + + /// Formats a database key index in a human readable fashion. + fn fmt_index( + &self, + index: DatabaseKeyIndex, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result; + + /// True if the computed value for `input` may have changed since `revision`. + fn maybe_changed_after(&self, input: DatabaseKeyIndex, revision: Revision) -> bool; + + /// Find the `CycleRecoveryStrategy` for a given input. + fn cycle_recovery_strategy(&self, input: DatabaseKeyIndex) -> CycleRecoveryStrategy; + + /// Executes the callback for each kind of query. + fn for_each_query(&self, op: &mut dyn FnMut(&dyn QueryStorageMassOps)); +} + +/// Internal operations performed on the query storage as a whole +/// (note that these ops do not need to know the identity of the +/// query, unlike `QueryStorageOps`). +pub trait QueryStorageMassOps { + fn purge(&self); +} + +pub trait DatabaseKey: Clone + Debug + Eq + Hash {} + +pub trait QueryFunction: Query { + /// See `CycleRecoveryStrategy` + const CYCLE_STRATEGY: CycleRecoveryStrategy; + + fn execute(db: &>::DynDb, key: Self::Key) -> Self::Value; + + fn cycle_fallback( + db: &>::DynDb, + cycle: &Cycle, + key: &Self::Key, + ) -> Self::Value { + let _ = (db, cycle, key); + panic!("query `{:?}` doesn't support cycle fallback", Self::default()) + } +} + +/// Cycle recovery strategy: Is this query capable of recovering from +/// a cycle that results from executing the function? If so, how? +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum CycleRecoveryStrategy { + /// Cannot recover from cycles: panic. + /// + /// This is the default. It is also what happens if a cycle + /// occurs and the queries involved have different recovery + /// strategies. + /// + /// In the case of a failure due to a cycle, the panic + /// value will be XXX (FIXME). + Panic, + + /// Recovers from cycles by storing a sentinel value. + /// + /// This value is computed by the `QueryFunction::cycle_fallback` + /// function. + Fallback, +} + +/// Create a query table, which has access to the storage for the query +/// and offers methods like `get`. +pub fn get_query_table<'me, Q>(db: &'me >::DynDb) -> QueryTable<'me, Q> +where + Q: Query + 'me, + Q::Storage: QueryStorageOps, +{ + let group_storage: &Q::GroupStorage = HasQueryGroup::group_storage(db); + let query_storage: &Q::Storage = Q::query_storage(group_storage); + QueryTable::new(db, query_storage) +} + +/// Create a mutable query table, which has access to the storage +/// for the query and offers methods like `set`. +pub fn get_query_table_mut<'me, Q>(db: &'me mut >::DynDb) -> QueryTableMut<'me, Q> +where + Q: Query, +{ + let (group_storage, runtime) = HasQueryGroup::group_storage_mut(db); + let query_storage = Q::query_storage_mut(group_storage); + QueryTableMut::new(runtime, &**query_storage) +} + +pub trait QueryGroup: Sized { + type GroupStorage; + + /// Dyn version of the associated database trait. + type DynDb: ?Sized + Database + HasQueryGroup; +} + +/// Trait implemented by a database for each group that it supports. +/// `S` and `K` are the types for *group storage* and *group key*, respectively. +pub trait HasQueryGroup: Database +where + G: QueryGroup, +{ + /// Access the group storage struct from the database. + fn group_storage(&self) -> &G::GroupStorage; + + /// Access the group storage struct from the database. + /// Also returns a ref to the `Runtime`, since otherwise + /// the database is borrowed and one cannot get access to it. + fn group_storage_mut(&mut self) -> (&G::GroupStorage, &mut Runtime); +} + +// ANCHOR:QueryStorageOps +pub trait QueryStorageOps +where + Self: QueryStorageMassOps, + Q: Query, +{ + // ANCHOR_END:QueryStorageOps + + /// See CycleRecoveryStrategy + const CYCLE_STRATEGY: CycleRecoveryStrategy; + + fn new(group_index: u16) -> Self; + + /// Format a database key index in a suitable way. + fn fmt_index( + &self, + db: &>::DynDb, + index: u32, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result; + + // ANCHOR:maybe_changed_after + /// True if the value of `input`, which must be from this query, may have + /// changed after the given revision ended. + /// + /// This function should only be invoked with a revision less than the current + /// revision. + fn maybe_changed_after( + &self, + db: &>::DynDb, + index: u32, + revision: Revision, + ) -> bool; + // ANCHOR_END:maybe_changed_after + + fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy { + Self::CYCLE_STRATEGY + } + + // ANCHOR:fetch + /// Execute the query, returning the result (often, the result + /// will be memoized). This is the "main method" for + /// queries. + /// + /// Returns `Err` in the event of a cycle, meaning that computing + /// the value for this `key` is recursively attempting to fetch + /// itself. + fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value; + // ANCHOR_END:fetch + + /// Returns the durability associated with a given key. + fn durability(&self, db: &>::DynDb, key: &Q::Key) -> Durability; + + /// Get the (current) set of the entries in the query storage + fn entries(&self, db: &>::DynDb) -> C + where + C: std::iter::FromIterator>; +} + +/// An optional trait that is implemented for "user mutable" storage: +/// that is, storage whose value is not derived from other storage but +/// is set independently. +pub trait InputQueryStorageOps +where + Q: Query, +{ + fn set(&self, runtime: &mut Runtime, key: &Q::Key, new_value: Q::Value, durability: Durability); +} + +/// An optional trait that is implemented for "user mutable" storage: +/// that is, storage whose value is not derived from other storage but +/// is set independently. +pub trait LruQueryStorageOps { + fn set_lru_capacity(&self, new_capacity: u16); +} + +pub trait DerivedQueryStorageOps +where + Q: Query, +{ + fn invalidate(&self, runtime: &mut Runtime, key: &S) + where + S: Eq + Hash, + Q::Key: Borrow; +} + +pub type CycleParticipants = Arc>; diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/revision.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/revision.rs new file mode 100644 index 00000000000..7f4c333fb19 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/revision.rs @@ -0,0 +1,66 @@ +use std::num::NonZeroU32; +use std::sync::atomic::{AtomicU32, Ordering}; + +/// Value of the initial revision, as a u32. We don't use 0 +/// because we want to use a `NonZeroU32`. +const START: u32 = 1; + +/// A unique identifier for the current version of the database; each +/// time an input is changed, the revision number is incremented. +/// `Revision` is used internally to track which values may need to be +/// recomputed, but is not something you should have to interact with +/// directly as a user of salsa. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Revision { + generation: NonZeroU32, +} + +impl Revision { + pub(crate) fn start() -> Self { + Self::from(START) + } + + pub(crate) fn from(g: u32) -> Self { + Self { generation: NonZeroU32::new(g).unwrap() } + } + + pub(crate) fn next(self) -> Revision { + Self::from(self.generation.get() + 1) + } + + fn as_u32(self) -> u32 { + self.generation.get() + } +} + +impl std::fmt::Debug for Revision { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(fmt, "R{}", self.generation) + } +} + +#[derive(Debug)] +pub(crate) struct AtomicRevision { + data: AtomicU32, +} + +impl AtomicRevision { + pub(crate) const fn start() -> Self { + Self { data: AtomicU32::new(START) } + } + + pub(crate) fn load(&self) -> Revision { + Revision::from(self.data.load(Ordering::SeqCst)) + } + + pub(crate) fn store(&self, r: Revision) { + self.data.store(r.as_u32(), Ordering::SeqCst); + } + + /// Increment by 1, returning previous value. + pub(crate) fn fetch_then_increment(&self) -> Revision { + let v = self.data.fetch_add(1, Ordering::SeqCst); + assert!(v != u32::MAX, "revision overflow"); + Revision::from(v) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/runtime.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/runtime.rs new file mode 100644 index 00000000000..5fe5f4b46d3 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/runtime.rs @@ -0,0 +1,668 @@ +use crate::durability::Durability; +use crate::hash::FxIndexSet; +use crate::plumbing::CycleRecoveryStrategy; +use crate::revision::{AtomicRevision, Revision}; +use crate::{Cancelled, Cycle, Database, DatabaseKeyIndex, Event, EventKind}; +use itertools::Itertools; +use parking_lot::lock_api::{RawRwLock, RawRwLockRecursive}; +use parking_lot::{Mutex, RwLock}; +use std::hash::Hash; +use std::panic::panic_any; +use std::sync::atomic::{AtomicU32, Ordering}; +use tracing::debug; +use triomphe::{Arc, ThinArc}; + +mod dependency_graph; +use dependency_graph::DependencyGraph; + +pub(crate) mod local_state; +use local_state::LocalState; + +use self::local_state::{ActiveQueryGuard, QueryRevisions}; + +/// The salsa runtime stores the storage for all queries as well as +/// tracking the query stack and dependencies between cycles. +/// +/// Each new runtime you create (e.g., via `Runtime::new` or +/// `Runtime::default`) will have an independent set of query storage +/// associated with it. Normally, therefore, you only do this once, at +/// the start of your application. +pub struct Runtime { + /// Our unique runtime id. + id: RuntimeId, + + /// If this is a "forked" runtime, then the `revision_guard` will + /// be `Some`; this guard holds a read-lock on the global query + /// lock. + revision_guard: Option, + + /// Local state that is specific to this runtime (thread). + local_state: LocalState, + + /// Shared state that is accessible via all runtimes. + shared_state: Arc, +} + +#[derive(Clone, Debug)] +pub(crate) enum WaitResult { + Completed, + Panicked, + Cycle(Cycle), +} + +impl Default for Runtime { + fn default() -> Self { + Runtime { + id: RuntimeId { counter: 0 }, + revision_guard: None, + shared_state: Default::default(), + local_state: Default::default(), + } + } +} + +impl std::fmt::Debug for Runtime { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Runtime") + .field("id", &self.id()) + .field("forked", &self.revision_guard.is_some()) + .field("shared_state", &self.shared_state) + .finish() + } +} + +impl Runtime { + /// Create a new runtime; equivalent to `Self::default`. This is + /// used when creating a new database. + pub fn new() -> Self { + Self::default() + } + + /// See [`crate::storage::Storage::snapshot`]. + pub(crate) fn snapshot(&self) -> Self { + if self.local_state.query_in_progress() { + panic!("it is not legal to `snapshot` during a query (see salsa-rs/salsa#80)"); + } + + let revision_guard = RevisionGuard::new(&self.shared_state); + + let id = RuntimeId { counter: self.shared_state.next_id.fetch_add(1, Ordering::SeqCst) }; + + Runtime { + id, + revision_guard: Some(revision_guard), + shared_state: self.shared_state.clone(), + local_state: Default::default(), + } + } + + /// A "synthetic write" causes the system to act *as though* some + /// input of durability `durability` has changed. This is mostly + /// useful for profiling scenarios. + /// + /// **WARNING:** Just like an ordinary write, this method triggers + /// cancellation. If you invoke it while a snapshot exists, it + /// will block until that snapshot is dropped -- if that snapshot + /// is owned by the current thread, this could trigger deadlock. + pub fn synthetic_write(&mut self, durability: Durability) { + self.with_incremented_revision(|_next_revision| Some(durability)); + } + + /// The unique identifier attached to this `SalsaRuntime`. Each + /// snapshotted runtime has a distinct identifier. + #[inline] + pub fn id(&self) -> RuntimeId { + self.id + } + + /// Returns the database-key for the query that this thread is + /// actively executing (if any). + pub fn active_query(&self) -> Option { + self.local_state.active_query() + } + + /// Read current value of the revision counter. + #[inline] + pub(crate) fn current_revision(&self) -> Revision { + self.shared_state.revisions[0].load() + } + + /// The revision in which values with durability `d` may have last + /// changed. For D0, this is just the current revision. But for + /// higher levels of durability, this value may lag behind the + /// current revision. If we encounter a value of durability Di, + /// then, we can check this function to get a "bound" on when the + /// value may have changed, which allows us to skip walking its + /// dependencies. + #[inline] + pub(crate) fn last_changed_revision(&self, d: Durability) -> Revision { + self.shared_state.revisions[d.index()].load() + } + + /// Read current value of the revision counter. + #[inline] + pub(crate) fn pending_revision(&self) -> Revision { + self.shared_state.pending_revision.load() + } + + #[cold] + pub(crate) fn unwind_cancelled(&self) { + self.report_untracked_read(); + Cancelled::PendingWrite.throw(); + } + + /// Acquires the **global query write lock** (ensuring that no queries are + /// executing) and then increments the current revision counter; invokes + /// `op` with the global query write lock still held. + /// + /// While we wait to acquire the global query write lock, this method will + /// also increment `pending_revision_increments`, thus signalling to queries + /// that their results are "cancelled" and they should abort as expeditiously + /// as possible. + /// + /// The `op` closure should actually perform the writes needed. It is given + /// the new revision as an argument, and its return value indicates whether + /// any pre-existing value was modified: + /// + /// - returning `None` means that no pre-existing value was modified (this + /// could occur e.g. when setting some key on an input that was never set + /// before) + /// - returning `Some(d)` indicates that a pre-existing value was modified + /// and it had the durability `d`. This will update the records for when + /// values with each durability were modified. + /// + /// Note that, given our writer model, we can assume that only one thread is + /// attempting to increment the global revision at a time. + pub(crate) fn with_incremented_revision(&mut self, op: F) + where + F: FnOnce(Revision) -> Option, + { + tracing::debug!("increment_revision()"); + + if !self.permits_increment() { + panic!("increment_revision invoked during a query computation"); + } + + // Set the `pending_revision` field so that people + // know current revision is cancelled. + let current_revision = self.shared_state.pending_revision.fetch_then_increment(); + + // To modify the revision, we need the lock. + let shared_state = self.shared_state.clone(); + let _lock = shared_state.query_lock.write(); + + let old_revision = self.shared_state.revisions[0].fetch_then_increment(); + assert_eq!(current_revision, old_revision); + + let new_revision = current_revision.next(); + + debug!("increment_revision: incremented to {:?}", new_revision); + + if let Some(d) = op(new_revision) { + for rev in &self.shared_state.revisions[1..=d.index()] { + rev.store(new_revision); + } + } + } + + pub(crate) fn permits_increment(&self) -> bool { + self.revision_guard.is_none() && !self.local_state.query_in_progress() + } + + #[inline] + pub(crate) fn push_query(&self, database_key_index: DatabaseKeyIndex) -> ActiveQueryGuard<'_> { + self.local_state.push_query(database_key_index) + } + + /// Reports that the currently active query read the result from + /// another query. + /// + /// Also checks whether the "cycle participant" flag is set on + /// the current stack frame -- if so, panics with `CycleParticipant` + /// value, which should be caught by the code executing the query. + /// + /// # Parameters + /// + /// - `database_key`: the query whose result was read + /// - `changed_revision`: the last revision in which the result of that + /// query had changed + pub(crate) fn report_query_read_and_unwind_if_cycle_resulted( + &self, + input: DatabaseKeyIndex, + durability: Durability, + changed_at: Revision, + ) { + self.local_state + .report_query_read_and_unwind_if_cycle_resulted(input, durability, changed_at); + } + + /// Reports that the query depends on some state unknown to salsa. + /// + /// Queries which report untracked reads will be re-executed in the next + /// revision. + pub fn report_untracked_read(&self) { + self.local_state.report_untracked_read(self.current_revision()); + } + + /// Acts as though the current query had read an input with the given durability; this will force the current query's durability to be at most `durability`. + /// + /// This is mostly useful to control the durability level for [on-demand inputs](https://salsa-rs.github.io/salsa/common_patterns/on_demand_inputs.html). + pub fn report_synthetic_read(&self, durability: Durability) { + let changed_at = self.last_changed_revision(durability); + self.local_state.report_synthetic_read(durability, changed_at); + } + + /// Handles a cycle in the dependency graph that was detected when the + /// current thread tried to block on `database_key_index` which is being + /// executed by `to_id`. If this function returns, then `to_id` no longer + /// depends on the current thread, and so we should continue executing + /// as normal. Otherwise, the function will throw a `Cycle` which is expected + /// to be caught by some frame on our stack. This occurs either if there is + /// a frame on our stack with cycle recovery (possibly the top one!) or if there + /// is no cycle recovery at all. + fn unblock_cycle_and_maybe_throw( + &self, + db: &dyn Database, + dg: &mut DependencyGraph, + database_key_index: DatabaseKeyIndex, + to_id: RuntimeId, + ) { + debug!("unblock_cycle_and_maybe_throw(database_key={:?})", database_key_index); + + let mut from_stack = self.local_state.take_query_stack(); + let from_id = self.id(); + + // Make a "dummy stack frame". As we iterate through the cycle, we will collect the + // inputs from each participant. Then, if we are participating in cycle recovery, we + // will propagate those results to all participants. + let mut cycle_query = ActiveQuery::new(database_key_index); + + // Identify the cycle participants: + let cycle = { + let mut v = vec![]; + dg.for_each_cycle_participant( + from_id, + &mut from_stack, + database_key_index, + to_id, + |aqs| { + aqs.iter_mut().for_each(|aq| { + cycle_query.add_from(aq); + v.push(aq.database_key_index); + }); + }, + ); + + // We want to give the participants in a deterministic order + // (at least for this execution, not necessarily across executions), + // no matter where it started on the stack. Find the minimum + // key and rotate it to the front. + let index = v.iter().position_min().unwrap_or_default(); + v.rotate_left(index); + + // No need to store extra memory. + v.shrink_to_fit(); + + Cycle::new(Arc::new(v)) + }; + debug!("cycle {:?}, cycle_query {:#?}", cycle.debug(db), cycle_query,); + + // We can remove the cycle participants from the list of dependencies; + // they are a strongly connected component (SCC) and we only care about + // dependencies to things outside the SCC that control whether it will + // form again. + cycle_query.remove_cycle_participants(&cycle); + + // Mark each cycle participant that has recovery set, along with + // any frames that come after them on the same thread. Those frames + // are going to be unwound so that fallback can occur. + dg.for_each_cycle_participant(from_id, &mut from_stack, database_key_index, to_id, |aqs| { + aqs.iter_mut() + .skip_while(|aq| match db.cycle_recovery_strategy(aq.database_key_index) { + CycleRecoveryStrategy::Panic => true, + CycleRecoveryStrategy::Fallback => false, + }) + .for_each(|aq| { + debug!("marking {:?} for fallback", aq.database_key_index.debug(db)); + aq.take_inputs_from(&cycle_query); + assert!(aq.cycle.is_none()); + aq.cycle = Some(cycle.clone()); + }); + }); + + // Unblock every thread that has cycle recovery with a `WaitResult::Cycle`. + // They will throw the cycle, which will be caught by the frame that has + // cycle recovery so that it can execute that recovery. + let (me_recovered, others_recovered) = + dg.maybe_unblock_runtimes_in_cycle(from_id, &from_stack, database_key_index, to_id); + + self.local_state.restore_query_stack(from_stack); + + if me_recovered { + // If the current thread has recovery, we want to throw + // so that it can begin. + cycle.throw() + } else if others_recovered { + // If other threads have recovery but we didn't: return and we will block on them. + } else { + // if nobody has recover, then we panic + panic_any(cycle); + } + } + + /// Block until `other_id` completes executing `database_key`; + /// panic or unwind in the case of a cycle. + /// + /// `query_mutex_guard` is the guard for the current query's state; + /// it will be dropped after we have successfully registered the + /// dependency. + /// + /// # Propagating panics + /// + /// If the thread `other_id` panics, then our thread is considered + /// cancelled, so this function will panic with a `Cancelled` value. + /// + /// # Cycle handling + /// + /// If the thread `other_id` already depends on the current thread, + /// and hence there is a cycle in the query graph, then this function + /// will unwind instead of returning normally. The method of unwinding + /// depends on the [`Self::mutual_cycle_recovery_strategy`] + /// of the cycle participants: + /// + /// * [`CycleRecoveryStrategy::Panic`]: panic with the [`Cycle`] as the value. + /// * [`CycleRecoveryStrategy::Fallback`]: initiate unwinding with [`CycleParticipant::unwind`]. + pub(crate) fn block_on_or_unwind( + &self, + db: &dyn Database, + database_key: DatabaseKeyIndex, + other_id: RuntimeId, + query_mutex_guard: QueryMutexGuard, + ) { + let mut dg = self.shared_state.dependency_graph.lock(); + + if dg.depends_on(other_id, self.id()) { + self.unblock_cycle_and_maybe_throw(db, &mut dg, database_key, other_id); + + // If the above fn returns, then (via cycle recovery) it has unblocked the + // cycle, so we can continue. + assert!(!dg.depends_on(other_id, self.id())); + } + + db.salsa_event(Event { + runtime_id: self.id(), + kind: EventKind::WillBlockOn { other_runtime_id: other_id, database_key }, + }); + + let stack = self.local_state.take_query_stack(); + + let (stack, result) = DependencyGraph::block_on( + dg, + self.id(), + database_key, + other_id, + stack, + query_mutex_guard, + ); + + self.local_state.restore_query_stack(stack); + + match result { + WaitResult::Completed => (), + + // If the other thread panicked, then we consider this thread + // cancelled. The assumption is that the panic will be detected + // by the other thread and responded to appropriately. + WaitResult::Panicked => Cancelled::PropagatedPanic.throw(), + + WaitResult::Cycle(c) => c.throw(), + } + } + + /// Invoked when this runtime completed computing `database_key` with + /// the given result `wait_result` (`wait_result` should be `None` if + /// computing `database_key` panicked and could not complete). + /// This function unblocks any dependent queries and allows them + /// to continue executing. + pub(crate) fn unblock_queries_blocked_on( + &self, + database_key: DatabaseKeyIndex, + wait_result: WaitResult, + ) { + self.shared_state + .dependency_graph + .lock() + .unblock_runtimes_blocked_on(database_key, wait_result); + } +} + +/// State that will be common to all threads (when we support multiple threads) +struct SharedState { + /// Stores the next id to use for a snapshotted runtime (starts at 1). + next_id: AtomicU32, + + /// Whenever derived queries are executing, they acquire this lock + /// in read mode. Mutating inputs (and thus creating a new + /// revision) requires a write lock (thus guaranteeing that no + /// derived queries are in progress). Note that this is not needed + /// to prevent **race conditions** -- the revision counter itself + /// is stored in an `AtomicUsize` so it can be cheaply read + /// without acquiring the lock. Rather, the `query_lock` is used + /// to ensure a higher-level consistency property. + query_lock: RwLock<()>, + + /// This is typically equal to `revision` -- set to `revision+1` + /// when a new revision is pending (which implies that the current + /// revision is cancelled). + pending_revision: AtomicRevision, + + /// Stores the "last change" revision for values of each Durability. + /// This vector is always of length at least 1 (for Durability 0) + /// but its total length depends on the number of Durabilities. The + /// element at index 0 is special as it represents the "current + /// revision". In general, we have the invariant that revisions + /// in here are *declining* -- that is, `revisions[i] >= + /// revisions[i + 1]`, for all `i`. This is because when you + /// modify a value with durability D, that implies that values + /// with durability less than D may have changed too. + revisions: [AtomicRevision; Durability::LEN], + + /// The dependency graph tracks which runtimes are blocked on one + /// another, waiting for queries to terminate. + dependency_graph: Mutex, +} + +impl std::panic::RefUnwindSafe for SharedState {} + +impl Default for SharedState { + fn default() -> Self { + #[allow(clippy::declare_interior_mutable_const)] + const START: AtomicRevision = AtomicRevision::start(); + SharedState { + next_id: AtomicU32::new(1), + query_lock: Default::default(), + revisions: [START; Durability::LEN], + pending_revision: START, + dependency_graph: Default::default(), + } + } +} + +impl std::fmt::Debug for SharedState { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let query_lock = if self.query_lock.is_locked_exclusive() { + "" + } else if self.query_lock.is_locked() { + "" + } else { + "" + }; + fmt.debug_struct("SharedState") + .field("query_lock", &query_lock) + .field("revisions", &self.revisions) + .field("pending_revision", &self.pending_revision) + .finish() + } +} + +#[derive(Debug)] +struct ActiveQuery { + /// What query is executing + database_key_index: DatabaseKeyIndex, + + /// Minimum durability of inputs observed so far. + durability: Durability, + + /// Maximum revision of all inputs observed. If we observe an + /// untracked read, this will be set to the most recent revision. + changed_at: Revision, + + /// Set of subqueries that were accessed thus far, or `None` if + /// there was an untracked the read. + dependencies: Option>, + + /// Stores the entire cycle, if one is found and this query is part of it. + cycle: Option, +} + +impl ActiveQuery { + fn new(database_key_index: DatabaseKeyIndex) -> Self { + ActiveQuery { + database_key_index, + durability: Durability::MAX, + changed_at: Revision::start(), + dependencies: Some(FxIndexSet::default()), + cycle: None, + } + } + + fn add_read(&mut self, input: DatabaseKeyIndex, durability: Durability, revision: Revision) { + if let Some(set) = &mut self.dependencies { + set.insert(input); + } + + self.durability = self.durability.min(durability); + self.changed_at = self.changed_at.max(revision); + } + + fn add_untracked_read(&mut self, changed_at: Revision) { + self.dependencies = None; + self.durability = Durability::LOW; + self.changed_at = changed_at; + } + + fn add_synthetic_read(&mut self, durability: Durability, revision: Revision) { + self.dependencies = None; + self.durability = self.durability.min(durability); + self.changed_at = self.changed_at.max(revision); + } + + pub(crate) fn revisions(&self) -> QueryRevisions { + let (inputs, untracked) = match &self.dependencies { + None => (None, true), + + Some(dependencies) => ( + if dependencies.is_empty() { + None + } else { + Some(ThinArc::from_header_and_iter((), dependencies.iter().copied())) + }, + false, + ), + }; + + QueryRevisions { + changed_at: self.changed_at, + inputs, + untracked, + durability: self.durability, + } + } + + /// Adds any dependencies from `other` into `self`. + /// Used during cycle recovery, see [`Runtime::create_cycle_error`]. + fn add_from(&mut self, other: &ActiveQuery) { + self.changed_at = self.changed_at.max(other.changed_at); + self.durability = self.durability.min(other.durability); + if let Some(other_dependencies) = &other.dependencies { + if let Some(my_dependencies) = &mut self.dependencies { + my_dependencies.extend(other_dependencies.iter().copied()); + } + } else { + self.dependencies = None; + } + } + + /// Removes the participants in `cycle` from my dependencies. + /// Used during cycle recovery, see [`Runtime::create_cycle_error`]. + fn remove_cycle_participants(&mut self, cycle: &Cycle) { + if let Some(my_dependencies) = &mut self.dependencies { + for p in cycle.participant_keys() { + my_dependencies.swap_remove(&p); + } + } + } + + /// Copy the changed-at, durability, and dependencies from `cycle_query`. + /// Used during cycle recovery, see [`Runtime::create_cycle_error`]. + pub(crate) fn take_inputs_from(&mut self, cycle_query: &ActiveQuery) { + self.changed_at = cycle_query.changed_at; + self.durability = cycle_query.durability; + self.dependencies.clone_from(&cycle_query.dependencies); + } +} + +/// A unique identifier for a particular runtime. Each time you create +/// a snapshot, a fresh `RuntimeId` is generated. Once a snapshot is +/// complete, its `RuntimeId` may potentially be re-used. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct RuntimeId { + counter: u32, +} + +#[derive(Clone, Debug)] +pub(crate) struct StampedValue { + pub(crate) value: V, + pub(crate) durability: Durability, + pub(crate) changed_at: Revision, +} + +struct RevisionGuard { + shared_state: Arc, +} + +impl RevisionGuard { + fn new(shared_state: &Arc) -> Self { + // Subtle: we use a "recursive" lock here so that it is not an + // error to acquire a read-lock when one is already held (this + // happens when a query uses `snapshot` to spawn off parallel + // workers, for example). + // + // This has the side-effect that we are responsible to ensure + // that people contending for the write lock do not starve, + // but this is what we achieve via the cancellation mechanism. + // + // (In particular, since we only ever have one "mutating + // handle" to the database, the only contention for the global + // query lock occurs when there are "futures" evaluating + // queries in parallel, and those futures hold a read-lock + // already, so the starvation problem is more about them bring + // themselves to a close, versus preventing other people from + // *starting* work). + unsafe { + shared_state.query_lock.raw().lock_shared_recursive(); + } + + Self { shared_state: shared_state.clone() } + } +} + +impl Drop for RevisionGuard { + fn drop(&mut self) { + // Release our read-lock without using RAII. As documented in + // `Snapshot::new` above, this requires the unsafe keyword. + unsafe { + self.shared_state.query_lock.raw().unlock_shared(); + } + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/runtime/dependency_graph.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/runtime/dependency_graph.rs new file mode 100644 index 00000000000..ed1d499f637 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/runtime/dependency_graph.rs @@ -0,0 +1,250 @@ +use triomphe::Arc; + +use crate::{DatabaseKeyIndex, RuntimeId}; +use parking_lot::{Condvar, MutexGuard}; +use rustc_hash::FxHashMap; +use smallvec::SmallVec; + +use super::{ActiveQuery, WaitResult}; + +type QueryStack = Vec; + +#[derive(Debug, Default)] +pub(super) struct DependencyGraph { + /// A `(K -> V)` pair in this map indicates that the runtime + /// `K` is blocked on some query executing in the runtime `V`. + /// This encodes a graph that must be acyclic (or else deadlock + /// will result). + edges: FxHashMap, + + /// Encodes the `RuntimeId` that are blocked waiting for the result + /// of a given query. + query_dependents: FxHashMap>, + + /// When a key K completes which had dependent queries Qs blocked on it, + /// it stores its `WaitResult` here. As they wake up, each query Q in Qs will + /// come here to fetch their results. + wait_results: FxHashMap, +} + +#[derive(Debug)] +struct Edge { + blocked_on_id: RuntimeId, + blocked_on_key: DatabaseKeyIndex, + stack: QueryStack, + + /// Signalled whenever a query with dependents completes. + /// Allows those dependents to check if they are ready to unblock. + condvar: Arc, +} + +impl DependencyGraph { + /// True if `from_id` depends on `to_id`. + /// + /// (i.e., there is a path from `from_id` to `to_id` in the graph.) + pub(super) fn depends_on(&mut self, from_id: RuntimeId, to_id: RuntimeId) -> bool { + let mut p = from_id; + while let Some(q) = self.edges.get(&p).map(|edge| edge.blocked_on_id) { + if q == to_id { + return true; + } + + p = q; + } + p == to_id + } + + /// Invokes `closure` with a `&mut ActiveQuery` for each query that participates in the cycle. + /// The cycle runs as follows: + /// + /// 1. The runtime `from_id`, which has the stack `from_stack`, would like to invoke `database_key`... + /// 2. ...but `database_key` is already being executed by `to_id`... + /// 3. ...and `to_id` is transitively dependent on something which is present on `from_stack`. + pub(super) fn for_each_cycle_participant( + &mut self, + from_id: RuntimeId, + from_stack: &mut QueryStack, + database_key: DatabaseKeyIndex, + to_id: RuntimeId, + mut closure: impl FnMut(&mut [ActiveQuery]), + ) { + debug_assert!(self.depends_on(to_id, from_id)); + + // To understand this algorithm, consider this [drawing](https://is.gd/TGLI9v): + // + // database_key = QB2 + // from_id = A + // to_id = B + // from_stack = [QA1, QA2, QA3] + // + // self.edges[B] = { C, QC2, [QB1..QB3] } + // self.edges[C] = { A, QA2, [QC1..QC3] } + // + // The cyclic + // edge we have + // failed to add. + // : + // A : B C + // : + // QA1 v QB1 QC1 + // ┌► QA2 ┌──► QB2 ┌─► QC2 + // │ QA3 ───┘ QB3 ──┘ QC3 ───┐ + // │ │ + // └───────────────────────────────┘ + // + // Final output: [QB2, QB3, QC2, QC3, QA2, QA3] + + let mut id = to_id; + let mut key = database_key; + while id != from_id { + // Looking at the diagram above, the idea is to + // take the edge from `to_id` starting at `key` + // (inclusive) and down to the end. We can then + // load up the next thread (i.e., we start at B/QB2, + // and then load up the dependency on C/QC2). + let edge = self.edges.get_mut(&id).unwrap(); + let prefix = edge.stack.iter_mut().take_while(|p| p.database_key_index != key).count(); + closure(&mut edge.stack[prefix..]); + id = edge.blocked_on_id; + key = edge.blocked_on_key; + } + + // Finally, we copy in the results from `from_stack`. + let prefix = from_stack.iter_mut().take_while(|p| p.database_key_index != key).count(); + closure(&mut from_stack[prefix..]); + } + + /// Unblock each blocked runtime (excluding the current one) if some + /// query executing in that runtime is participating in cycle fallback. + /// + /// Returns a boolean (Current, Others) where: + /// * Current is true if the current runtime has cycle participants + /// with fallback; + /// * Others is true if other runtimes were unblocked. + pub(super) fn maybe_unblock_runtimes_in_cycle( + &mut self, + from_id: RuntimeId, + from_stack: &QueryStack, + database_key: DatabaseKeyIndex, + to_id: RuntimeId, + ) -> (bool, bool) { + // See diagram in `for_each_cycle_participant`. + let mut id = to_id; + let mut key = database_key; + let mut others_unblocked = false; + while id != from_id { + let edge = self.edges.get(&id).unwrap(); + let prefix = edge.stack.iter().take_while(|p| p.database_key_index != key).count(); + let next_id = edge.blocked_on_id; + let next_key = edge.blocked_on_key; + + if let Some(cycle) = edge.stack[prefix..].iter().rev().find_map(|aq| aq.cycle.clone()) { + // Remove `id` from the list of runtimes blocked on `next_key`: + self.query_dependents.get_mut(&next_key).unwrap().retain(|r| *r != id); + + // Unblock runtime so that it can resume execution once lock is released: + self.unblock_runtime(id, WaitResult::Cycle(cycle)); + + others_unblocked = true; + } + + id = next_id; + key = next_key; + } + + let prefix = from_stack.iter().take_while(|p| p.database_key_index != key).count(); + let this_unblocked = from_stack[prefix..].iter().any(|aq| aq.cycle.is_some()); + + (this_unblocked, others_unblocked) + } + + /// Modifies the graph so that `from_id` is blocked + /// on `database_key`, which is being computed by + /// `to_id`. + /// + /// For this to be reasonable, the lock on the + /// results table for `database_key` must be held. + /// This ensures that computing `database_key` doesn't + /// complete before `block_on` executes. + /// + /// Preconditions: + /// * No path from `to_id` to `from_id` + /// (i.e., `me.depends_on(to_id, from_id)` is false) + /// * `held_mutex` is a read lock (or stronger) on `database_key` + pub(super) fn block_on( + mut me: MutexGuard<'_, Self>, + from_id: RuntimeId, + database_key: DatabaseKeyIndex, + to_id: RuntimeId, + from_stack: QueryStack, + query_mutex_guard: QueryMutexGuard, + ) -> (QueryStack, WaitResult) { + let condvar = me.add_edge(from_id, database_key, to_id, from_stack); + + // Release the mutex that prevents `database_key` + // from completing, now that the edge has been added. + drop(query_mutex_guard); + + loop { + if let Some(stack_and_result) = me.wait_results.remove(&from_id) { + debug_assert!(!me.edges.contains_key(&from_id)); + return stack_and_result; + } + condvar.wait(&mut me); + } + } + + /// Helper for `block_on`: performs actual graph modification + /// to add a dependency edge from `from_id` to `to_id`, which is + /// computing `database_key`. + fn add_edge( + &mut self, + from_id: RuntimeId, + database_key: DatabaseKeyIndex, + to_id: RuntimeId, + from_stack: QueryStack, + ) -> Arc { + assert_ne!(from_id, to_id); + debug_assert!(!self.edges.contains_key(&from_id)); + debug_assert!(!self.depends_on(to_id, from_id)); + + let condvar = Arc::new(Condvar::new()); + self.edges.insert( + from_id, + Edge { + blocked_on_id: to_id, + blocked_on_key: database_key, + stack: from_stack, + condvar: condvar.clone(), + }, + ); + self.query_dependents.entry(database_key).or_default().push(from_id); + condvar + } + + /// Invoked when runtime `to_id` completes executing + /// `database_key`. + pub(super) fn unblock_runtimes_blocked_on( + &mut self, + database_key: DatabaseKeyIndex, + wait_result: WaitResult, + ) { + let dependents = self.query_dependents.remove(&database_key).unwrap_or_default(); + + for from_id in dependents { + self.unblock_runtime(from_id, wait_result.clone()); + } + } + + /// Unblock the runtime with the given id with the given wait-result. + /// This will cause it resume execution (though it will have to grab + /// the lock on this data structure first, to recover the wait result). + fn unblock_runtime(&mut self, id: RuntimeId, wait_result: WaitResult) { + let edge = self.edges.remove(&id).expect("not blocked"); + self.wait_results.insert(id, (edge.stack, wait_result)); + + // Now that we have inserted the `wait_results`, + // notify the thread. + edge.condvar.notify_one(); + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/runtime/local_state.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/runtime/local_state.rs new file mode 100644 index 00000000000..73869671886 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/runtime/local_state.rs @@ -0,0 +1,204 @@ +use tracing::debug; +use triomphe::ThinArc; + +use crate::durability::Durability; +use crate::runtime::ActiveQuery; +use crate::runtime::Revision; +use crate::Cycle; +use crate::DatabaseKeyIndex; +use std::cell::RefCell; + +/// State that is specific to a single execution thread. +/// +/// Internally, this type uses ref-cells. +/// +/// **Note also that all mutations to the database handle (and hence +/// to the local-state) must be undone during unwinding.** +pub(super) struct LocalState { + /// Vector of active queries. + /// + /// This is normally `Some`, but it is set to `None` + /// while the query is blocked waiting for a result. + /// + /// Unwinding note: pushes onto this vector must be popped -- even + /// during unwinding. + query_stack: RefCell>>, +} + +/// Summarizes "all the inputs that a query used" +#[derive(Debug, Clone)] +pub(crate) struct QueryRevisions { + /// The most revision in which some input changed. + pub(crate) changed_at: Revision, + + /// Minimum durability of the inputs to this query. + pub(crate) durability: Durability, + + /// Whether the input is untracked. + /// Invariant: if `untracked`, `inputs` is `None`. + /// Why is this encoded like this and not a proper enum? Struct size, this saves us 8 bytes. + pub(crate) untracked: bool, + + /// The inputs that went into our query, if we are tracking them. + pub(crate) inputs: Option>, +} + +impl Default for LocalState { + fn default() -> Self { + LocalState { query_stack: RefCell::new(Some(Vec::new())) } + } +} + +impl LocalState { + #[inline] + pub(super) fn push_query(&self, database_key_index: DatabaseKeyIndex) -> ActiveQueryGuard<'_> { + let mut query_stack = self.query_stack.borrow_mut(); + let query_stack = query_stack.as_mut().expect("local stack taken"); + query_stack.push(ActiveQuery::new(database_key_index)); + ActiveQueryGuard { local_state: self, database_key_index, push_len: query_stack.len() } + } + + fn with_query_stack(&self, c: impl FnOnce(&mut Vec) -> R) -> R { + c(self.query_stack.borrow_mut().as_mut().expect("query stack taken")) + } + + pub(super) fn query_in_progress(&self) -> bool { + self.with_query_stack(|stack| !stack.is_empty()) + } + + pub(super) fn active_query(&self) -> Option { + self.with_query_stack(|stack| { + stack.last().map(|active_query| active_query.database_key_index) + }) + } + + pub(super) fn report_query_read_and_unwind_if_cycle_resulted( + &self, + input: DatabaseKeyIndex, + durability: Durability, + changed_at: Revision, + ) { + debug!( + "report_query_read_and_unwind_if_cycle_resulted(input={:?}, durability={:?}, changed_at={:?})", + input, durability, changed_at + ); + self.with_query_stack(|stack| { + if let Some(top_query) = stack.last_mut() { + top_query.add_read(input, durability, changed_at); + + // We are a cycle participant: + // + // C0 --> ... --> Ci --> Ci+1 -> ... -> Cn --> C0 + // ^ ^ + // : | + // This edge -----+ | + // | + // | + // N0 + // + // In this case, the value we have just read from `Ci+1` + // is actually the cycle fallback value and not especially + // interesting. We unwind now with `CycleParticipant` to avoid + // executing the rest of our query function. This unwinding + // will be caught and our own fallback value will be used. + // + // Note that `Ci+1` may` have *other* callers who are not + // participants in the cycle (e.g., N0 in the graph above). + // They will not have the `cycle` marker set in their + // stack frames, so they will just read the fallback value + // from `Ci+1` and continue on their merry way. + if let Some(cycle) = &top_query.cycle { + cycle.clone().throw() + } + } + }) + } + + pub(super) fn report_untracked_read(&self, current_revision: Revision) { + self.with_query_stack(|stack| { + if let Some(top_query) = stack.last_mut() { + top_query.add_untracked_read(current_revision); + } + }) + } + + /// Update the top query on the stack to act as though it read a value + /// of durability `durability` which changed in `revision`. + pub(super) fn report_synthetic_read(&self, durability: Durability, revision: Revision) { + self.with_query_stack(|stack| { + if let Some(top_query) = stack.last_mut() { + top_query.add_synthetic_read(durability, revision); + } + }) + } + + /// Takes the query stack and returns it. This is used when + /// the current thread is blocking. The stack must be restored + /// with [`Self::restore_query_stack`] when the thread unblocks. + pub(super) fn take_query_stack(&self) -> Vec { + self.query_stack.take().expect("query stack already taken") + } + + /// Restores a query stack taken with [`Self::take_query_stack`] once + /// the thread unblocks. + pub(super) fn restore_query_stack(&self, stack: Vec) { + assert!(self.query_stack.borrow().is_none(), "query stack not taken"); + self.query_stack.replace(Some(stack)); + } +} + +impl std::panic::RefUnwindSafe for LocalState {} + +/// When a query is pushed onto the `active_query` stack, this guard +/// is returned to represent its slot. The guard can be used to pop +/// the query from the stack -- in the case of unwinding, the guard's +/// destructor will also remove the query. +pub(crate) struct ActiveQueryGuard<'me> { + local_state: &'me LocalState, + push_len: usize, + database_key_index: DatabaseKeyIndex, +} + +impl ActiveQueryGuard<'_> { + fn pop_helper(&self) -> ActiveQuery { + self.local_state.with_query_stack(|stack| { + // Sanity check: pushes and pops should be balanced. + assert_eq!(stack.len(), self.push_len); + debug_assert_eq!(stack.last().unwrap().database_key_index, self.database_key_index); + stack.pop().unwrap() + }) + } + + /// Invoked when the query has successfully completed execution. + pub(super) fn complete(self) -> ActiveQuery { + let query = self.pop_helper(); + std::mem::forget(self); + query + } + + /// Pops an active query from the stack. Returns the [`QueryRevisions`] + /// which summarizes the other queries that were accessed during this + /// query's execution. + #[inline] + pub(crate) fn pop(self) -> QueryRevisions { + // Extract accumulated inputs. + let popped_query = self.complete(); + + // If this frame were a cycle participant, it would have unwound. + assert!(popped_query.cycle.is_none()); + + popped_query.revisions() + } + + /// If the active query is registered as a cycle participant, remove and + /// return that cycle. + pub(crate) fn take_cycle(&self) -> Option { + self.local_state.with_query_stack(|stack| stack.last_mut()?.cycle.take()) + } +} + +impl Drop for ActiveQueryGuard<'_> { + fn drop(&mut self) { + self.pop_helper(); + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/src/storage.rs b/src/tools/rust-analyzer/crates/ra-salsa/src/storage.rs new file mode 100644 index 00000000000..e0acf44041b --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/src/storage.rs @@ -0,0 +1,53 @@ +use crate::{plumbing::DatabaseStorageTypes, Runtime}; +use triomphe::Arc; + +/// Stores the cached results and dependency information for all the queries +/// defined on your salsa database. Also embeds a [`Runtime`] which is used to +/// manage query execution. Every database must include a `storage: +/// Storage` field. +pub struct Storage { + query_store: Arc, + runtime: Runtime, +} + +impl Default for Storage { + fn default() -> Self { + Self { query_store: Default::default(), runtime: Default::default() } + } +} + +impl Storage { + /// Gives access to the underlying salsa runtime. + pub fn salsa_runtime(&self) -> &Runtime { + &self.runtime + } + + /// Gives access to the underlying salsa runtime. + pub fn salsa_runtime_mut(&mut self) -> &mut Runtime { + &mut self.runtime + } + + /// Access the query storage tables. Not meant to be used directly by end + /// users. + pub fn query_store(&self) -> &DB::DatabaseStorage { + &self.query_store + } + + /// Access the query storage tables. Not meant to be used directly by end + /// users. + pub fn query_store_mut(&mut self) -> (&DB::DatabaseStorage, &mut Runtime) { + (&self.query_store, &mut self.runtime) + } + + /// Returns a "snapshotted" storage, suitable for use in a forked database. + /// This snapshot hold a read-lock on the global state, which means that any + /// attempt to `set` an input will block until the forked runtime is + /// dropped. See `ParallelDatabase::snapshot` for more information. + /// + /// **Warning.** This second handle is intended to be used from a separate + /// thread. Using two database handles from the **same thread** can lead to + /// deadlock. + pub fn snapshot(&self) -> Self { + Storage { query_store: self.query_store.clone(), runtime: self.runtime.snapshot() } + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/cycles.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/cycles.rs new file mode 100644 index 00000000000..81136626551 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/cycles.rs @@ -0,0 +1,492 @@ +use std::panic::UnwindSafe; + +use expect_test::expect; +use ra_salsa::{Durability, ParallelDatabase, Snapshot}; + +// Axes: +// +// Threading +// * Intra-thread +// * Cross-thread -- part of cycle is on one thread, part on another +// +// Recovery strategies: +// * Panic +// * Fallback +// * Mixed -- multiple strategies within cycle participants +// +// Across revisions: +// * N/A -- only one revision +// * Present in new revision, not old +// * Present in old revision, not new +// * Present in both revisions +// +// Dependencies +// * Tracked +// * Untracked -- cycle participant(s) contain untracked reads +// +// Layers +// * Direct -- cycle participant is directly invoked from test +// * Indirect -- invoked a query that invokes the cycle +// +// +// | Thread | Recovery | Old, New | Dep style | Layers | Test Name | +// | ------ | -------- | -------- | --------- | ------ | --------- | +// | Intra | Panic | N/A | Tracked | direct | cycle_memoized | +// | Intra | Panic | N/A | Untracked | direct | cycle_volatile | +// | Intra | Fallback | N/A | Tracked | direct | cycle_cycle | +// | Intra | Fallback | N/A | Tracked | indirect | inner_cycle | +// | Intra | Fallback | Both | Tracked | direct | cycle_revalidate | +// | Intra | Fallback | New | Tracked | direct | cycle_appears | +// | Intra | Fallback | Old | Tracked | direct | cycle_disappears | +// | Intra | Fallback | Old | Tracked | direct | cycle_disappears_durability | +// | Intra | Mixed | N/A | Tracked | direct | cycle_mixed_1 | +// | Intra | Mixed | N/A | Tracked | direct | cycle_mixed_2 | +// | Cross | Fallback | N/A | Tracked | both | parallel/cycles.rs: recover_parallel_cycle | +// | Cross | Panic | N/A | Tracked | both | parallel/cycles.rs: panic_parallel_cycle | + +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +struct Error { + cycle: Vec, +} + +#[ra_salsa::database(GroupStruct)] +#[derive(Default)] +struct DatabaseImpl { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for DatabaseImpl {} + +impl ParallelDatabase for DatabaseImpl { + fn snapshot(&self) -> Snapshot { + Snapshot::new(DatabaseImpl { storage: self.storage.snapshot() }) + } +} + +/// The queries A, B, and C in `Database` can be configured +/// to invoke one another in arbitrary ways using this +/// enum. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum CycleQuery { + None, + A, + B, + C, + AthenC, +} + +#[ra_salsa::query_group(GroupStruct)] +trait Database: ra_salsa::Database { + // `a` and `b` depend on each other and form a cycle + fn memoized_a(&self) -> (); + fn memoized_b(&self) -> (); + fn volatile_a(&self) -> (); + fn volatile_b(&self) -> (); + + #[ra_salsa::input] + fn a_invokes(&self) -> CycleQuery; + + #[ra_salsa::input] + fn b_invokes(&self) -> CycleQuery; + + #[ra_salsa::input] + fn c_invokes(&self) -> CycleQuery; + + #[ra_salsa::cycle(recover_a)] + fn cycle_a(&self) -> Result<(), Error>; + + #[ra_salsa::cycle(recover_b)] + fn cycle_b(&self) -> Result<(), Error>; + + fn cycle_c(&self) -> Result<(), Error>; +} + +fn recover_a(db: &dyn Database, cycle: &ra_salsa::Cycle) -> Result<(), Error> { + Err(Error { cycle: cycle.all_participants(db) }) +} + +fn recover_b(db: &dyn Database, cycle: &ra_salsa::Cycle) -> Result<(), Error> { + Err(Error { cycle: cycle.all_participants(db) }) +} + +fn memoized_a(db: &dyn Database) { + db.memoized_b() +} + +fn memoized_b(db: &dyn Database) { + db.memoized_a() +} + +fn volatile_a(db: &dyn Database) { + db.salsa_runtime().report_untracked_read(); + db.volatile_b() +} + +fn volatile_b(db: &dyn Database) { + db.salsa_runtime().report_untracked_read(); + db.volatile_a() +} + +impl CycleQuery { + fn invoke(self, db: &dyn Database) -> Result<(), Error> { + match self { + CycleQuery::A => db.cycle_a(), + CycleQuery::B => db.cycle_b(), + CycleQuery::C => db.cycle_c(), + CycleQuery::AthenC => { + let _ = db.cycle_a(); + db.cycle_c() + } + CycleQuery::None => Ok(()), + } + } +} + +fn cycle_a(db: &dyn Database) -> Result<(), Error> { + db.a_invokes().invoke(db) +} + +fn cycle_b(db: &dyn Database) -> Result<(), Error> { + db.b_invokes().invoke(db) +} + +fn cycle_c(db: &dyn Database) -> Result<(), Error> { + db.c_invokes().invoke(db) +} + +#[track_caller] +fn extract_cycle(f: impl FnOnce() + UnwindSafe) -> ra_salsa::Cycle { + let v = std::panic::catch_unwind(f); + if let Err(d) = &v { + if let Some(cycle) = d.downcast_ref::() { + return cycle.clone(); + } + } + panic!("unexpected value: {v:?}") +} + +#[test] +fn cycle_memoized() { + let db = DatabaseImpl::default(); + let cycle = extract_cycle(|| db.memoized_a()); + expect![[r#" + [ + "cycles::MemoizedAQuery::memoized_a(())", + "cycles::MemoizedBQuery::memoized_b(())", + ] + "#]] + .assert_debug_eq(&cycle.unexpected_participants(&db)); +} + +#[test] +fn cycle_volatile() { + let db = DatabaseImpl::default(); + let cycle = extract_cycle(|| db.volatile_a()); + expect![[r#" + [ + "cycles::VolatileAQuery::volatile_a(())", + "cycles::VolatileBQuery::volatile_b(())", + ] + "#]] + .assert_debug_eq(&cycle.unexpected_participants(&db)); +} + +#[test] +fn cycle_cycle() { + let mut query = DatabaseImpl::default(); + + // A --> B + // ^ | + // +-----+ + + query.set_a_invokes(CycleQuery::B); + query.set_b_invokes(CycleQuery::A); + + assert!(query.cycle_a().is_err()); +} + +#[test] +fn inner_cycle() { + let mut query = DatabaseImpl::default(); + + // A --> B <-- C + // ^ | + // +-----+ + + query.set_a_invokes(CycleQuery::B); + query.set_b_invokes(CycleQuery::A); + query.set_c_invokes(CycleQuery::B); + + let err = query.cycle_c(); + assert!(err.is_err()); + let cycle = err.unwrap_err().cycle; + expect![[r#" + [ + "cycles::CycleAQuery::cycle_a(())", + "cycles::CycleBQuery::cycle_b(())", + ] + "#]] + .assert_debug_eq(&cycle); +} + +#[test] +fn cycle_revalidate() { + let mut db = DatabaseImpl::default(); + + // A --> B + // ^ | + // +-----+ + db.set_a_invokes(CycleQuery::B); + db.set_b_invokes(CycleQuery::A); + + assert!(db.cycle_a().is_err()); + db.set_b_invokes(CycleQuery::A); // same value as default + assert!(db.cycle_a().is_err()); +} + +#[test] +fn cycle_revalidate_unchanged_twice() { + let mut db = DatabaseImpl::default(); + + // A --> B + // ^ | + // +-----+ + db.set_a_invokes(CycleQuery::B); + db.set_b_invokes(CycleQuery::A); + + assert!(db.cycle_a().is_err()); + db.set_c_invokes(CycleQuery::A); // force new revisi5on + + // on this run + expect![[r#" + Err( + Error { + cycle: [ + "cycles::CycleAQuery::cycle_a(())", + "cycles::CycleBQuery::cycle_b(())", + ], + }, + ) + "#]] + .assert_debug_eq(&db.cycle_a()); +} + +#[test] +fn cycle_appears() { + let mut db = DatabaseImpl::default(); + + // A --> B + db.set_a_invokes(CycleQuery::B); + db.set_b_invokes(CycleQuery::None); + assert!(db.cycle_a().is_ok()); + + // A --> B + // ^ | + // +-----+ + db.set_b_invokes(CycleQuery::A); + tracing::debug!("Set Cycle Leaf"); + assert!(db.cycle_a().is_err()); +} + +#[test] +fn cycle_disappears() { + let mut db = DatabaseImpl::default(); + + // A --> B + // ^ | + // +-----+ + db.set_a_invokes(CycleQuery::B); + db.set_b_invokes(CycleQuery::A); + assert!(db.cycle_a().is_err()); + + // A --> B + db.set_b_invokes(CycleQuery::None); + assert!(db.cycle_a().is_ok()); +} + +/// A variant on `cycle_disappears` in which the values of +/// `a_invokes` and `b_invokes` are set with durability values. +/// If we are not careful, this could cause us to overlook +/// the fact that the cycle will no longer occur. +#[test] +fn cycle_disappears_durability() { + let mut db = DatabaseImpl::default(); + db.set_a_invokes_with_durability(CycleQuery::B, Durability::LOW); + db.set_b_invokes_with_durability(CycleQuery::A, Durability::HIGH); + + let res = db.cycle_a(); + assert!(res.is_err()); + + // At this point, `a` read `LOW` input, and `b` read `HIGH` input. However, + // because `b` participates in the same cycle as `a`, its final durability + // should be `LOW`. + // + // Check that setting a `LOW` input causes us to re-execute `b` query, and + // observe that the cycle goes away. + db.set_a_invokes_with_durability(CycleQuery::None, Durability::LOW); + + let res = db.cycle_b(); + assert!(res.is_ok()); +} + +#[test] +fn cycle_mixed_1() { + let mut db = DatabaseImpl::default(); + // A --> B <-- C + // | ^ + // +-----+ + db.set_a_invokes(CycleQuery::B); + db.set_b_invokes(CycleQuery::C); + db.set_c_invokes(CycleQuery::B); + + let u = db.cycle_c(); + expect![[r#" + Err( + Error { + cycle: [ + "cycles::CycleBQuery::cycle_b(())", + "cycles::CycleCQuery::cycle_c(())", + ], + }, + ) + "#]] + .assert_debug_eq(&u); +} + +#[test] +fn cycle_mixed_2() { + let mut db = DatabaseImpl::default(); + + // Configuration: + // + // A --> B --> C + // ^ | + // +-----------+ + db.set_a_invokes(CycleQuery::B); + db.set_b_invokes(CycleQuery::C); + db.set_c_invokes(CycleQuery::A); + + let u = db.cycle_a(); + expect![[r#" + Err( + Error { + cycle: [ + "cycles::CycleAQuery::cycle_a(())", + "cycles::CycleBQuery::cycle_b(())", + "cycles::CycleCQuery::cycle_c(())", + ], + }, + ) + "#]] + .assert_debug_eq(&u); +} + +#[test] +fn cycle_deterministic_order() { + // No matter whether we start from A or B, we get the same set of participants: + let db = || { + let mut db = DatabaseImpl::default(); + // A --> B + // ^ | + // +-----+ + db.set_a_invokes(CycleQuery::B); + db.set_b_invokes(CycleQuery::A); + db + }; + let a = db().cycle_a(); + let b = db().cycle_b(); + expect![[r#" + ( + Err( + Error { + cycle: [ + "cycles::CycleAQuery::cycle_a(())", + "cycles::CycleBQuery::cycle_b(())", + ], + }, + ), + Err( + Error { + cycle: [ + "cycles::CycleAQuery::cycle_a(())", + "cycles::CycleBQuery::cycle_b(())", + ], + }, + ), + ) + "#]] + .assert_debug_eq(&(a, b)); +} + +#[test] +fn cycle_multiple() { + // No matter whether we start from A or B, we get the same set of participants: + let mut db = DatabaseImpl::default(); + + // Configuration: + // + // A --> B <-- C + // ^ | ^ + // +-----+ | + // | | + // +-----+ + // + // Here, conceptually, B encounters a cycle with A and then + // recovers. + db.set_a_invokes(CycleQuery::B); + db.set_b_invokes(CycleQuery::AthenC); + db.set_c_invokes(CycleQuery::B); + + let c = db.cycle_c(); + let b = db.cycle_b(); + let a = db.cycle_a(); + expect![[r#" + ( + Err( + Error { + cycle: [ + "cycles::CycleAQuery::cycle_a(())", + "cycles::CycleBQuery::cycle_b(())", + ], + }, + ), + Err( + Error { + cycle: [ + "cycles::CycleAQuery::cycle_a(())", + "cycles::CycleBQuery::cycle_b(())", + ], + }, + ), + Err( + Error { + cycle: [ + "cycles::CycleAQuery::cycle_a(())", + "cycles::CycleBQuery::cycle_b(())", + ], + }, + ), + ) + "#]] + .assert_debug_eq(&(a, b, c)); +} + +#[test] +fn cycle_recovery_set_but_not_participating() { + let mut db = DatabaseImpl::default(); + + // A --> C -+ + // ^ | + // +--+ + db.set_a_invokes(CycleQuery::C); + db.set_c_invokes(CycleQuery::C); + + // Here we expect C to panic and A not to recover: + let r = extract_cycle(|| drop(db.cycle_a())); + expect![[r#" + [ + "cycles::CycleCQuery::cycle_c(())", + ] + "#]] + .assert_debug_eq(&r.all_participants(&db)); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/dyn_trait.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/dyn_trait.rs new file mode 100644 index 00000000000..6075ae5c11e --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/dyn_trait.rs @@ -0,0 +1,28 @@ +//! Test that you can implement a query using a `dyn Trait` setup. + +#[ra_salsa::database(DynTraitStorage)] +#[derive(Default)] +struct DynTraitDatabase { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for DynTraitDatabase {} + +#[ra_salsa::query_group(DynTraitStorage)] +trait DynTrait { + #[ra_salsa::input] + fn input(&self, x: u32) -> u32; + + fn output(&self, x: u32) -> u32; +} + +fn output(db: &dyn DynTrait, x: u32) -> u32 { + db.input(x) * 2 +} + +#[test] +fn dyn_trait() { + let mut query = DynTraitDatabase::default(); + query.set_input(22, 23); + assert_eq!(query.output(22), 46); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/constants.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/constants.rs new file mode 100644 index 00000000000..6e51545b60a --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/constants.rs @@ -0,0 +1,145 @@ +use crate::implementation::{TestContext, TestContextImpl}; +use ra_salsa::debug::DebugQueryTable; +use ra_salsa::Durability; + +#[ra_salsa::query_group(Constants)] +pub(crate) trait ConstantsDatabase: TestContext { + #[ra_salsa::input] + fn input(&self, key: char) -> usize; + + fn add(&self, key1: char, key2: char) -> usize; + + fn add3(&self, key1: char, key2: char, key3: char) -> usize; +} + +fn add(db: &dyn ConstantsDatabase, key1: char, key2: char) -> usize { + db.log().add(format!("add({key1}, {key2})")); + db.input(key1) + db.input(key2) +} + +fn add3(db: &dyn ConstantsDatabase, key1: char, key2: char, key3: char) -> usize { + db.log().add(format!("add3({key1}, {key2}, {key3})")); + db.add(key1, key2) + db.input(key3) +} + +// Test we can assign a constant and things will be correctly +// recomputed afterwards. +#[test] +fn invalidate_constant() { + let db = &mut TestContextImpl::default(); + db.set_input_with_durability('a', 44, Durability::HIGH); + db.set_input_with_durability('b', 22, Durability::HIGH); + assert_eq!(db.add('a', 'b'), 66); + + db.set_input_with_durability('a', 66, Durability::HIGH); + assert_eq!(db.add('a', 'b'), 88); +} + +#[test] +fn invalidate_constant_1() { + let db = &mut TestContextImpl::default(); + + // Not constant: + db.set_input('a', 44); + assert_eq!(db.add('a', 'a'), 88); + + // Becomes constant: + db.set_input_with_durability('a', 44, Durability::HIGH); + assert_eq!(db.add('a', 'a'), 88); + + // Invalidates: + db.set_input_with_durability('a', 33, Durability::HIGH); + assert_eq!(db.add('a', 'a'), 66); +} + +// Test cases where we assign same value to 'a' after declaring it a +// constant. +#[test] +fn set_after_constant_same_value() { + let db = &mut TestContextImpl::default(); + db.set_input_with_durability('a', 44, Durability::HIGH); + db.set_input_with_durability('a', 44, Durability::HIGH); + db.set_input('a', 44); +} + +#[test] +fn not_constant() { + let mut db = TestContextImpl::default(); + + db.set_input('a', 22); + db.set_input('b', 44); + assert_eq!(db.add('a', 'b'), 66); + assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b'))); +} + +#[test] +fn durability() { + let mut db = TestContextImpl::default(); + + db.set_input_with_durability('a', 22, Durability::HIGH); + db.set_input_with_durability('b', 44, Durability::HIGH); + assert_eq!(db.add('a', 'b'), 66); + assert_eq!(Durability::HIGH, AddQuery.in_db(&db).durability(('a', 'b'))); +} + +#[test] +fn mixed_constant() { + let mut db = TestContextImpl::default(); + + db.set_input_with_durability('a', 22, Durability::HIGH); + db.set_input('b', 44); + assert_eq!(db.add('a', 'b'), 66); + assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b'))); +} + +#[test] +fn becomes_constant_with_change() { + let mut db = TestContextImpl::default(); + + db.set_input('a', 22); + db.set_input('b', 44); + assert_eq!(db.add('a', 'b'), 66); + assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b'))); + + db.set_input_with_durability('a', 23, Durability::HIGH); + assert_eq!(db.add('a', 'b'), 67); + assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b'))); + + db.set_input_with_durability('b', 45, Durability::HIGH); + assert_eq!(db.add('a', 'b'), 68); + assert_eq!(Durability::HIGH, AddQuery.in_db(&db).durability(('a', 'b'))); + + db.set_input_with_durability('b', 45, Durability::MEDIUM); + assert_eq!(db.add('a', 'b'), 68); + assert_eq!(Durability::MEDIUM, AddQuery.in_db(&db).durability(('a', 'b'))); +} + +// Test a subtle case in which an input changes from constant to +// non-constant, but its value doesn't change. If we're not careful, +// this can cause us to incorrectly consider derived values as still +// being constant. +#[test] +fn constant_to_non_constant() { + let mut db = TestContextImpl::default(); + + db.set_input_with_durability('a', 11, Durability::HIGH); + db.set_input_with_durability('b', 22, Durability::HIGH); + db.set_input_with_durability('c', 33, Durability::HIGH); + + // Here, `add3` invokes `add`, which yields 33. Both calls are + // constant. + assert_eq!(db.add3('a', 'b', 'c'), 66); + + db.set_input('a', 11); + + // Here, `add3` invokes `add`, which *still* yields 33, but which + // is no longer constant. Since value didn't change, we might + // preserve `add3` unchanged, not noticing that it is no longer + // constant. + assert_eq!(db.add3('a', 'b', 'c'), 66); + + // In that case, we would not get the correct result here, when + // 'a' changes *again*. + db.set_input('a', 22); + assert_eq!(db.add3('a', 'b', 'c'), 77); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/counter.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/counter.rs new file mode 100644 index 00000000000..c04857e24c9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/counter.rs @@ -0,0 +1,14 @@ +use std::cell::Cell; + +#[derive(Default)] +pub(crate) struct Counter { + value: Cell, +} + +impl Counter { + pub(crate) fn increment(&self) -> usize { + let v = self.value.get(); + self.value.set(v + 1); + v + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/implementation.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/implementation.rs new file mode 100644 index 00000000000..e9a59c46304 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/implementation.rs @@ -0,0 +1,59 @@ +use crate::constants; +use crate::counter::Counter; +use crate::log::Log; +use crate::memoized_dep_inputs; +use crate::memoized_inputs; +use crate::memoized_volatile; + +pub(crate) trait TestContext: ra_salsa::Database { + fn clock(&self) -> &Counter; + fn log(&self) -> &Log; +} + +#[ra_salsa::database( + constants::Constants, + memoized_dep_inputs::MemoizedDepInputs, + memoized_inputs::MemoizedInputs, + memoized_volatile::MemoizedVolatile +)] +#[derive(Default)] +pub(crate) struct TestContextImpl { + storage: ra_salsa::Storage, + clock: Counter, + log: Log, +} + +impl TestContextImpl { + #[track_caller] + pub(crate) fn assert_log(&self, expected_log: &[&str]) { + let expected_text = &format!("{expected_log:#?}"); + let actual_text = &format!("{:#?}", self.log().take()); + + if expected_text == actual_text { + return; + } + + #[allow(clippy::print_stdout)] + for diff in dissimilar::diff(expected_text, actual_text) { + match diff { + dissimilar::Chunk::Delete(l) => println!("-{l}"), + dissimilar::Chunk::Equal(l) => println!(" {l}"), + dissimilar::Chunk::Insert(r) => println!("+{r}"), + } + } + + panic!("incorrect log results"); + } +} + +impl TestContext for TestContextImpl { + fn clock(&self) -> &Counter { + &self.clock + } + + fn log(&self) -> &Log { + &self.log + } +} + +impl ra_salsa::Database for TestContextImpl {} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/log.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/log.rs new file mode 100644 index 00000000000..1ee57fe667d --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/log.rs @@ -0,0 +1,16 @@ +use std::cell::RefCell; + +#[derive(Default)] +pub(crate) struct Log { + data: RefCell>, +} + +impl Log { + pub(crate) fn add(&self, text: impl Into) { + self.data.borrow_mut().push(text.into()); + } + + pub(crate) fn take(&self) -> Vec { + self.data.take() + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/main.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/main.rs new file mode 100644 index 00000000000..bcd13c75f71 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/main.rs @@ -0,0 +1,9 @@ +mod constants; +mod counter; +mod implementation; +mod log; +mod memoized_dep_inputs; +mod memoized_inputs; +mod memoized_volatile; + +fn main() {} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_dep_inputs.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_dep_inputs.rs new file mode 100644 index 00000000000..0043bb45745 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_dep_inputs.rs @@ -0,0 +1,60 @@ +use crate::implementation::{TestContext, TestContextImpl}; + +#[ra_salsa::query_group(MemoizedDepInputs)] +pub(crate) trait MemoizedDepInputsContext: TestContext { + fn dep_memoized2(&self) -> usize; + fn dep_memoized1(&self) -> usize; + #[ra_salsa::dependencies] + fn dep_derived1(&self) -> usize; + #[ra_salsa::input] + fn dep_input1(&self) -> usize; + #[ra_salsa::input] + fn dep_input2(&self) -> usize; +} + +fn dep_memoized2(db: &dyn MemoizedDepInputsContext) -> usize { + db.log().add("Memoized2 invoked"); + db.dep_memoized1() +} + +fn dep_memoized1(db: &dyn MemoizedDepInputsContext) -> usize { + db.log().add("Memoized1 invoked"); + db.dep_derived1() * 2 +} + +fn dep_derived1(db: &dyn MemoizedDepInputsContext) -> usize { + db.log().add("Derived1 invoked"); + db.dep_input1() / 2 +} + +#[test] +fn revalidate() { + let db = &mut TestContextImpl::default(); + + db.set_dep_input1(0); + + // Initial run starts from Memoized2: + let v = db.dep_memoized2(); + assert_eq!(v, 0); + db.assert_log(&["Memoized2 invoked", "Memoized1 invoked", "Derived1 invoked"]); + + // After that, we first try to validate Memoized1 but wind up + // running Memoized2. Note that we don't try to validate + // Derived1, so it is invoked by Memoized1. + db.set_dep_input1(44); + let v = db.dep_memoized2(); + assert_eq!(v, 44); + db.assert_log(&["Memoized1 invoked", "Derived1 invoked", "Memoized2 invoked"]); + + // Here validation of Memoized1 succeeds so Memoized2 never runs. + db.set_dep_input1(45); + let v = db.dep_memoized2(); + assert_eq!(v, 44); + db.assert_log(&["Memoized1 invoked", "Derived1 invoked"]); + + // Here, a change to input2 doesn't affect us, so nothing runs. + db.set_dep_input2(45); + let v = db.dep_memoized2(); + assert_eq!(v, 44); + db.assert_log(&[]); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_inputs.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_inputs.rs new file mode 100644 index 00000000000..007dc3db95a --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_inputs.rs @@ -0,0 +1,76 @@ +use crate::implementation::{TestContext, TestContextImpl}; + +#[ra_salsa::query_group(MemoizedInputs)] +pub(crate) trait MemoizedInputsContext: TestContext { + fn max(&self) -> usize; + #[ra_salsa::input] + fn input1(&self) -> usize; + #[ra_salsa::input] + fn input2(&self) -> usize; +} + +fn max(db: &dyn MemoizedInputsContext) -> usize { + db.log().add("Max invoked"); + std::cmp::max(db.input1(), db.input2()) +} + +#[test] +fn revalidate() { + let db = &mut TestContextImpl::default(); + + db.set_input1(0); + db.set_input2(0); + + let v = db.max(); + assert_eq!(v, 0); + db.assert_log(&["Max invoked"]); + + let v = db.max(); + assert_eq!(v, 0); + db.assert_log(&[]); + + db.set_input1(44); + db.assert_log(&[]); + + let v = db.max(); + assert_eq!(v, 44); + db.assert_log(&["Max invoked"]); + + let v = db.max(); + assert_eq!(v, 44); + db.assert_log(&[]); + + db.set_input1(44); + db.assert_log(&[]); + db.set_input2(66); + db.assert_log(&[]); + db.set_input1(64); + db.assert_log(&[]); + + let v = db.max(); + assert_eq!(v, 66); + db.assert_log(&["Max invoked"]); + + let v = db.max(); + assert_eq!(v, 66); + db.assert_log(&[]); +} + +/// Test that invoking `set` on an input with the same value still +/// triggers a new revision. +#[test] +fn set_after_no_change() { + let db = &mut TestContextImpl::default(); + + db.set_input2(0); + + db.set_input1(44); + let v = db.max(); + assert_eq!(v, 44); + db.assert_log(&["Max invoked"]); + + db.set_input1(44); + let v = db.max(); + assert_eq!(v, 44); + db.assert_log(&["Max invoked"]); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_volatile.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_volatile.rs new file mode 100644 index 00000000000..cd00cc2e6cc --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/incremental/memoized_volatile.rs @@ -0,0 +1,77 @@ +use crate::implementation::{TestContext, TestContextImpl}; +use ra_salsa::{Database, Durability}; + +#[ra_salsa::query_group(MemoizedVolatile)] +pub(crate) trait MemoizedVolatileContext: TestContext { + // Queries for testing a "volatile" value wrapped by + // memoization. + fn memoized2(&self) -> usize; + fn memoized1(&self) -> usize; + fn volatile(&self) -> usize; +} + +fn memoized2(db: &dyn MemoizedVolatileContext) -> usize { + db.log().add("Memoized2 invoked"); + db.memoized1() +} + +fn memoized1(db: &dyn MemoizedVolatileContext) -> usize { + db.log().add("Memoized1 invoked"); + let v = db.volatile(); + v / 2 +} + +fn volatile(db: &dyn MemoizedVolatileContext) -> usize { + db.log().add("Volatile invoked"); + db.salsa_runtime().report_untracked_read(); + db.clock().increment() +} + +#[test] +fn volatile_x2() { + let query = TestContextImpl::default(); + + // Invoking volatile twice doesn't execute twice, because volatile + // queries are memoized by default. + query.volatile(); + query.volatile(); + query.assert_log(&["Volatile invoked"]); +} + +/// Test that: +/// +/// - On the first run of R0, we recompute everything. +/// - On the second run of R1, we recompute nothing. +/// - On the first run of R1, we recompute Memoized1 but not Memoized2 (since Memoized1 result +/// did not change). +/// - On the second run of R1, we recompute nothing. +/// - On the first run of R2, we recompute everything (since Memoized1 result *did* change). +#[test] +fn revalidate() { + let mut query = TestContextImpl::default(); + + query.memoized2(); + query.assert_log(&["Memoized2 invoked", "Memoized1 invoked", "Volatile invoked"]); + + query.memoized2(); + query.assert_log(&[]); + + // Second generation: volatile will change (to 1) but memoized1 + // will not (still 0, as 1/2 = 0) + query.synthetic_write(Durability::LOW); + query.memoized2(); + query.assert_log(&["Volatile invoked", "Memoized1 invoked"]); + query.memoized2(); + query.assert_log(&[]); + + // Third generation: volatile will change (to 2) and memoized1 + // will too (to 1). Therefore, after validating that Memoized1 + // changed, we now invoke Memoized2. + query.synthetic_write(Durability::LOW); + + query.memoized2(); + query.assert_log(&["Volatile invoked", "Memoized1 invoked", "Memoized2 invoked"]); + + query.memoized2(); + query.assert_log(&[]); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/interned.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/interned.rs new file mode 100644 index 00000000000..108b129fa3f --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/interned.rs @@ -0,0 +1,90 @@ +//! Test that you can implement a query using a `dyn Trait` setup. + +use ra_salsa::InternId; + +#[ra_salsa::database(InternStorage)] +#[derive(Default)] +struct Database { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for Database {} + +impl ra_salsa::ParallelDatabase for Database { + fn snapshot(&self) -> ra_salsa::Snapshot { + ra_salsa::Snapshot::new(Database { storage: self.storage.snapshot() }) + } +} + +#[ra_salsa::query_group(InternStorage)] +trait Intern { + #[ra_salsa::interned] + fn intern1(&self, x: String) -> InternId; + + #[ra_salsa::interned] + fn intern2(&self, x: String, y: String) -> InternId; + + #[ra_salsa::interned] + fn intern_key(&self, x: String) -> InternKey; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct InternKey(InternId); + +impl ra_salsa::InternKey for InternKey { + fn from_intern_id(v: InternId) -> Self { + InternKey(v) + } + + fn as_intern_id(&self) -> InternId { + self.0 + } +} + +#[test] +fn test_intern1() { + let db = Database::default(); + let foo0 = db.intern1("foo".to_owned()); + let bar0 = db.intern1("bar".to_owned()); + let foo1 = db.intern1("foo".to_owned()); + let bar1 = db.intern1("bar".to_owned()); + + assert_eq!(foo0, foo1); + assert_eq!(bar0, bar1); + assert_ne!(foo0, bar0); + + assert_eq!("foo".to_owned(), db.lookup_intern1(foo0)); + assert_eq!("bar".to_owned(), db.lookup_intern1(bar0)); +} + +#[test] +fn test_intern2() { + let db = Database::default(); + let foo0 = db.intern2("x".to_owned(), "foo".to_owned()); + let bar0 = db.intern2("x".to_owned(), "bar".to_owned()); + let foo1 = db.intern2("x".to_owned(), "foo".to_owned()); + let bar1 = db.intern2("x".to_owned(), "bar".to_owned()); + + assert_eq!(foo0, foo1); + assert_eq!(bar0, bar1); + assert_ne!(foo0, bar0); + + assert_eq!(("x".to_owned(), "foo".to_owned()), db.lookup_intern2(foo0)); + assert_eq!(("x".to_owned(), "bar".to_owned()), db.lookup_intern2(bar0)); +} + +#[test] +fn test_intern_key() { + let db = Database::default(); + let foo0 = db.intern_key("foo".to_owned()); + let bar0 = db.intern_key("bar".to_owned()); + let foo1 = db.intern_key("foo".to_owned()); + let bar1 = db.intern_key("bar".to_owned()); + + assert_eq!(foo0, foo1); + assert_eq!(bar0, bar1); + assert_ne!(foo0, bar0); + + assert_eq!("foo".to_owned(), db.lookup_intern_key(foo0)); + assert_eq!("bar".to_owned(), db.lookup_intern_key(bar0)); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/lru.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/lru.rs new file mode 100644 index 00000000000..f351f242468 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/lru.rs @@ -0,0 +1,104 @@ +//! Test setting LRU actually limits the number of things in the database; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +#[derive(Debug, PartialEq, Eq)] +struct HotPotato(u32); + +static N_POTATOES: AtomicUsize = AtomicUsize::new(0); + +impl HotPotato { + fn new(id: u32) -> HotPotato { + N_POTATOES.fetch_add(1, Ordering::SeqCst); + HotPotato(id) + } +} + +impl Drop for HotPotato { + fn drop(&mut self) { + N_POTATOES.fetch_sub(1, Ordering::SeqCst); + } +} + +#[ra_salsa::query_group(QueryGroupStorage)] +trait QueryGroup: ra_salsa::Database { + #[ra_salsa::lru] + fn get(&self, x: u32) -> Arc; + #[ra_salsa::lru] + fn get_volatile(&self, x: u32) -> usize; +} + +fn get(_db: &dyn QueryGroup, x: u32) -> Arc { + Arc::new(HotPotato::new(x)) +} + +fn get_volatile(db: &dyn QueryGroup, _x: u32) -> usize { + static COUNTER: AtomicUsize = AtomicUsize::new(0); + db.salsa_runtime().report_untracked_read(); + COUNTER.fetch_add(1, Ordering::SeqCst) +} + +#[ra_salsa::database(QueryGroupStorage)] +#[derive(Default)] +struct Database { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for Database {} + +#[test] +fn lru_works() { + let mut db = Database::default(); + GetQuery.in_db_mut(&mut db).set_lru_capacity(32); + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 0); + + for i in 0..128u32 { + let p = db.get(i); + assert_eq!(p.0, i) + } + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 32); + + for i in 0..128u32 { + let p = db.get(i); + assert_eq!(p.0, i) + } + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 32); + + GetQuery.in_db_mut(&mut db).set_lru_capacity(32); + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 32); + + GetQuery.in_db_mut(&mut db).set_lru_capacity(64); + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 32); + for i in 0..128u32 { + let p = db.get(i); + assert_eq!(p.0, i) + } + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 64); + + // Special case: setting capacity to zero disables LRU + GetQuery.in_db_mut(&mut db).set_lru_capacity(0); + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 64); + for i in 0..128u32 { + let p = db.get(i); + assert_eq!(p.0, i) + } + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 128); + + drop(db); + assert_eq!(N_POTATOES.load(Ordering::SeqCst), 0); +} + +#[test] +fn lru_doesnt_break_volatile_queries() { + let mut db = Database::default(); + GetVolatileQuery.in_db_mut(&mut db).set_lru_capacity(32); + // Here, we check that we execute each volatile query at most once, despite + // LRU. That does mean that we have more values in DB than the LRU capacity, + // but it's much better than inconsistent results from volatile queries! + for i in (0..3).flat_map(|_| 0..128usize) { + let x = db.get_volatile(i as u32); + assert_eq!(x, i) + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/macros.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/macros.rs new file mode 100644 index 00000000000..7bb6369b500 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/macros.rs @@ -0,0 +1,12 @@ +#[ra_salsa::query_group(MyStruct)] +trait MyDatabase: ra_salsa::Database { + #[ra_salsa::invoke(another_module::another_name)] + fn my_query(&self, key: ()) -> (); +} + +mod another_module { + #[allow(dead_code)] + pub(crate) fn another_name(_: &dyn crate::MyDatabase, (): ()) {} +} + +fn main() {} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/no_send_sync.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/no_send_sync.rs new file mode 100644 index 00000000000..56bd3f4a7ed --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/no_send_sync.rs @@ -0,0 +1,31 @@ +use std::rc::Rc; + +#[ra_salsa::query_group(NoSendSyncStorage)] +trait NoSendSyncDatabase: ra_salsa::Database { + fn no_send_sync_value(&self, key: bool) -> Rc; + fn no_send_sync_key(&self, key: Rc) -> bool; +} + +fn no_send_sync_value(_db: &dyn NoSendSyncDatabase, key: bool) -> Rc { + Rc::new(key) +} + +fn no_send_sync_key(_db: &dyn NoSendSyncDatabase, key: Rc) -> bool { + *key +} + +#[ra_salsa::database(NoSendSyncStorage)] +#[derive(Default)] +struct DatabaseImpl { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for DatabaseImpl {} + +#[test] +fn no_send_sync() { + let db = DatabaseImpl::default(); + + assert_eq!(db.no_send_sync_value(true), Rc::new(true)); + assert!(!db.no_send_sync_key(Rc::new(false))); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/on_demand_inputs.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/on_demand_inputs.rs new file mode 100644 index 00000000000..4d7832f9ba0 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/on_demand_inputs.rs @@ -0,0 +1,147 @@ +//! Test that "on-demand" input pattern works. +//! +//! On-demand inputs are inputs computed lazily on the fly. They are simulated +//! via a b query with zero inputs, which uses `add_synthetic_read` to +//! tweak durability and `invalidate` to clear the input. + +#![allow(clippy::disallowed_types, clippy::type_complexity)] + +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use ra_salsa::{Database as _, Durability, EventKind}; + +#[ra_salsa::query_group(QueryGroupStorage)] +trait QueryGroup: ra_salsa::Database + AsRef> { + fn a(&self, x: u32) -> u32; + fn b(&self, x: u32) -> u32; + fn c(&self, x: u32) -> u32; +} + +fn a(db: &dyn QueryGroup, x: u32) -> u32 { + let durability = if x % 2 == 0 { Durability::LOW } else { Durability::HIGH }; + db.salsa_runtime().report_synthetic_read(durability); + let external_state: &HashMap = db.as_ref(); + external_state[&x] +} + +fn b(db: &dyn QueryGroup, x: u32) -> u32 { + db.a(x) +} + +fn c(db: &dyn QueryGroup, x: u32) -> u32 { + db.b(x) +} + +#[ra_salsa::database(QueryGroupStorage)] +#[derive(Default)] +struct Database { + storage: ra_salsa::Storage, + external_state: HashMap, + on_event: Option>, +} + +impl ra_salsa::Database for Database { + fn salsa_event(&self, event: ra_salsa::Event) { + if let Some(cb) = &self.on_event { + cb(self, event) + } + } +} + +impl AsRef> for Database { + fn as_ref(&self) -> &HashMap { + &self.external_state + } +} + +#[test] +fn on_demand_input_works() { + let mut db = Database::default(); + + db.external_state.insert(1, 10); + assert_eq!(db.b(1), 10); + assert_eq!(db.a(1), 10); + + // We changed external state, but haven't signaled about this yet, + // so we expect to see the old answer + db.external_state.insert(1, 92); + assert_eq!(db.b(1), 10); + assert_eq!(db.a(1), 10); + + AQuery.in_db_mut(&mut db).invalidate(&1); + assert_eq!(db.b(1), 92); + assert_eq!(db.a(1), 92); + + // Downstream queries should also be rerun if we call `a` first. + db.external_state.insert(1, 50); + AQuery.in_db_mut(&mut db).invalidate(&1); + assert_eq!(db.a(1), 50); + assert_eq!(db.b(1), 50); +} + +#[test] +fn on_demand_input_durability() { + let mut db = Database::default(); + + let events = Rc::new(RefCell::new(vec![])); + db.on_event = Some(Box::new({ + let events = events.clone(); + move |db, event| { + if let EventKind::WillCheckCancellation = event.kind { + // these events are not interesting + } else { + events.borrow_mut().push(format!("{:?}", event.debug(db))) + } + } + })); + + events.replace(vec![]); + db.external_state.insert(1, 10); + db.external_state.insert(2, 20); + assert_eq!(db.b(1), 10); + assert_eq!(db.b(2), 20); + expect_test::expect![[r#" + RefCell { + value: [ + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::BQuery::b(1) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(1) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::BQuery::b(2) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }", + ], + } + "#]].assert_debug_eq(&events); + + db.synthetic_write(Durability::LOW); + events.replace(vec![]); + assert_eq!(db.c(1), 10); + assert_eq!(db.c(2), 20); + // Re-execute `a(2)` because that has low durability, but not `a(1)` + expect_test::expect![[r#" + RefCell { + value: [ + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::CQuery::c(1) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::BQuery::b(1) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::CQuery::c(2) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::BQuery::b(2) } }", + ], + } + "#]].assert_debug_eq(&events); + + db.synthetic_write(Durability::HIGH); + events.replace(vec![]); + assert_eq!(db.c(1), 10); + assert_eq!(db.c(2), 20); + // Re-execute both `a(1)` and `a(2)`, but we don't re-execute any `b` queries as the + // result didn't actually change. + expect_test::expect![[r#" + RefCell { + value: [ + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(1) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::CQuery::c(1) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::CQuery::c(2) } }", + ], + } + "#]].assert_debug_eq(&events); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/panic_safely.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/panic_safely.rs new file mode 100644 index 00000000000..047a50eb4b2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/panic_safely.rs @@ -0,0 +1,93 @@ +use ra_salsa::{Database, ParallelDatabase, Snapshot}; +use std::panic::{self, AssertUnwindSafe}; +use std::sync::atomic::{AtomicU32, Ordering::SeqCst}; + +#[ra_salsa::query_group(PanicSafelyStruct)] +trait PanicSafelyDatabase: ra_salsa::Database { + #[ra_salsa::input] + fn one(&self) -> usize; + + fn panic_safely(&self) -> (); + + fn outer(&self) -> (); +} + +fn panic_safely(db: &dyn PanicSafelyDatabase) { + assert_eq!(db.one(), 1); +} + +static OUTER_CALLS: AtomicU32 = AtomicU32::new(0); + +fn outer(db: &dyn PanicSafelyDatabase) { + OUTER_CALLS.fetch_add(1, SeqCst); + db.panic_safely(); +} + +#[ra_salsa::database(PanicSafelyStruct)] +#[derive(Default)] +struct DatabaseStruct { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for DatabaseStruct {} + +impl ra_salsa::ParallelDatabase for DatabaseStruct { + fn snapshot(&self) -> Snapshot { + Snapshot::new(DatabaseStruct { storage: self.storage.snapshot() }) + } +} + +#[test] +fn should_panic_safely() { + let mut db = DatabaseStruct::default(); + db.set_one(0); + + // Invoke `db.panic_safely() without having set `db.one`. `db.one` will + // return 0 and we should catch the panic. + let result = panic::catch_unwind(AssertUnwindSafe({ + let db = db.snapshot(); + move || db.panic_safely() + })); + assert!(result.is_err()); + + // Set `db.one` to 1 and assert ok + db.set_one(1); + let result = panic::catch_unwind(AssertUnwindSafe(|| db.panic_safely())); + assert!(result.is_ok()); + + // Check, that memoized outer is not invalidated by a panic + { + assert_eq!(OUTER_CALLS.load(SeqCst), 0); + db.outer(); + assert_eq!(OUTER_CALLS.load(SeqCst), 1); + + db.set_one(0); + let result = panic::catch_unwind(AssertUnwindSafe(|| db.outer())); + assert!(result.is_err()); + assert_eq!(OUTER_CALLS.load(SeqCst), 1); + + db.set_one(1); + db.outer(); + assert_eq!(OUTER_CALLS.load(SeqCst), 2); + } +} + +#[test] +fn storages_are_unwind_safe() { + fn check_unwind_safe() {} + check_unwind_safe::<&DatabaseStruct>(); +} + +#[test] +fn panics_clear_query_stack() { + let db = DatabaseStruct::default(); + + // Invoke `db.panic_if_not_one() without having set `db.input`. `db.input` + // will default to 0 and we should catch the panic. + let result = panic::catch_unwind(AssertUnwindSafe(|| db.panic_safely())); + assert!(result.is_err()); + + // The database has been poisoned and any attempt to increment the + // revision should panic. + assert_eq!(db.salsa_runtime().active_query(), None); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/cancellation.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/cancellation.rs new file mode 100644 index 00000000000..e47a8ef9aa8 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/cancellation.rs @@ -0,0 +1,132 @@ +use crate::setup::{CancellationFlag, Knobs, ParDatabase, ParDatabaseImpl, WithValue}; +use ra_salsa::{Cancelled, ParallelDatabase}; + +macro_rules! assert_cancelled { + ($thread:expr) => { + match $thread.join() { + Ok(value) => panic!("expected cancellation, got {:?}", value), + Err(payload) => match payload.downcast::() { + Ok(_) => {} + Err(payload) => ::std::panic::resume_unwind(payload), + }, + } + }; +} + +/// Add test where a call to `sum` is cancelled by a simultaneous +/// write. Check that we recompute the result in next revision, even +/// though none of the inputs have changed. +#[test] +fn in_par_get_set_cancellation_immediate() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 100); + db.set_input('b', 10); + db.set_input('c', 1); + db.set_input('d', 0); + + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + move || { + // This will not return until it sees cancellation is + // signaled. + db.knobs().sum_signal_on_entry.with_value(1, || { + db.knobs() + .sum_wait_for_cancellation + .with_value(CancellationFlag::Panic, || db.sum("abc")) + }) + } + }); + + // Wait until we have entered `sum` in the other thread. + db.wait_for(1); + + // Try to set the input. This will signal cancellation. + db.set_input('d', 1000); + + // This should re-compute the value (even though no input has changed). + let thread2 = std::thread::spawn({ + let db = db.snapshot(); + move || db.sum("abc") + }); + + assert_eq!(db.sum("d"), 1000); + assert_cancelled!(thread1); + assert_eq!(thread2.join().unwrap(), 111); +} + +/// Here, we check that `sum`'s cancellation is propagated +/// to `sum2` properly. +#[test] +fn in_par_get_set_cancellation_transitive() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 100); + db.set_input('b', 10); + db.set_input('c', 1); + db.set_input('d', 0); + + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + move || { + // This will not return until it sees cancellation is + // signaled. + db.knobs().sum_signal_on_entry.with_value(1, || { + db.knobs() + .sum_wait_for_cancellation + .with_value(CancellationFlag::Panic, || db.sum2("abc")) + }) + } + }); + + // Wait until we have entered `sum` in the other thread. + db.wait_for(1); + + // Try to set the input. This will signal cancellation. + db.set_input('d', 1000); + + // This should re-compute the value (even though no input has changed). + let thread2 = std::thread::spawn({ + let db = db.snapshot(); + move || db.sum2("abc") + }); + + assert_eq!(db.sum2("d"), 1000); + assert_cancelled!(thread1); + assert_eq!(thread2.join().unwrap(), 111); +} + +/// https://github.com/ra_salsa-rs/ra_salsa/issues/66 +#[test] +fn no_back_dating_in_cancellation() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 1); + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + move || { + // Here we compute a long-chain of queries, + // but the last one gets cancelled. + db.knobs().sum_signal_on_entry.with_value(1, || { + db.knobs() + .sum_wait_for_cancellation + .with_value(CancellationFlag::Panic, || db.sum3("a")) + }) + } + }); + + db.wait_for(1); + + // Set unrelated input to bump revision + db.set_input('b', 2); + + // Here we should recompuet the whole chain again, clearing the cancellation + // state. If we get `usize::max()` here, it is a bug! + assert_eq!(db.sum3("a"), 1); + + assert_cancelled!(thread1); + + db.set_input('a', 3); + db.set_input('a', 4); + assert_eq!(db.sum3("ab"), 6); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/frozen.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/frozen.rs new file mode 100644 index 00000000000..9e42e261517 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/frozen.rs @@ -0,0 +1,57 @@ +use crate::setup::{ParDatabase, ParDatabaseImpl}; +use crate::signal::Signal; +use ra_salsa::{Database, ParallelDatabase}; +use std::{ + panic::{catch_unwind, AssertUnwindSafe}, + sync::Arc, +}; + +/// Add test where a call to `sum` is cancelled by a simultaneous +/// write. Check that we recompute the result in next revision, even +/// though none of the inputs have changed. +#[test] +fn in_par_get_set_cancellation() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 1); + + let signal = Arc::new(Signal::default()); + + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + let signal = signal.clone(); + move || { + // Check that cancellation flag is not yet set, because + // `set` cannot have been called yet. + catch_unwind(AssertUnwindSafe(|| db.unwind_if_cancelled())).unwrap(); + + // Signal other thread to proceed. + signal.signal(1); + + // Wait for other thread to signal cancellation + catch_unwind(AssertUnwindSafe(|| loop { + db.unwind_if_cancelled(); + std::thread::yield_now(); + })) + .unwrap_err(); + } + }); + + let thread2 = std::thread::spawn({ + move || { + // Wait until thread 1 has asserted that they are not cancelled + // before we invoke `set.` + signal.wait_for(1); + + // This will block until thread1 drops the revision lock. + db.set_input('a', 2); + + db.input('a') + } + }); + + thread1.join().unwrap(); + + let c = thread2.join().unwrap(); + assert_eq!(c, 2); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/independent.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/independent.rs new file mode 100644 index 00000000000..cbbac0608d1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/independent.rs @@ -0,0 +1,29 @@ +use crate::setup::{ParDatabase, ParDatabaseImpl}; +use ra_salsa::ParallelDatabase; + +/// Test two `sum` queries (on distinct keys) executing in different +/// threads. Really just a test that `snapshot` etc compiles. +#[test] +fn in_par_two_independent_queries() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 100); + db.set_input('b', 10); + db.set_input('c', 1); + db.set_input('d', 200); + db.set_input('e', 20); + db.set_input('f', 2); + + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + move || db.sum("abc") + }); + + let thread2 = std::thread::spawn({ + let db = db.snapshot(); + move || db.sum("def") + }); + + assert_eq!(thread1.join().unwrap(), 111); + assert_eq!(thread2.join().unwrap(), 222); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/main.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/main.rs new file mode 100644 index 00000000000..31c0da18375 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/main.rs @@ -0,0 +1,13 @@ +mod setup; + +mod cancellation; +mod frozen; +mod independent; +mod parallel_cycle_all_recover; +mod parallel_cycle_mid_recover; +mod parallel_cycle_none_recover; +mod parallel_cycle_one_recovers; +mod race; +mod signal; +mod stress; +mod true_parallel; diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_all_recover.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_all_recover.rs new file mode 100644 index 00000000000..dabdb3babc0 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_all_recover.rs @@ -0,0 +1,109 @@ +//! Test for cycle recover spread across two threads. +//! See `../cycles.rs` for a complete listing of cycle tests, +//! both intra and cross thread. + +use crate::setup::{Knobs, ParDatabaseImpl}; +use ra_salsa::ParallelDatabase; + +// Recover cycle test: +// +// The pattern is as follows. +// +// Thread A Thread B +// -------- -------- +// a1 b1 +// | wait for stage 1 (blocks) +// signal stage 1 | +// wait for stage 2 (blocks) (unblocked) +// | signal stage 2 +// (unblocked) wait for stage 3 (blocks) +// a2 | +// b1 (blocks -> stage 3) | +// | (unblocked) +// | b2 +// | a1 (cycle detected, recovers) +// | b2 completes, recovers +// | b1 completes, recovers +// a2 sees cycle, recovers +// a1 completes, recovers + +#[test] +fn parallel_cycle_all_recover() { + let db = ParDatabaseImpl::default(); + db.knobs().signal_on_will_block.set(3); + + let thread_a = std::thread::spawn({ + let db = db.snapshot(); + move || db.a1(1) + }); + + let thread_b = std::thread::spawn({ + let db = db.snapshot(); + move || db.b1(1) + }); + + assert_eq!(thread_a.join().unwrap(), 11); + assert_eq!(thread_b.join().unwrap(), 21); +} + +#[ra_salsa::query_group(ParallelCycleAllRecover)] +pub(crate) trait TestDatabase: Knobs { + #[ra_salsa::cycle(recover_a1)] + fn a1(&self, key: i32) -> i32; + + #[ra_salsa::cycle(recover_a2)] + fn a2(&self, key: i32) -> i32; + + #[ra_salsa::cycle(recover_b1)] + fn b1(&self, key: i32) -> i32; + + #[ra_salsa::cycle(recover_b2)] + fn b2(&self, key: i32) -> i32; +} + +fn recover_a1(_db: &dyn TestDatabase, _cycle: &ra_salsa::Cycle, key: &i32) -> i32 { + tracing::debug!("recover_a1"); + key * 10 + 1 +} + +fn recover_a2(_db: &dyn TestDatabase, _cycle: &ra_salsa::Cycle, key: &i32) -> i32 { + tracing::debug!("recover_a2"); + key * 10 + 2 +} + +fn recover_b1(_db: &dyn TestDatabase, _cycle: &ra_salsa::Cycle, key: &i32) -> i32 { + tracing::debug!("recover_b1"); + key * 20 + 1 +} + +fn recover_b2(_db: &dyn TestDatabase, _cycle: &ra_salsa::Cycle, key: &i32) -> i32 { + tracing::debug!("recover_b2"); + key * 20 + 2 +} + +fn a1(db: &dyn TestDatabase, key: i32) -> i32 { + // Wait to create the cycle until both threads have entered + db.signal(1); + db.wait_for(2); + + db.a2(key) +} + +fn a2(db: &dyn TestDatabase, key: i32) -> i32 { + db.b1(key) +} + +fn b1(db: &dyn TestDatabase, key: i32) -> i32 { + // Wait to create the cycle until both threads have entered + db.wait_for(1); + db.signal(2); + + // Wait for thread A to block on this thread + db.wait_for(3); + + db.b2(key) +} + +fn b2(db: &dyn TestDatabase, key: i32) -> i32 { + db.a1(key) +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_mid_recover.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_mid_recover.rs new file mode 100644 index 00000000000..20c508e0b8b --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_mid_recover.rs @@ -0,0 +1,109 @@ +//! Test for cycle recover spread across two threads. +//! See `../cycles.rs` for a complete listing of cycle tests, +//! both intra and cross thread. + +use crate::setup::{Knobs, ParDatabaseImpl}; +use ra_salsa::ParallelDatabase; + +// Recover cycle test: +// +// The pattern is as follows. +// +// Thread A Thread B +// -------- -------- +// a1 b1 +// | wait for stage 1 (blocks) +// signal stage 1 | +// wait for stage 2 (blocks) (unblocked) +// | | +// | b2 +// | b3 +// | a1 (blocks -> stage 2) +// (unblocked) | +// a2 (cycle detected) | +// b3 recovers +// b2 resumes +// b1 panics because bug + +#[test] +fn parallel_cycle_mid_recovers() { + let db = ParDatabaseImpl::default(); + db.knobs().signal_on_will_block.set(2); + + let thread_a = std::thread::spawn({ + let db = db.snapshot(); + move || db.a1(1) + }); + + let thread_b = std::thread::spawn({ + let db = db.snapshot(); + move || db.b1(1) + }); + + // We expect that the recovery function yields + // `1 * 20 + 2`, which is returned (and forwarded) + // to b1, and from there to a2 and a1. + assert_eq!(thread_a.join().unwrap(), 22); + assert_eq!(thread_b.join().unwrap(), 22); +} + +#[ra_salsa::query_group(ParallelCycleMidRecovers)] +pub(crate) trait TestDatabase: Knobs { + fn a1(&self, key: i32) -> i32; + + fn a2(&self, key: i32) -> i32; + + #[ra_salsa::cycle(recover_b1)] + fn b1(&self, key: i32) -> i32; + + fn b2(&self, key: i32) -> i32; + + #[ra_salsa::cycle(recover_b3)] + fn b3(&self, key: i32) -> i32; +} + +fn recover_b1(_db: &dyn TestDatabase, _cycle: &ra_salsa::Cycle, key: &i32) -> i32 { + tracing::debug!("recover_b1"); + key * 20 + 2 +} + +fn recover_b3(_db: &dyn TestDatabase, _cycle: &ra_salsa::Cycle, key: &i32) -> i32 { + tracing::debug!("recover_b1"); + key * 200 + 2 +} + +fn a1(db: &dyn TestDatabase, key: i32) -> i32 { + // tell thread b we have started + db.signal(1); + + // wait for thread b to block on a1 + db.wait_for(2); + + db.a2(key) +} + +fn a2(db: &dyn TestDatabase, key: i32) -> i32 { + // create the cycle + db.b1(key) +} + +fn b1(db: &dyn TestDatabase, key: i32) -> i32 { + // wait for thread a to have started + db.wait_for(1); + + db.b2(key); + + 0 +} + +fn b2(db: &dyn TestDatabase, key: i32) -> i32 { + // will encounter a cycle but recover + db.b3(key); + db.b1(key); // hasn't recovered yet + 0 +} + +fn b3(db: &dyn TestDatabase, key: i32) -> i32 { + // will block on thread a, signaling stage 2 + db.a1(key) +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_none_recover.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_none_recover.rs new file mode 100644 index 00000000000..88d5fee0a22 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_none_recover.rs @@ -0,0 +1,68 @@ +//! Test a cycle where no queries recover that occurs across threads. +//! See the `../cycles.rs` for a complete listing of cycle tests, +//! both intra and cross thread. + +use crate::setup::{Knobs, ParDatabaseImpl}; +use expect_test::expect; +use ra_salsa::ParallelDatabase; + +#[test] +fn parallel_cycle_none_recover() { + let db = ParDatabaseImpl::default(); + db.knobs().signal_on_will_block.set(3); + + let thread_a = std::thread::spawn({ + let db = db.snapshot(); + move || db.a(-1) + }); + + let thread_b = std::thread::spawn({ + let db = db.snapshot(); + move || db.b(-1) + }); + + // We expect B to panic because it detects a cycle (it is the one that calls A, ultimately). + // Right now, it panics with a string. + let err_b = thread_b.join().unwrap_err(); + if let Some(c) = err_b.downcast_ref::() { + expect![[r#" + [ + "parallel::parallel_cycle_none_recover::AQuery::a(-1)", + "parallel::parallel_cycle_none_recover::BQuery::b(-1)", + ] + "#]] + .assert_debug_eq(&c.unexpected_participants(&db)); + } else { + panic!("b failed in an unexpected way: {err_b:?}"); + } + + // We expect A to propagate a panic, which causes us to use the sentinel + // type `Canceled`. + assert!(thread_a.join().unwrap_err().downcast_ref::().is_some()); +} + +#[ra_salsa::query_group(ParallelCycleNoneRecover)] +pub(crate) trait TestDatabase: Knobs { + fn a(&self, key: i32) -> i32; + fn b(&self, key: i32) -> i32; +} + +fn a(db: &dyn TestDatabase, key: i32) -> i32 { + // Wait to create the cycle until both threads have entered + db.signal(1); + db.wait_for(2); + + db.b(key) +} + +fn b(db: &dyn TestDatabase, key: i32) -> i32 { + // Wait to create the cycle until both threads have entered + db.wait_for(1); + db.signal(2); + + // Wait for thread A to block on this thread + db.wait_for(3); + + // Now try to execute A + db.a(key) +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_one_recovers.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_one_recovers.rs new file mode 100644 index 00000000000..074ed1bd349 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/parallel_cycle_one_recovers.rs @@ -0,0 +1,94 @@ +//! Test for cycle recover spread across two threads. +//! See `../cycles.rs` for a complete listing of cycle tests, +//! both intra and cross thread. + +use crate::setup::{Knobs, ParDatabaseImpl}; +use ra_salsa::ParallelDatabase; + +// Recover cycle test: +// +// The pattern is as follows. +// +// Thread A Thread B +// -------- -------- +// a1 b1 +// | wait for stage 1 (blocks) +// signal stage 1 | +// wait for stage 2 (blocks) (unblocked) +// | signal stage 2 +// (unblocked) wait for stage 3 (blocks) +// a2 | +// b1 (blocks -> stage 3) | +// | (unblocked) +// | b2 +// | a1 (cycle detected) +// a2 recovery fn executes | +// a1 completes normally | +// b2 completes, recovers +// b1 completes, recovers + +#[test] +fn parallel_cycle_one_recovers() { + let db = ParDatabaseImpl::default(); + db.knobs().signal_on_will_block.set(3); + + let thread_a = std::thread::spawn({ + let db = db.snapshot(); + move || db.a1(1) + }); + + let thread_b = std::thread::spawn({ + let db = db.snapshot(); + move || db.b1(1) + }); + + // We expect that the recovery function yields + // `1 * 20 + 2`, which is returned (and forwarded) + // to b1, and from there to a2 and a1. + assert_eq!(thread_a.join().unwrap(), 22); + assert_eq!(thread_b.join().unwrap(), 22); +} + +#[ra_salsa::query_group(ParallelCycleOneRecovers)] +pub(crate) trait TestDatabase: Knobs { + fn a1(&self, key: i32) -> i32; + + #[ra_salsa::cycle(recover)] + fn a2(&self, key: i32) -> i32; + + fn b1(&self, key: i32) -> i32; + + fn b2(&self, key: i32) -> i32; +} + +fn recover(_db: &dyn TestDatabase, _cycle: &ra_salsa::Cycle, key: &i32) -> i32 { + tracing::debug!("recover"); + key * 20 + 2 +} + +fn a1(db: &dyn TestDatabase, key: i32) -> i32 { + // Wait to create the cycle until both threads have entered + db.signal(1); + db.wait_for(2); + + db.a2(key) +} + +fn a2(db: &dyn TestDatabase, key: i32) -> i32 { + db.b1(key) +} + +fn b1(db: &dyn TestDatabase, key: i32) -> i32 { + // Wait to create the cycle until both threads have entered + db.wait_for(1); + db.signal(2); + + // Wait for thread A to block on this thread + db.wait_for(3); + + db.b2(key) +} + +fn b2(db: &dyn TestDatabase, key: i32) -> i32 { + db.a1(key) +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/race.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/race.rs new file mode 100644 index 00000000000..7aa6d4530b4 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/race.rs @@ -0,0 +1,37 @@ +use std::panic::AssertUnwindSafe; + +use crate::setup::{ParDatabase, ParDatabaseImpl}; +use ra_salsa::{Cancelled, ParallelDatabase}; + +/// Test where a read and a set are racing with one another. +/// Should be atomic. +#[test] +fn in_par_get_set_race() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 100); + db.set_input('b', 10); + db.set_input('c', 1); + + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + move || Cancelled::catch(AssertUnwindSafe(|| db.sum("abc"))) + }); + + let thread2 = std::thread::spawn(move || { + db.set_input('a', 1000); + db.sum("a") + }); + + // If the 1st thread runs first, you get 111, otherwise you get + // 1011; if they run concurrently and the 1st thread observes the + // cancellation, it'll unwind. + let result1 = thread1.join().unwrap(); + if let Ok(value1) = result1 { + assert!(value1 == 111 || value1 == 1011, "illegal result {value1}"); + } + + // thread2 can not observe a cancellation because it performs a + // database write before running any other queries. + assert_eq!(thread2.join().unwrap(), 1000); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/setup.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/setup.rs new file mode 100644 index 00000000000..fd1f51326e3 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/setup.rs @@ -0,0 +1,197 @@ +use crate::signal::Signal; +use ra_salsa::Database; +use ra_salsa::ParallelDatabase; +use ra_salsa::Snapshot; +use std::sync::Arc; +use std::{ + cell::Cell, + panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, +}; + +#[ra_salsa::query_group(Par)] +pub(crate) trait ParDatabase: Knobs { + #[ra_salsa::input] + fn input(&self, key: char) -> usize; + + fn sum(&self, key: &'static str) -> usize; + + /// Invokes `sum` + fn sum2(&self, key: &'static str) -> usize; + + /// Invokes `sum` but doesn't really care about the result. + fn sum2_drop_sum(&self, key: &'static str) -> usize; + + /// Invokes `sum2` + fn sum3(&self, key: &'static str) -> usize; + + /// Invokes `sum2_drop_sum` + fn sum3_drop_sum(&self, key: &'static str) -> usize; +} + +/// Various "knobs" and utilities used by tests to force +/// a certain behavior. +pub(crate) trait Knobs { + fn knobs(&self) -> &KnobsStruct; + + fn signal(&self, stage: usize); + + fn wait_for(&self, stage: usize); +} + +pub(crate) trait WithValue { + fn with_value(&self, value: T, closure: impl FnOnce() -> R) -> R; +} + +impl WithValue for Cell { + fn with_value(&self, value: T, closure: impl FnOnce() -> R) -> R { + let old_value = self.replace(value); + + let result = catch_unwind(AssertUnwindSafe(closure)); + + self.set(old_value); + + match result { + Ok(r) => r, + Err(payload) => resume_unwind(payload), + } + } +} + +#[derive(Default, Clone, Copy, PartialEq, Eq)] +pub(crate) enum CancellationFlag { + #[default] + Down, + Panic, +} + +/// Various "knobs" that can be used to customize how the queries +/// behave on one specific thread. Note that this state is +/// intentionally thread-local (apart from `signal`). +#[derive(Clone, Default)] +pub(crate) struct KnobsStruct { + /// A kind of flexible barrier used to coordinate execution across + /// threads to ensure we reach various weird states. + pub(crate) signal: Arc, + + /// When this database is about to block, send a signal. + pub(crate) signal_on_will_block: Cell, + + /// Invocations of `sum` will signal this stage on entry. + pub(crate) sum_signal_on_entry: Cell, + + /// Invocations of `sum` will wait for this stage on entry. + pub(crate) sum_wait_for_on_entry: Cell, + + /// If true, invocations of `sum` will panic before they exit. + pub(crate) sum_should_panic: Cell, + + /// If true, invocations of `sum` will wait for cancellation before + /// they exit. + pub(crate) sum_wait_for_cancellation: Cell, + + /// Invocations of `sum` will wait for this stage prior to exiting. + pub(crate) sum_wait_for_on_exit: Cell, + + /// Invocations of `sum` will signal this stage prior to exiting. + pub(crate) sum_signal_on_exit: Cell, + + /// Invocations of `sum3_drop_sum` will panic unconditionally + pub(crate) sum3_drop_sum_should_panic: Cell, +} + +fn sum(db: &dyn ParDatabase, key: &'static str) -> usize { + let mut sum = 0; + + db.signal(db.knobs().sum_signal_on_entry.get()); + + db.wait_for(db.knobs().sum_wait_for_on_entry.get()); + + if db.knobs().sum_should_panic.get() { + panic!("query set to panic before exit") + } + + for ch in key.chars() { + sum += db.input(ch); + } + + match db.knobs().sum_wait_for_cancellation.get() { + CancellationFlag::Down => (), + CancellationFlag::Panic => { + tracing::debug!("waiting for cancellation"); + loop { + db.unwind_if_cancelled(); + std::thread::yield_now(); + } + } + } + + db.wait_for(db.knobs().sum_wait_for_on_exit.get()); + + db.signal(db.knobs().sum_signal_on_exit.get()); + + sum +} + +fn sum2(db: &dyn ParDatabase, key: &'static str) -> usize { + db.sum(key) +} + +fn sum2_drop_sum(db: &dyn ParDatabase, key: &'static str) -> usize { + let _ = db.sum(key); + 22 +} + +fn sum3(db: &dyn ParDatabase, key: &'static str) -> usize { + db.sum2(key) +} + +fn sum3_drop_sum(db: &dyn ParDatabase, key: &'static str) -> usize { + if db.knobs().sum3_drop_sum_should_panic.get() { + panic!("sum3_drop_sum executed") + } + db.sum2_drop_sum(key) +} + +#[ra_salsa::database( + Par, + crate::parallel_cycle_all_recover::ParallelCycleAllRecover, + crate::parallel_cycle_none_recover::ParallelCycleNoneRecover, + crate::parallel_cycle_mid_recover::ParallelCycleMidRecovers, + crate::parallel_cycle_one_recovers::ParallelCycleOneRecovers +)] +#[derive(Default)] +pub(crate) struct ParDatabaseImpl { + storage: ra_salsa::Storage, + knobs: KnobsStruct, +} + +impl Database for ParDatabaseImpl { + fn salsa_event(&self, event: ra_salsa::Event) { + if let ra_salsa::EventKind::WillBlockOn { .. } = event.kind { + self.signal(self.knobs().signal_on_will_block.get()); + } + } +} + +impl ParallelDatabase for ParDatabaseImpl { + fn snapshot(&self) -> Snapshot { + Snapshot::new(ParDatabaseImpl { + storage: self.storage.snapshot(), + knobs: self.knobs.clone(), + }) + } +} + +impl Knobs for ParDatabaseImpl { + fn knobs(&self) -> &KnobsStruct { + &self.knobs + } + + fn signal(&self, stage: usize) { + self.knobs.signal.signal(stage); + } + + fn wait_for(&self, stage: usize) { + self.knobs.signal.wait_for(stage); + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/signal.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/signal.rs new file mode 100644 index 00000000000..0af7b66e482 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/signal.rs @@ -0,0 +1,40 @@ +use parking_lot::{Condvar, Mutex}; + +#[derive(Default)] +pub(crate) struct Signal { + value: Mutex, + cond_var: Condvar, +} + +impl Signal { + pub(crate) fn signal(&self, stage: usize) { + tracing::debug!("signal({})", stage); + + // This check avoids acquiring the lock for things that will + // clearly be a no-op. Not *necessary* but helps to ensure we + // are more likely to encounter weird race conditions; + // otherwise calls to `sum` will tend to be unnecessarily + // synchronous. + if stage > 0 { + let mut v = self.value.lock(); + if stage > *v { + *v = stage; + self.cond_var.notify_all(); + } + } + } + + /// Waits until the given condition is true; the fn is invoked + /// with the current stage. + pub(crate) fn wait_for(&self, stage: usize) { + tracing::debug!("wait_for({})", stage); + + // As above, avoid lock if clearly a no-op. + if stage > 0 { + let mut v = self.value.lock(); + while *v < stage { + self.cond_var.wait(&mut v); + } + } + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/stress.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/stress.rs new file mode 100644 index 00000000000..f3a435b47f1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/stress.rs @@ -0,0 +1,168 @@ +use rand::seq::SliceRandom; +use rand::Rng; + +use ra_salsa::ParallelDatabase; +use ra_salsa::Snapshot; +use ra_salsa::{Cancelled, Database}; + +// Number of operations a reader performs +const N_MUTATOR_OPS: usize = 100; +const N_READER_OPS: usize = 100; + +#[ra_salsa::query_group(Stress)] +trait StressDatabase: ra_salsa::Database { + #[ra_salsa::input] + fn a(&self, key: usize) -> usize; + + fn b(&self, key: usize) -> usize; + + fn c(&self, key: usize) -> usize; +} + +fn b(db: &dyn StressDatabase, key: usize) -> usize { + db.unwind_if_cancelled(); + db.a(key) +} + +fn c(db: &dyn StressDatabase, key: usize) -> usize { + db.b(key) +} + +#[ra_salsa::database(Stress)] +#[derive(Default)] +struct StressDatabaseImpl { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for StressDatabaseImpl {} + +impl ra_salsa::ParallelDatabase for StressDatabaseImpl { + fn snapshot(&self) -> Snapshot { + Snapshot::new(StressDatabaseImpl { storage: self.storage.snapshot() }) + } +} + +#[derive(Clone, Copy, Debug)] +enum Query { + A, + B, + C, +} + +enum MutatorOp { + WriteOp(WriteOp), + LaunchReader { ops: Vec, check_cancellation: bool }, +} + +#[derive(Debug)] +enum WriteOp { + SetA(usize, usize), +} + +#[derive(Debug)] +enum ReadOp { + Get(Query, usize), +} + +impl rand::distributions::Distribution for rand::distributions::Standard { + fn sample(&self, rng: &mut R) -> Query { + *[Query::A, Query::B, Query::C].choose(rng).unwrap() + } +} + +impl rand::distributions::Distribution for rand::distributions::Standard { + fn sample(&self, rng: &mut R) -> MutatorOp { + if rng.gen_bool(0.5) { + MutatorOp::WriteOp(rng.gen()) + } else { + MutatorOp::LaunchReader { + ops: (0..N_READER_OPS).map(|_| rng.gen()).collect(), + check_cancellation: rng.gen(), + } + } + } +} + +impl rand::distributions::Distribution for rand::distributions::Standard { + fn sample(&self, rng: &mut R) -> WriteOp { + let key = rng.gen::() % 10; + let value = rng.gen::() % 10; + WriteOp::SetA(key, value) + } +} + +impl rand::distributions::Distribution for rand::distributions::Standard { + fn sample(&self, rng: &mut R) -> ReadOp { + let query = rng.gen::(); + let key = rng.gen::() % 10; + ReadOp::Get(query, key) + } +} + +fn db_reader_thread(db: &StressDatabaseImpl, ops: Vec, check_cancellation: bool) { + for op in ops { + if check_cancellation { + db.unwind_if_cancelled(); + } + op.execute(db); + } +} + +impl WriteOp { + fn execute(self, db: &mut StressDatabaseImpl) { + match self { + WriteOp::SetA(key, value) => { + db.set_a(key, value); + } + } + } +} + +impl ReadOp { + fn execute(self, db: &StressDatabaseImpl) { + match self { + ReadOp::Get(query, key) => match query { + Query::A => { + db.a(key); + } + Query::B => { + let _ = db.b(key); + } + Query::C => { + let _ = db.c(key); + } + }, + } + } +} + +#[test] +fn stress_test() { + let mut db = StressDatabaseImpl::default(); + for i in 0..10 { + db.set_a(i, i); + } + + let mut rng = rand::thread_rng(); + + // generate the ops that the mutator thread will perform + let write_ops: Vec = (0..N_MUTATOR_OPS).map(|_| rng.gen()).collect(); + + // execute the "main thread", which sometimes snapshots off other threads + let mut all_threads = vec![]; + for op in write_ops { + match op { + MutatorOp::WriteOp(w) => w.execute(&mut db), + MutatorOp::LaunchReader { ops, check_cancellation } => { + all_threads.push(std::thread::spawn({ + let db = db.snapshot(); + move || Cancelled::catch(|| db_reader_thread(&db, ops, check_cancellation)) + })) + } + } + } + + for thread in all_threads { + thread.join().unwrap().ok(); + } +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/true_parallel.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/true_parallel.rs new file mode 100644 index 00000000000..44db17bd852 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/parallel/true_parallel.rs @@ -0,0 +1,125 @@ +use crate::setup::{Knobs, ParDatabase, ParDatabaseImpl, WithValue}; +use ra_salsa::ParallelDatabase; +use std::panic::{self, AssertUnwindSafe}; + +/// Test where two threads are executing sum. We show that they can +/// both be executing sum in parallel by having thread1 wait for +/// thread2 to send a signal before it leaves (similarly, thread2 +/// waits for thread1 to send a signal before it enters). +#[test] +fn true_parallel_different_keys() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 100); + db.set_input('b', 10); + db.set_input('c', 1); + + // Thread 1 will signal stage 1 when it enters and wait for stage 2. + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + move || { + let v = db + .knobs() + .sum_signal_on_entry + .with_value(1, || db.knobs().sum_wait_for_on_exit.with_value(2, || db.sum("a"))); + v + } + }); + + // Thread 2 will wait_for stage 1 when it enters and signal stage 2 + // when it leaves. + let thread2 = std::thread::spawn({ + let db = db.snapshot(); + move || { + let v = db + .knobs() + .sum_wait_for_on_entry + .with_value(1, || db.knobs().sum_signal_on_exit.with_value(2, || db.sum("b"))); + v + } + }); + + assert_eq!(thread1.join().unwrap(), 100); + assert_eq!(thread2.join().unwrap(), 10); +} + +/// Add a test that tries to trigger a conflict, where we fetch +/// `sum("abc")` from two threads simultaneously, and of them +/// therefore has to block. +#[test] +fn true_parallel_same_keys() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 100); + db.set_input('b', 10); + db.set_input('c', 1); + + // Thread 1 will wait_for a barrier in the start of `sum` + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + move || { + let v = db + .knobs() + .sum_signal_on_entry + .with_value(1, || db.knobs().sum_wait_for_on_entry.with_value(2, || db.sum("abc"))); + v + } + }); + + // Thread 2 will wait until Thread 1 has entered sum and then -- + // once it has set itself to block -- signal Thread 1 to + // continue. This way, we test out the mechanism of one thread + // blocking on another. + let thread2 = std::thread::spawn({ + let db = db.snapshot(); + move || { + db.knobs().signal.wait_for(1); + db.knobs().signal_on_will_block.set(2); + db.sum("abc") + } + }); + + assert_eq!(thread1.join().unwrap(), 111); + assert_eq!(thread2.join().unwrap(), 111); +} + +/// Add a test that tries to trigger a conflict, where we fetch `sum("a")` +/// from two threads simultaneously. After `thread2` begins blocking, +/// we force `thread1` to panic and should see that propagate to `thread2`. +#[test] +fn true_parallel_propagate_panic() { + let mut db = ParDatabaseImpl::default(); + + db.set_input('a', 1); + + // `thread1` will wait_for a barrier in the start of `sum`. Once it can + // continue, it will panic. + let thread1 = std::thread::spawn({ + let db = db.snapshot(); + move || { + let v = db.knobs().sum_signal_on_entry.with_value(1, || { + db.knobs() + .sum_wait_for_on_entry + .with_value(2, || db.knobs().sum_should_panic.with_value(true, || db.sum("a"))) + }); + v + } + }); + + // `thread2` will wait until `thread1` has entered sum and then -- once it + // has set itself to block -- signal `thread1` to continue. + let thread2 = std::thread::spawn({ + let db = db.snapshot(); + move || { + db.knobs().signal.wait_for(1); + db.knobs().signal_on_will_block.set(2); + db.sum("a") + } + }); + + let result1 = panic::catch_unwind(AssertUnwindSafe(|| thread1.join().unwrap())); + let result2 = panic::catch_unwind(AssertUnwindSafe(|| thread2.join().unwrap())); + + assert!(result1.is_err()); + assert!(result2.is_err()); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/implementation.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/implementation.rs new file mode 100644 index 00000000000..39b2befd15b --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/implementation.rs @@ -0,0 +1,19 @@ +use crate::queries; +use std::cell::Cell; + +#[ra_salsa::database(queries::GroupStruct)] +#[derive(Default)] +pub(crate) struct DatabaseImpl { + storage: ra_salsa::Storage, + counter: Cell, +} + +impl queries::Counter for DatabaseImpl { + fn increment(&self) -> usize { + let v = self.counter.get(); + self.counter.set(v + 1); + v + } +} + +impl ra_salsa::Database for DatabaseImpl {} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/main.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/main.rs new file mode 100644 index 00000000000..e92c61740e0 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/main.rs @@ -0,0 +1,5 @@ +mod implementation; +mod queries; +mod tests; + +fn main() {} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/queries.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/queries.rs new file mode 100644 index 00000000000..bc9b10ae7bb --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/queries.rs @@ -0,0 +1,22 @@ +pub(crate) trait Counter: ra_salsa::Database { + fn increment(&self) -> usize; +} + +#[ra_salsa::query_group(GroupStruct)] +pub(crate) trait Database: Counter { + fn memoized(&self) -> usize; + fn volatile(&self) -> usize; +} + +/// Because this query is memoized, we only increment the counter +/// the first time it is invoked. +fn memoized(db: &dyn Database) -> usize { + db.volatile() +} + +/// Because this query is volatile, each time it is invoked, +/// we will increment the counter. +fn volatile(db: &dyn Database) -> usize { + db.salsa_runtime().report_untracked_read(); + db.increment() +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/tests.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/tests.rs new file mode 100644 index 00000000000..7c33bbfc747 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/storage_varieties/tests.rs @@ -0,0 +1,49 @@ +#![cfg(test)] + +use crate::implementation::DatabaseImpl; +use crate::queries::Database; +use ra_salsa::Database as _Database; +use ra_salsa::Durability; + +#[test] +fn memoized_twice() { + let db = DatabaseImpl::default(); + let v1 = db.memoized(); + let v2 = db.memoized(); + assert_eq!(v1, v2); +} + +#[test] +fn volatile_twice() { + let mut db = DatabaseImpl::default(); + let v1 = db.volatile(); + let v2 = db.volatile(); // volatiles are cached, so 2nd read returns the same + assert_eq!(v1, v2); + + db.synthetic_write(Durability::LOW); // clears volatile caches + + let v3 = db.volatile(); // will re-increment the counter + let v4 = db.volatile(); // second call will be cached + assert_eq!(v1 + 1, v3); + assert_eq!(v3, v4); +} + +#[test] +fn intermingled() { + let mut db = DatabaseImpl::default(); + let v1 = db.volatile(); + let v2 = db.memoized(); + let v3 = db.volatile(); // cached + let v4 = db.memoized(); // cached + + assert_eq!(v1, v2); + assert_eq!(v1, v3); + assert_eq!(v2, v4); + + db.synthetic_write(Durability::LOW); // clears volatile caches + + let v5 = db.memoized(); // re-executes volatile, caches new result + let v6 = db.memoized(); // re-use cached result + assert_eq!(v4 + 1, v5); + assert_eq!(v5, v6); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/transparent.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/transparent.rs new file mode 100644 index 00000000000..886f4641065 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/transparent.rs @@ -0,0 +1,39 @@ +//! Test that transparent (uncached) queries work + +#[ra_salsa::query_group(QueryGroupStorage)] +trait QueryGroup { + #[ra_salsa::input] + fn input(&self, x: u32) -> u32; + #[ra_salsa::transparent] + fn wrap(&self, x: u32) -> u32; + fn get(&self, x: u32) -> u32; +} + +fn wrap(db: &dyn QueryGroup, x: u32) -> u32 { + db.input(x) +} + +fn get(db: &dyn QueryGroup, x: u32) -> u32 { + db.wrap(x) +} + +#[ra_salsa::database(QueryGroupStorage)] +#[derive(Default)] +struct Database { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for Database {} + +#[test] +fn transparent_queries_work() { + let mut db = Database::default(); + + db.set_input(1, 10); + assert_eq!(db.get(1), 10); + assert_eq!(db.get(1), 10); + + db.set_input(1, 92); + assert_eq!(db.get(1), 92); + assert_eq!(db.get(1), 92); +} diff --git a/src/tools/rust-analyzer/crates/ra-salsa/tests/variadic.rs b/src/tools/rust-analyzer/crates/ra-salsa/tests/variadic.rs new file mode 100644 index 00000000000..11a6d13ebe2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ra-salsa/tests/variadic.rs @@ -0,0 +1,51 @@ +#[ra_salsa::query_group(HelloWorld)] +trait HelloWorldDatabase: ra_salsa::Database { + #[ra_salsa::input] + fn input(&self, a: u32, b: u32) -> u32; + + fn none(&self) -> u32; + + fn one(&self, k: u32) -> u32; + + fn two(&self, a: u32, b: u32) -> u32; + + fn trailing(&self, a: u32, b: u32) -> u32; +} + +fn none(_db: &dyn HelloWorldDatabase) -> u32 { + 22 +} + +fn one(_db: &dyn HelloWorldDatabase, k: u32) -> u32 { + k * 2 +} + +fn two(_db: &dyn HelloWorldDatabase, a: u32, b: u32) -> u32 { + a * b +} + +fn trailing(_db: &dyn HelloWorldDatabase, a: u32, b: u32) -> u32 { + a - b +} + +#[ra_salsa::database(HelloWorld)] +#[derive(Default)] +struct DatabaseStruct { + storage: ra_salsa::Storage, +} + +impl ra_salsa::Database for DatabaseStruct {} + +#[test] +fn execute() { + let mut db = DatabaseStruct::default(); + + // test what happens with inputs: + db.set_input(1, 2, 3); + assert_eq!(db.input(1, 2), 3); + + assert_eq!(db.none(), 22); + assert_eq!(db.one(11), 22); + assert_eq!(db.two(11, 2), 22); + assert_eq!(db.trailing(24, 2), 22); +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 4844c514ae9..c2164614274 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -22,7 +22,7 @@ use ide::{ }; use ide_db::{ base_db::{ - salsa::{self, debug::DebugQueryTable, ParallelDatabase}, + ra_salsa::{self, debug::DebugQueryTable, ParallelDatabase}, SourceDatabase, SourceRootDatabase, }, EditionedFileId, LineIndexDatabase, SnippetCap, @@ -46,8 +46,8 @@ use crate::cli::{ /// Need to wrap Snapshot to provide `Clone` impl for `map_with` struct Snap(DB); -impl Clone for Snap> { - fn clone(&self) -> Snap> { +impl Clone for Snap> { + fn clone(&self) -> Snap> { Snap(self.0.snapshot()) } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 60ee0295a3a..23d988ddff3 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -18,7 +18,7 @@ use std::{iter, mem}; use hir::{db::DefDatabase, ChangeWithProcMacros, ProcMacros, ProcMacrosBuilder}; use ide::CrateId; use ide_db::{ - base_db::{salsa::Durability, CrateGraph, CrateWorkspaceData, ProcMacroPaths}, + base_db::{ra_salsa::Durability, CrateGraph, CrateWorkspaceData, ProcMacroPaths}, FxHashMap, }; use itertools::Itertools; diff --git a/src/tools/rust-analyzer/crates/salsa/Cargo.toml b/src/tools/rust-analyzer/crates/salsa/Cargo.toml deleted file mode 100644 index 0d3e1197b5c..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "salsa" -version = "0.0.0" -authors = ["Salsa developers"] -edition = "2021" -license = "Apache-2.0 OR MIT" -repository = "https://github.com/salsa-rs/salsa" -description = "A generic framework for on-demand, incrementalized computation (experimental)" - -rust-version.workspace = true - -[lib] -name = "salsa" - -[dependencies] -indexmap = "2.1.0" -lock_api = "0.4" -tracing = "0.1" -parking_lot = "0.12.1" -rustc-hash = "1.0" -smallvec = "1.0.0" -oorandom = "11" -triomphe = "0.1.11" -itertools.workspace = true - -salsa-macros = { version = "0.0.0", path = "salsa-macros" } - -[dev-dependencies] -linked-hash-map = "0.5.6" -rand = "0.8.5" -expect-test = "1.4.0" -dissimilar = "1.0.7" - -[lints] -workspace = true diff --git a/src/tools/rust-analyzer/crates/salsa/FAQ.md b/src/tools/rust-analyzer/crates/salsa/FAQ.md deleted file mode 100644 index 9c9f6f92da9..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/FAQ.md +++ /dev/null @@ -1,34 +0,0 @@ -# Frequently asked questions - -## Why is it called salsa? - -I like salsa! Don't you?! Well, ok, there's a bit more to it. The -underlying algorithm for figuring out which bits of code need to be -re-executed after any given change is based on the algorithm used in -rustc. Michael Woerister and I first described the rustc algorithm in -terms of two colors, red and green, and hence we called it the -"red-green algorithm". This made me think of the New Mexico State -Question --- ["Red or green?"][nm] --- which refers to chile -(salsa). Although this version no longer uses colors (we borrowed -revision counters from Glimmer, instead), I still like the name. - -[nm]: https://www.sos.state.nm.us/about-new-mexico/state-question/ - -## What is the relationship between salsa and an Entity-Component System (ECS)? - -You may have noticed that Salsa "feels" a lot like an ECS in some -ways. That's true -- Salsa's queries are a bit like *components* (and -the keys to the queries are a bit like *entities*). But there is one -big difference: **ECS is -- at its heart -- a mutable system**. You -can get or set a component of some entity whenever you like. In -contrast, salsa's queries **define "derived values" via pure -computations**. - -Partly as a consequence, ECS doesn't handle incremental updates for -you. When you update some component of some entity, you have to ensure -that other entities' components are updated appropriately. - -Finally, ECS offers interesting metadata and "aspect-like" facilities, -such as iterating over all entities that share certain components. -Salsa has no analogue to that. - diff --git a/src/tools/rust-analyzer/crates/salsa/LICENSE-APACHE b/src/tools/rust-analyzer/crates/salsa/LICENSE-APACHE deleted file mode 100644 index 1b5ec8b78e2..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/LICENSE-APACHE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS diff --git a/src/tools/rust-analyzer/crates/salsa/LICENSE-MIT b/src/tools/rust-analyzer/crates/salsa/LICENSE-MIT deleted file mode 100644 index 31aa79387f2..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/src/tools/rust-analyzer/crates/salsa/README.md b/src/tools/rust-analyzer/crates/salsa/README.md deleted file mode 100644 index 4a8d9f8c731..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# salsa - -*A generic framework for on-demand, incrementalized computation.* - -## Obligatory warning - -This is a fork of https://github.com/salsa-rs/salsa/ adjusted to rust-analyzer's needs. - -## Credits - -This system is heavily inspired by [adapton](http://adapton.org/), [glimmer](https://github.com/glimmerjs/glimmer-vm), and rustc's query -system. So credit goes to Eduard-Mihai Burtescu, Matthew Hammer, -Yehuda Katz, and Michael Woerister. - -## Key idea - -The key idea of `salsa` is that you define your program as a set of -**queries**. Every query is used like function `K -> V` that maps from -some key of type `K` to a value of type `V`. Queries come in two basic -varieties: - -- **Inputs**: the base inputs to your system. You can change these - whenever you like. -- **Functions**: pure functions (no side effects) that transform your - inputs into other values. The results of queries is memoized to - avoid recomputing them a lot. When you make changes to the inputs, - we'll figure out (fairly intelligently) when we can re-use these - memoized values and when we have to recompute them. - -## Want to learn more? - -To learn more about Salsa, try one of the following: - -- read the [heavily commented `hello_world` example](https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs); -- check out the [Salsa book](https://salsa-rs.github.io/salsa); -- watch one of our [videos](https://salsa-rs.github.io/salsa/videos.html). - -## Getting in touch - -The bulk of the discussion happens in the [issues](https://github.com/salsa-rs/salsa/issues) -and [pull requests](https://github.com/salsa-rs/salsa/pulls), -but we have a [zulip chat](https://salsa.zulipchat.com/) as well. diff --git a/src/tools/rust-analyzer/crates/salsa/salsa-macros/Cargo.toml b/src/tools/rust-analyzer/crates/salsa/salsa-macros/Cargo.toml deleted file mode 100644 index 791d2f6e9f5..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/salsa-macros/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "salsa-macros" -version = "0.0.0" -authors = ["Salsa developers"] -edition = "2021" -license = "Apache-2.0 OR MIT" -repository = "https://github.com/salsa-rs/salsa" -description = "Procedural macros for the salsa crate" - -rust-version.workspace = true - -[lib] -proc-macro = true -name = "salsa_macros" - -[dependencies] -heck = "0.4" -proc-macro2 = "1.0" -quote = "1.0" -syn = { version = "2.0", features = ["full", "extra-traits"] } - -[lints] -workspace = true diff --git a/src/tools/rust-analyzer/crates/salsa/salsa-macros/LICENSE-APACHE b/src/tools/rust-analyzer/crates/salsa/salsa-macros/LICENSE-APACHE deleted file mode 100644 index 0bf2cad6488..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/salsa-macros/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE diff --git a/src/tools/rust-analyzer/crates/salsa/salsa-macros/LICENSE-MIT b/src/tools/rust-analyzer/crates/salsa/salsa-macros/LICENSE-MIT deleted file mode 100644 index d99cce5f720..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/salsa-macros/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT diff --git a/src/tools/rust-analyzer/crates/salsa/salsa-macros/README.md b/src/tools/rust-analyzer/crates/salsa/salsa-macros/README.md deleted file mode 100644 index 94389aee61a..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/salsa-macros/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md diff --git a/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/database_storage.rs b/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/database_storage.rs deleted file mode 100644 index f16d814b9f0..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/database_storage.rs +++ /dev/null @@ -1,243 +0,0 @@ -//! Implementation for `[salsa::database]` decorator. - -use heck::ToSnakeCase; -use proc_macro::TokenStream; -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::{Ident, ItemStruct, Path, Token}; - -type PunctuatedQueryGroups = Punctuated; - -pub(crate) fn database(args: TokenStream, input: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as QueryGroupList); - let input = syn::parse_macro_input!(input as ItemStruct); - - let query_groups = &args.query_groups; - let database_name = &input.ident; - let visibility = &input.vis; - let db_storage_field = quote! { storage }; - - let mut output = proc_macro2::TokenStream::new(); - output.extend(quote! { #input }); - - let query_group_names_snake: Vec<_> = query_groups - .iter() - .map(|query_group| { - let group_name = query_group.name(); - Ident::new(&group_name.to_string().to_snake_case(), group_name.span()) - }) - .collect(); - - let query_group_storage_names: Vec<_> = query_groups - .iter() - .map(|QueryGroup { group_path }| { - quote! { - <#group_path as salsa::plumbing::QueryGroup>::GroupStorage - } - }) - .collect(); - - // For each query group `foo::MyGroup` create a link to its - // `foo::MyGroupGroupStorage` - let mut storage_fields = proc_macro2::TokenStream::new(); - let mut storage_initializers = proc_macro2::TokenStream::new(); - let mut has_group_impls = proc_macro2::TokenStream::new(); - for (((query_group, group_name_snake), group_storage), group_index) in query_groups - .iter() - .zip(&query_group_names_snake) - .zip(&query_group_storage_names) - .zip(0_u16..) - { - let group_path = &query_group.group_path; - - // rewrite the last identifier (`MyGroup`, above) to - // (e.g.) `MyGroupGroupStorage`. - storage_fields.extend(quote! { - #group_name_snake: #group_storage, - }); - - // rewrite the last identifier (`MyGroup`, above) to - // (e.g.) `MyGroupGroupStorage`. - storage_initializers.extend(quote! { - #group_name_snake: #group_storage::new(#group_index), - }); - - // ANCHOR:HasQueryGroup - has_group_impls.extend(quote! { - impl salsa::plumbing::HasQueryGroup<#group_path> for #database_name { - fn group_storage(&self) -> &#group_storage { - &self.#db_storage_field.query_store().#group_name_snake - } - - fn group_storage_mut(&mut self) -> (&#group_storage, &mut salsa::Runtime) { - let (query_store_mut, runtime) = self.#db_storage_field.query_store_mut(); - (&query_store_mut.#group_name_snake, runtime) - } - } - }); - // ANCHOR_END:HasQueryGroup - } - - // create group storage wrapper struct - output.extend(quote! { - #[doc(hidden)] - #visibility struct __SalsaDatabaseStorage { - #storage_fields - } - - impl Default for __SalsaDatabaseStorage { - fn default() -> Self { - Self { - #storage_initializers - } - } - } - }); - - // Create a tuple (D1, D2, ...) where Di is the data for a given query group. - let mut database_data = vec![]; - for QueryGroup { group_path } in query_groups { - database_data.push(quote! { - <#group_path as salsa::plumbing::QueryGroup>::GroupData - }); - } - - // ANCHOR:DatabaseStorageTypes - output.extend(quote! { - impl salsa::plumbing::DatabaseStorageTypes for #database_name { - type DatabaseStorage = __SalsaDatabaseStorage; - } - }); - // ANCHOR_END:DatabaseStorageTypes - - // ANCHOR:DatabaseOps - let mut fmt_ops = proc_macro2::TokenStream::new(); - let mut maybe_changed_ops = proc_macro2::TokenStream::new(); - let mut cycle_recovery_strategy_ops = proc_macro2::TokenStream::new(); - let mut for_each_ops = proc_macro2::TokenStream::new(); - for ((QueryGroup { group_path }, group_storage), group_index) in - query_groups.iter().zip(&query_group_storage_names).zip(0_u16..) - { - fmt_ops.extend(quote! { - #group_index => { - let storage: &#group_storage = - >::group_storage(self); - storage.fmt_index(self, input, fmt) - } - }); - maybe_changed_ops.extend(quote! { - #group_index => { - let storage: &#group_storage = - >::group_storage(self); - storage.maybe_changed_after(self, input, revision) - } - }); - cycle_recovery_strategy_ops.extend(quote! { - #group_index => { - let storage: &#group_storage = - >::group_storage(self); - storage.cycle_recovery_strategy(self, input) - } - }); - for_each_ops.extend(quote! { - let storage: &#group_storage = - >::group_storage(self); - storage.for_each_query(runtime, &mut op); - }); - } - output.extend(quote! { - impl salsa::plumbing::DatabaseOps for #database_name { - fn ops_database(&self) -> &dyn salsa::Database { - self - } - - fn ops_salsa_runtime(&self) -> &salsa::Runtime { - self.#db_storage_field.salsa_runtime() - } - - fn synthetic_write(&mut self, durability: salsa::Durability) { - self.#db_storage_field.salsa_runtime_mut().synthetic_write(durability) - } - - fn fmt_index( - &self, - input: salsa::DatabaseKeyIndex, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - match input.group_index() { - #fmt_ops - i => panic!("salsa: invalid group index {}", i) - } - } - - fn maybe_changed_after( - &self, - input: salsa::DatabaseKeyIndex, - revision: salsa::Revision - ) -> bool { - match input.group_index() { - #maybe_changed_ops - i => panic!("salsa: invalid group index {}", i) - } - } - - fn cycle_recovery_strategy( - &self, - input: salsa::DatabaseKeyIndex, - ) -> salsa::plumbing::CycleRecoveryStrategy { - match input.group_index() { - #cycle_recovery_strategy_ops - i => panic!("salsa: invalid group index {}", i) - } - } - - fn for_each_query( - &self, - mut op: &mut dyn FnMut(&dyn salsa::plumbing::QueryStorageMassOps), - ) { - let runtime = salsa::Database::salsa_runtime(self); - #for_each_ops - } - } - }); - // ANCHOR_END:DatabaseOps - - output.extend(has_group_impls); - - output.into() -} - -#[derive(Clone, Debug)] -struct QueryGroupList { - query_groups: PunctuatedQueryGroups, -} - -impl Parse for QueryGroupList { - fn parse(input: ParseStream<'_>) -> syn::Result { - let query_groups: PunctuatedQueryGroups = - input.parse_terminated(QueryGroup::parse, Token![,])?; - Ok(QueryGroupList { query_groups }) - } -} - -#[derive(Clone, Debug)] -struct QueryGroup { - group_path: Path, -} - -impl QueryGroup { - /// The name of the query group trait. - fn name(&self) -> Ident { - self.group_path.segments.last().unwrap().ident.clone() - } -} - -impl Parse for QueryGroup { - /// ```ignore - /// impl HelloWorldDatabase; - /// ``` - fn parse(input: ParseStream<'_>) -> syn::Result { - let group_path: Path = input.parse()?; - Ok(QueryGroup { group_path }) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/lib.rs b/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/lib.rs deleted file mode 100644 index d3e17c5ebf1..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/lib.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! This crate provides salsa's macros and attributes. - -#![recursion_limit = "256"] - -#[macro_use] -extern crate quote; - -use proc_macro::TokenStream; - -mod database_storage; -mod parenthesized; -mod query_group; - -/// The decorator that defines a salsa "query group" trait. This is a -/// trait that defines everything that a block of queries need to -/// execute, as well as defining the queries themselves that are -/// exported for others to use. -/// -/// This macro declares the "prototype" for a group of queries. It will -/// expand into a trait and a set of structs, one per query. -/// -/// For each query, you give the name of the accessor method to invoke -/// the query (e.g., `my_query`, below), as well as its parameter -/// types and the output type. You also give the name for a query type -/// (e.g., `MyQuery`, below) that represents the query, and optionally -/// other details, such as its storage. -/// -/// # Examples -/// -/// The simplest example is something like this: -/// -/// ```ignore -/// #[salsa::query_group] -/// trait TypeckDatabase { -/// #[salsa::input] // see below for other legal attributes -/// fn my_query(&self, input: u32) -> u64; -/// -/// /// Queries can have any number of inputs (including zero); if there -/// /// is not exactly one input, then the key type will be -/// /// a tuple of the input types, so in this case `(u32, f32)`. -/// fn other_query(&self, input1: u32, input2: f32) -> u64; -/// } -/// ``` -/// -/// Here is a list of legal `salsa::XXX` attributes: -/// -/// - Storage attributes: control how the query data is stored and set. These -/// are described in detail in the section below. -/// - `#[salsa::input]` -/// - `#[salsa::memoized]` -/// - `#[salsa::dependencies]` -/// - Query execution: -/// - `#[salsa::invoke(path::to::my_fn)]` -- for a non-input, this -/// indicates the function to call when a query must be -/// recomputed. The default is to call a function in the same -/// module with the same name as the query. -/// - `#[query_type(MyQueryTypeName)]` specifies the name of the -/// dummy struct created for the query. Default is the name of the -/// query, in camel case, plus the word "Query" (e.g., -/// `MyQueryQuery` and `OtherQueryQuery` in the examples above). -/// -/// # Storage attributes -/// -/// Here are the possible storage values for each query. The default -/// is `storage memoized`. -/// -/// ## Input queries -/// -/// Specifying `storage input` will give you an **input -/// query**. Unlike derived queries, whose value is given by a -/// function, input queries are explicitly set by doing -/// `db.query(QueryType).set(key, value)` (where `QueryType` is the -/// `type` specified for the query). Accessing a value that has not -/// yet been set will panic. Each time you invoke `set`, we assume the -/// value has changed, and so we will potentially re-execute derived -/// queries that read (transitively) from this input. -/// -/// ## Derived queries -/// -/// Derived queries are specified by a function. -/// -/// - `#[salsa::memoized]` (the default) -- The result is memoized -/// between calls. If the inputs have changed, we will recompute -/// the value, but then compare against the old memoized value, -/// which can significantly reduce the amount of recomputation -/// required in new revisions. This does require that the value -/// implements `Eq`. -/// - `#[salsa::dependencies]` -- does not cache the value, so it will -/// be recomputed every time it is needed. We do track the inputs, however, -/// so if they have not changed, then things that rely on this query -/// may be known not to have changed. -/// -/// ## Attribute combinations -/// -/// Some attributes are mutually exclusive. For example, it is an error to add -/// multiple storage specifiers or to annotate a function to `invoke` on an -/// `input` query. -#[proc_macro_attribute] -pub fn query_group(args: TokenStream, input: TokenStream) -> TokenStream { - query_group::query_group(args, input) -} - -/// This attribute is placed on your database struct. It takes a list of the -/// query groups that your database supports. The format looks like so: -/// -/// ```rust,ignore -/// #[salsa::database(MyQueryGroup1, MyQueryGroup2)] -/// struct MyDatabase { -/// runtime: salsa::Runtime, // <-- your database will need this field, too -/// } -/// ``` -/// -/// Here, the struct `MyDatabase` would support the two query groups -/// `MyQueryGroup1` and `MyQueryGroup2`. In addition to the `database` -/// attribute, the struct needs to have a `runtime` field (of type -/// [`salsa::Runtime`]) and to implement the `salsa::Database` trait. -/// -/// See [the `hello_world` example][hw] for more details. -/// -/// [`salsa::Runtime`]: struct.Runtime.html -/// [hw]: https://github.com/salsa-rs/salsa/tree/master/examples/hello_world -#[proc_macro_attribute] -pub fn database(args: TokenStream, input: TokenStream) -> TokenStream { - database_storage::database(args, input) -} diff --git a/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/parenthesized.rs b/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/parenthesized.rs deleted file mode 100644 index 5ecd1b8a058..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/parenthesized.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Parenthesis helper -pub(crate) struct Parenthesized(pub(crate) T); - -impl syn::parse::Parse for Parenthesized -where - T: syn::parse::Parse, -{ - fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { - let content; - syn::parenthesized!(content in input); - content.parse::().map(Parenthesized) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/query_group.rs b/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/query_group.rs deleted file mode 100644 index eeaf008a15c..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/salsa-macros/src/query_group.rs +++ /dev/null @@ -1,753 +0,0 @@ -//! Implementation for `[salsa::query_group]` decorator. - -use crate::parenthesized::Parenthesized; -use heck::ToUpperCamelCase; -use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::ToTokens; -use syn::{ - parse_macro_input, parse_quote, spanned::Spanned, Attribute, Error, FnArg, Ident, ItemTrait, - ReturnType, TraitItem, Type, -}; - -/// Implementation for `[salsa::query_group]` decorator. -pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream { - let group_struct = parse_macro_input!(args as Ident); - let input: ItemTrait = parse_macro_input!(input as ItemTrait); - // println!("args: {:#?}", args); - // println!("input: {:#?}", input); - - let input_span = input.span(); - let (trait_attrs, salsa_attrs) = filter_attrs(input.attrs); - if !salsa_attrs.is_empty() { - return Error::new(input_span, format!("unsupported attributes: {salsa_attrs:?}")) - .to_compile_error() - .into(); - } - - let trait_vis = input.vis; - let trait_name = input.ident; - let _generics = input.generics.clone(); - let dyn_db = quote! { dyn #trait_name }; - - // Decompose the trait into the corresponding queries. - let mut queries = vec![]; - for item in input.items { - if let TraitItem::Fn(method) = item { - let query_name = method.sig.ident.to_string(); - - let mut storage = QueryStorage::Memoized; - let mut cycle = None; - let mut invoke = None; - - let mut query_type = - format_ident!("{}Query", query_name.to_string().to_upper_camel_case()); - let mut num_storages = 0; - - // Extract attributes. - let (attrs, salsa_attrs) = filter_attrs(method.attrs); - for SalsaAttr { name, tts, span } in salsa_attrs { - match name.as_str() { - "memoized" => { - storage = QueryStorage::Memoized; - num_storages += 1; - } - "dependencies" => { - storage = QueryStorage::LruDependencies; - num_storages += 1; - } - "lru" => { - storage = QueryStorage::LruMemoized; - num_storages += 1; - } - "input" => { - storage = QueryStorage::Input; - num_storages += 1; - } - "interned" => { - storage = QueryStorage::Interned; - num_storages += 1; - } - "cycle" => { - cycle = Some(parse_macro_input!(tts as Parenthesized).0); - } - "invoke" => { - invoke = Some(parse_macro_input!(tts as Parenthesized).0); - } - "query_type" => { - query_type = parse_macro_input!(tts as Parenthesized).0; - } - "transparent" => { - storage = QueryStorage::Transparent; - num_storages += 1; - } - _ => { - return Error::new(span, format!("unknown salsa attribute `{name}`")) - .to_compile_error() - .into(); - } - } - } - - let sig_span = method.sig.span(); - // Check attribute combinations. - if num_storages > 1 { - return Error::new(sig_span, "multiple storage attributes specified") - .to_compile_error() - .into(); - } - match &invoke { - Some(invoke) if storage == QueryStorage::Input => { - return Error::new( - invoke.span(), - "#[salsa::invoke] cannot be set on #[salsa::input] queries", - ) - .to_compile_error() - .into(); - } - _ => {} - } - - // Extract keys. - let mut iter = method.sig.inputs.iter(); - let self_receiver = match iter.next() { - Some(FnArg::Receiver(sr)) if sr.mutability.is_none() => sr, - _ => { - return Error::new( - sig_span, - format!("first argument of query `{query_name}` must be `&self`"), - ) - .to_compile_error() - .into(); - } - }; - let mut keys: Vec<(Ident, Type)> = vec![]; - for (idx, arg) in iter.enumerate() { - match arg { - FnArg::Typed(syn::PatType { pat, ty, .. }) => keys.push(( - match pat.as_ref() { - syn::Pat::Ident(ident_pat) => ident_pat.ident.clone(), - _ => format_ident!("key{}", idx), - }, - Type::clone(ty), - )), - arg => { - return Error::new( - arg.span(), - format!("unsupported argument `{arg:?}` of `{query_name}`",), - ) - .to_compile_error() - .into(); - } - } - } - - // Extract value. - let value = match method.sig.output { - ReturnType::Type(_, ref ty) => ty.as_ref().clone(), - ref ret => { - return Error::new( - ret.span(), - format!("unsupported return type `{ret:?}` of `{query_name}`"), - ) - .to_compile_error() - .into(); - } - }; - - // For `#[salsa::interned]` keys, we create a "lookup key" automatically. - // - // For a query like: - // - // fn foo(&self, x: Key1, y: Key2) -> u32 - // - // we would create - // - // fn lookup_foo(&self, x: u32) -> (Key1, Key2) - let lookup_query = if let QueryStorage::Interned = storage { - let lookup_query_type = - format_ident!("{}LookupQuery", query_name.to_string().to_upper_camel_case()); - let lookup_fn_name = format_ident!("lookup_{}", query_name); - let keys = keys.iter().map(|(_, ty)| ty); - let lookup_value: Type = parse_quote!((#(#keys),*)); - let lookup_keys = vec![(parse_quote! { key }, value.clone())]; - Some(Query { - query_type: lookup_query_type, - query_name: format!("{lookup_fn_name}"), - fn_name: lookup_fn_name, - receiver: self_receiver.clone(), - attrs: vec![], // FIXME -- some automatically generated docs on this method? - storage: QueryStorage::InternedLookup { intern_query_type: query_type.clone() }, - keys: lookup_keys, - value: lookup_value, - invoke: None, - cycle: cycle.clone(), - }) - } else { - None - }; - - queries.push(Query { - query_type, - query_name, - fn_name: method.sig.ident, - receiver: self_receiver.clone(), - attrs, - storage, - keys, - value, - invoke, - cycle, - }); - - queries.extend(lookup_query); - } - } - - let group_storage = format_ident!("{}GroupStorage__", trait_name, span = Span::call_site()); - - let mut query_fn_declarations = proc_macro2::TokenStream::new(); - let mut query_fn_definitions = proc_macro2::TokenStream::new(); - let mut storage_fields = proc_macro2::TokenStream::new(); - let mut queries_with_storage = vec![]; - for query in &queries { - #[allow(clippy::map_identity)] - // clippy is incorrect here, this is not the identity function due to match ergonomics - let (key_names, keys): (Vec<_>, Vec<_>) = query.keys.iter().map(|(a, b)| (a, b)).unzip(); - let value = &query.value; - let fn_name = &query.fn_name; - let qt = &query.query_type; - let attrs = &query.attrs; - let self_receiver = &query.receiver; - - query_fn_declarations.extend(quote! { - #(#attrs)* - fn #fn_name(#self_receiver, #(#key_names: #keys),*) -> #value; - }); - - // Special case: transparent queries don't create actual storage, - // just inline the definition - if let QueryStorage::Transparent = query.storage { - let invoke = query.invoke_tt(); - query_fn_definitions.extend(quote! { - fn #fn_name(&self, #(#key_names: #keys),*) -> #value { - #invoke(self, #(#key_names),*) - } - }); - continue; - } - - queries_with_storage.push(fn_name); - - let tracing = if let QueryStorage::Memoized | QueryStorage::LruMemoized = query.storage { - let s = format!("{trait_name}::{fn_name}"); - Some(quote! { - let _p = tracing::debug_span!(#s, #(#key_names = tracing::field::debug(&#key_names)),*).entered(); - }) - } else { - None - } - .into_iter(); - - query_fn_definitions.extend(quote! { - fn #fn_name(&self, #(#key_names: #keys),*) -> #value { - #(#tracing),* - // Create a shim to force the code to be monomorphized in the - // query crate. Our experiments revealed that this makes a big - // difference in total compilation time in rust-analyzer, though - // it's not totally obvious why that should be. - fn __shim(db: &(dyn #trait_name + '_), #(#key_names: #keys),*) -> #value { - salsa::plumbing::get_query_table::<#qt>(db).get((#(#key_names),*)) - } - __shim(self, #(#key_names),*) - - } - }); - - // For input queries, we need `set_foo` etc - if let QueryStorage::Input = query.storage { - let set_fn_name = format_ident!("set_{}", fn_name); - let set_with_durability_fn_name = format_ident!("set_{}_with_durability", fn_name); - - let set_fn_docs = format!( - " - Set the value of the `{fn_name}` input. - - See `{fn_name}` for details. - - *Note:* Setting values will trigger cancellation - of any ongoing queries; this method blocks until - those queries have been cancelled. - " - ); - - let set_constant_fn_docs = format!( - " - Set the value of the `{fn_name}` input with a - specific durability instead of the default of - `Durability::LOW`. You can use `Durability::MAX` - to promise that its value will never change again. - - See `{fn_name}` for details. - - *Note:* Setting values will trigger cancellation - of any ongoing queries; this method blocks until - those queries have been cancelled. - " - ); - - query_fn_declarations.extend(quote! { - # [doc = #set_fn_docs] - fn #set_fn_name(&mut self, #(#key_names: #keys,)* value__: #value); - - - # [doc = #set_constant_fn_docs] - fn #set_with_durability_fn_name(&mut self, #(#key_names: #keys,)* value__: #value, durability__: salsa::Durability); - }); - - query_fn_definitions.extend(quote! { - fn #set_fn_name(&mut self, #(#key_names: #keys,)* value__: #value) { - fn __shim(db: &mut dyn #trait_name, #(#key_names: #keys,)* value__: #value) { - salsa::plumbing::get_query_table_mut::<#qt>(db).set((#(#key_names),*), value__) - } - __shim(self, #(#key_names,)* value__) - } - - fn #set_with_durability_fn_name(&mut self, #(#key_names: #keys,)* value__: #value, durability__: salsa::Durability) { - fn __shim(db: &mut dyn #trait_name, #(#key_names: #keys,)* value__: #value, durability__: salsa::Durability) { - salsa::plumbing::get_query_table_mut::<#qt>(db).set_with_durability((#(#key_names),*), value__, durability__) - } - __shim(self, #(#key_names,)* value__ ,durability__) - } - }); - } - - // A field for the storage struct - storage_fields.extend(quote! { - #fn_name: std::sync::Arc<<#qt as salsa::Query>::Storage>, - }); - } - - // Emit the trait itself. - let mut output = { - let bounds = &input.supertraits; - quote! { - #(#trait_attrs)* - #trait_vis trait #trait_name : - salsa::Database + - salsa::plumbing::HasQueryGroup<#group_struct> + - #bounds - { - #query_fn_declarations - } - } - }; - - // Emit the query group struct and impl of `QueryGroup`. - output.extend(quote! { - /// Representative struct for the query group. - #trait_vis struct #group_struct { } - - impl salsa::plumbing::QueryGroup for #group_struct - { - type DynDb = #dyn_db; - type GroupStorage = #group_storage; - } - }); - - // Emit an impl of the trait - output.extend({ - let bounds = input.supertraits; - quote! { - impl #trait_name for DB - where - DB: #bounds, - DB: salsa::Database, - DB: salsa::plumbing::HasQueryGroup<#group_struct>, - { - #query_fn_definitions - } - } - }); - - let non_transparent_queries = - || queries.iter().filter(|q| !matches!(q.storage, QueryStorage::Transparent)); - - // Emit the query types. - for (query, query_index) in non_transparent_queries().zip(0_u16..) { - let fn_name = &query.fn_name; - let qt = &query.query_type; - - let storage = match &query.storage { - QueryStorage::Memoized => quote!(salsa::plumbing::MemoizedStorage), - QueryStorage::LruMemoized => quote!(salsa::plumbing::LruMemoizedStorage), - QueryStorage::LruDependencies => { - quote!(salsa::plumbing::LruDependencyStorage) - } - QueryStorage::Input if query.keys.is_empty() => { - quote!(salsa::plumbing::UnitInputStorage) - } - QueryStorage::Input => quote!(salsa::plumbing::InputStorage), - QueryStorage::Interned => quote!(salsa::plumbing::InternedStorage), - QueryStorage::InternedLookup { intern_query_type } => { - quote!(salsa::plumbing::LookupInternedStorage) - } - QueryStorage::Transparent => panic!("should have been filtered"), - }; - let keys = query.keys.iter().map(|(_, ty)| ty); - let value = &query.value; - let query_name = &query.query_name; - - // Emit the query struct and implement the Query trait on it. - output.extend(quote! { - #[derive(Default, Debug)] - #trait_vis struct #qt; - }); - - output.extend(quote! { - impl #qt { - /// Get access to extra methods pertaining to this query. - /// You can also use it to invoke this query. - #trait_vis fn in_db(self, db: &#dyn_db) -> salsa::QueryTable<'_, Self> - { - salsa::plumbing::get_query_table::<#qt>(db) - } - } - }); - - output.extend(quote! { - impl #qt { - /// Like `in_db`, but gives access to methods for setting the - /// value of an input. Not applicable to derived queries. - /// - /// # Threads, cancellation, and blocking - /// - /// Mutating the value of a query cannot be done while there are - /// still other queries executing. If you are using your database - /// within a single thread, this is not a problem: you only have - /// `&self` access to the database, but this method requires `&mut - /// self`. - /// - /// However, if you have used `snapshot` to create other threads, - /// then attempts to `set` will **block the current thread** until - /// those snapshots are dropped (usually when those threads - /// complete). This also implies that if you create a snapshot but - /// do not send it to another thread, then invoking `set` will - /// deadlock. - /// - /// Before blocking, the thread that is attempting to `set` will - /// also set a cancellation flag. This will cause any query - /// invocations in other threads to unwind with a `Cancelled` - /// sentinel value and eventually let the `set` succeed once all - /// threads have unwound past the salsa invocation. - /// - /// If your query implementations are performing expensive - /// operations without invoking another query, you can also use - /// the `Runtime::unwind_if_cancelled` method to check for an - /// ongoing cancellation and bring those operations to a close, - /// thus allowing the `set` to succeed. Otherwise, long-running - /// computations may lead to "starvation", meaning that the - /// thread attempting to `set` has to wait a long, long time. =) - #trait_vis fn in_db_mut(self, db: &mut #dyn_db) -> salsa::QueryTableMut<'_, Self> - { - salsa::plumbing::get_query_table_mut::<#qt>(db) - } - } - - impl<'d> salsa::QueryDb<'d> for #qt - { - type DynDb = #dyn_db + 'd; - type Group = #group_struct; - type GroupStorage = #group_storage; - } - - // ANCHOR:Query_impl - impl salsa::Query for #qt - { - type Key = (#(#keys),*); - type Value = #value; - type Storage = #storage; - - const QUERY_INDEX: u16 = #query_index; - - const QUERY_NAME: &'static str = #query_name; - - fn query_storage<'a>( - group_storage: &'a >::GroupStorage, - ) -> &'a std::sync::Arc { - &group_storage.#fn_name - } - - fn query_storage_mut<'a>( - group_storage: &'a >::GroupStorage, - ) -> &'a std::sync::Arc { - &group_storage.#fn_name - } - } - // ANCHOR_END:Query_impl - }); - - // Implement the QueryFunction trait for queries which need it. - if query.storage.needs_query_function() { - let span = query.fn_name.span(); - - let key_names: Vec<_> = query.keys.iter().map(|(pat, _)| pat).collect(); - let key_pattern = if query.keys.len() == 1 { - quote! { #(#key_names),* } - } else { - quote! { (#(#key_names),*) } - }; - let invoke = query.invoke_tt(); - - let recover = if let Some(cycle_recovery_fn) = &query.cycle { - quote! { - const CYCLE_STRATEGY: salsa::plumbing::CycleRecoveryStrategy = - salsa::plumbing::CycleRecoveryStrategy::Fallback; - fn cycle_fallback(db: &>::DynDb, cycle: &salsa::Cycle, #key_pattern: &::Key) - -> ::Value { - #cycle_recovery_fn( - db, - cycle, - #(#key_names),* - ) - } - } - } else { - quote! { - const CYCLE_STRATEGY: salsa::plumbing::CycleRecoveryStrategy = - salsa::plumbing::CycleRecoveryStrategy::Panic; - } - }; - - output.extend(quote_spanned! {span=> - // ANCHOR:QueryFunction_impl - impl salsa::plumbing::QueryFunction for #qt - { - fn execute(db: &>::DynDb, #key_pattern: ::Key) - -> ::Value { - #invoke(db, #(#key_names),*) - } - - #recover - } - // ANCHOR_END:QueryFunction_impl - }); - } - } - - let mut fmt_ops = proc_macro2::TokenStream::new(); - for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) { - fmt_ops.extend(quote! { - #query_index => { - salsa::plumbing::QueryStorageOps::fmt_index( - &*self.#fn_name, db, input.key_index(), fmt, - ) - } - }); - } - - let mut maybe_changed_ops = proc_macro2::TokenStream::new(); - for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) { - maybe_changed_ops.extend(quote! { - #query_index => { - salsa::plumbing::QueryStorageOps::maybe_changed_after( - &*self.#fn_name, db, input.key_index(), revision - ) - } - }); - } - - let mut cycle_recovery_strategy_ops = proc_macro2::TokenStream::new(); - for (Query { fn_name, .. }, query_index) in non_transparent_queries().zip(0_u16..) { - cycle_recovery_strategy_ops.extend(quote! { - #query_index => { - salsa::plumbing::QueryStorageOps::cycle_recovery_strategy( - &*self.#fn_name - ) - } - }); - } - - let mut for_each_ops = proc_macro2::TokenStream::new(); - for Query { fn_name, .. } in non_transparent_queries() { - for_each_ops.extend(quote! { - op(&*self.#fn_name); - }); - } - - // Emit query group storage struct - output.extend(quote! { - #trait_vis struct #group_storage { - #storage_fields - } - - // ANCHOR:group_storage_new - impl #group_storage { - #trait_vis fn new(group_index: u16) -> Self { - #group_storage { - #( - #queries_with_storage: - std::sync::Arc::new(salsa::plumbing::QueryStorageOps::new(group_index)), - )* - } - } - } - // ANCHOR_END:group_storage_new - - // ANCHOR:group_storage_methods - impl #group_storage { - #trait_vis fn fmt_index( - &self, - db: &(#dyn_db + '_), - input: salsa::DatabaseKeyIndex, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - match input.query_index() { - #fmt_ops - i => panic!("salsa: impossible query index {}", i), - } - } - - #trait_vis fn maybe_changed_after( - &self, - db: &(#dyn_db + '_), - input: salsa::DatabaseKeyIndex, - revision: salsa::Revision, - ) -> bool { - match input.query_index() { - #maybe_changed_ops - i => panic!("salsa: impossible query index {}", i), - } - } - - #trait_vis fn cycle_recovery_strategy( - &self, - db: &(#dyn_db + '_), - input: salsa::DatabaseKeyIndex, - ) -> salsa::plumbing::CycleRecoveryStrategy { - match input.query_index() { - #cycle_recovery_strategy_ops - i => panic!("salsa: impossible query index {}", i), - } - } - - #trait_vis fn for_each_query( - &self, - _runtime: &salsa::Runtime, - mut op: &mut dyn FnMut(&dyn salsa::plumbing::QueryStorageMassOps), - ) { - #for_each_ops - } - } - // ANCHOR_END:group_storage_methods - }); - output.into() -} - -struct SalsaAttr { - name: String, - tts: TokenStream, - span: Span, -} - -impl std::fmt::Debug for SalsaAttr { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(fmt, "{:?}", self.name) - } -} - -impl TryFrom for SalsaAttr { - type Error = syn::Attribute; - - fn try_from(attr: syn::Attribute) -> Result { - if is_not_salsa_attr_path(attr.path()) { - return Err(attr); - } - - let span = attr.span(); - let name = attr.path().segments[1].ident.to_string(); - let tts = match attr.meta { - syn::Meta::Path(path) => path.into_token_stream(), - syn::Meta::List(ref list) => { - let tts = list - .into_token_stream() - .into_iter() - .skip(attr.path().to_token_stream().into_iter().count()); - proc_macro2::TokenStream::from_iter(tts) - } - syn::Meta::NameValue(nv) => nv.into_token_stream(), - } - .into(); - - Ok(SalsaAttr { name, tts, span }) - } -} - -fn is_not_salsa_attr_path(path: &syn::Path) -> bool { - path.segments.first().map(|s| s.ident != "salsa").unwrap_or(true) || path.segments.len() != 2 -} - -fn filter_attrs(attrs: Vec) -> (Vec, Vec) { - let mut other = vec![]; - let mut salsa = vec![]; - // Leave non-salsa attributes untouched. These are - // attributes that don't start with `salsa::` or don't have - // exactly two segments in their path. - // Keep the salsa attributes around. - for attr in attrs { - match SalsaAttr::try_from(attr) { - Ok(it) => salsa.push(it), - Err(it) => other.push(it), - } - } - (other, salsa) -} - -#[derive(Debug)] -struct Query { - fn_name: Ident, - receiver: syn::Receiver, - query_name: String, - attrs: Vec, - query_type: Ident, - storage: QueryStorage, - keys: Vec<(Ident, syn::Type)>, - value: syn::Type, - invoke: Option, - cycle: Option, -} - -impl Query { - fn invoke_tt(&self) -> proc_macro2::TokenStream { - match &self.invoke { - Some(i) => i.into_token_stream(), - None => self.fn_name.clone().into_token_stream(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -enum QueryStorage { - Memoized, - LruDependencies, - LruMemoized, - Input, - Interned, - InternedLookup { intern_query_type: Ident }, - Transparent, -} - -impl QueryStorage { - /// Do we need a `QueryFunction` impl for this type of query? - fn needs_query_function(&self) -> bool { - match self { - QueryStorage::Input - | QueryStorage::Interned - | QueryStorage::InternedLookup { .. } - | QueryStorage::Transparent => false, - QueryStorage::Memoized | QueryStorage::LruMemoized | QueryStorage::LruDependencies => { - true - } - } - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/debug.rs b/src/tools/rust-analyzer/crates/salsa/src/debug.rs deleted file mode 100644 index 5f113541f04..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/debug.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Debugging APIs: these are meant for use when unit-testing or -//! debugging your application but aren't ordinarily needed. - -use crate::durability::Durability; -use crate::plumbing::QueryStorageOps; -use crate::Query; -use crate::QueryTable; - -/// Additional methods on queries that can be used to "peek into" -/// their current state. These methods are meant for debugging and -/// observing the effects of garbage collection etc. -pub trait DebugQueryTable { - /// Key of this query. - type Key; - - /// Value of this query. - type Value; - - /// Returns a lower bound on the durability for the given key. - /// This is typically the minimum durability of all values that - /// the query accessed, but we may return a lower durability in - /// some cases. - fn durability(&self, key: Self::Key) -> Durability; - - /// Get the (current) set of the entries in the query table. - fn entries(&self) -> C - where - C: FromIterator>; -} - -/// An entry from a query table, for debugging and inspecting the table state. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[non_exhaustive] -pub struct TableEntry { - /// key of the query - pub key: K, - /// value of the query, if it is stored - pub value: Option, -} - -impl TableEntry { - pub(crate) fn new(key: K, value: Option) -> TableEntry { - TableEntry { key, value } - } -} - -impl DebugQueryTable for QueryTable<'_, Q> -where - Q: Query, - Q::Storage: QueryStorageOps, -{ - type Key = Q::Key; - type Value = Q::Value; - - fn durability(&self, key: Q::Key) -> Durability { - self.storage.durability(self.db, &key) - } - - fn entries(&self) -> C - where - C: FromIterator>, - { - self.storage.entries(self.db) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/derived.rs b/src/tools/rust-analyzer/crates/salsa/src/derived.rs deleted file mode 100644 index 8b2fdd6b19c..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/derived.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::debug::TableEntry; -use crate::durability::Durability; -use crate::hash::FxIndexMap; -use crate::plumbing::DerivedQueryStorageOps; -use crate::plumbing::QueryFunction; -use crate::plumbing::QueryStorageMassOps; -use crate::plumbing::QueryStorageOps; -use crate::runtime::StampedValue; -use crate::Runtime; -use crate::{Database, DatabaseKeyIndex, QueryDb, Revision}; -use parking_lot::RwLock; -use std::borrow::Borrow; -use std::hash::Hash; -use triomphe::Arc; - -mod slot; -use slot::Slot; - -/// Memoized queries store the result plus a list of the other queries -/// that they invoked. This means we can avoid recomputing them when -/// none of those inputs have changed. -pub type MemoizedStorage = DerivedStorage; - -/// Handles storage where the value is 'derived' by executing a -/// function (in contrast to "inputs"). -pub struct DerivedStorage -where - Q: QueryFunction, -{ - group_index: u16, - slot_map: RwLock>>>, -} - -impl std::panic::RefUnwindSafe for DerivedStorage -where - Q: QueryFunction, - - Q::Key: std::panic::RefUnwindSafe, - Q::Value: std::panic::RefUnwindSafe, -{ -} - -impl DerivedStorage -where - Q: QueryFunction, - Q::Value: Eq, -{ - fn slot(&self, key: &Q::Key) -> Arc> { - if let Some(v) = self.slot_map.read().get(key) { - return v.clone(); - } - - let mut write = self.slot_map.write(); - let entry = write.entry(key.clone()); - let key_index = entry.index() as u32; - let database_key_index = DatabaseKeyIndex { - group_index: self.group_index, - query_index: Q::QUERY_INDEX, - key_index, - }; - entry.or_insert_with(|| Arc::new(Slot::new(database_key_index))).clone() - } -} - -impl QueryStorageOps for DerivedStorage -where - Q: QueryFunction, - Q::Value: Eq, -{ - const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = Q::CYCLE_STRATEGY; - - fn new(group_index: u16) -> Self { - DerivedStorage { group_index, slot_map: RwLock::new(FxIndexMap::default()) } - } - - fn fmt_index( - &self, - _db: &>::DynDb, - index: u32, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - let slot_map = self.slot_map.read(); - let key = slot_map.get_index(index as usize).unwrap().0; - write!(fmt, "{}::{}({:?})", std::any::type_name::(), Q::QUERY_NAME, key) - } - - fn maybe_changed_after( - &self, - db: &>::DynDb, - index: u32, - revision: Revision, - ) -> bool { - debug_assert!(revision < db.salsa_runtime().current_revision()); - let (key, slot) = { - let read = self.slot_map.read(); - let Some((key, slot)) = read.get_index(index as usize) else { - return false; - }; - (key.clone(), slot.clone()) - }; - slot.maybe_changed_after(db, revision, &key) - } - - fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { - db.unwind_if_cancelled(); - - let slot = self.slot(key); - let StampedValue { value, durability, changed_at } = slot.read(db, key); - - db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( - slot.database_key_index(), - durability, - changed_at, - ); - - value - } - - fn durability(&self, db: &>::DynDb, key: &Q::Key) -> Durability { - self.slot_map.read().get(key).map_or(Durability::LOW, |slot| slot.durability(db)) - } - - fn entries(&self, _db: &>::DynDb) -> C - where - C: std::iter::FromIterator>, - { - let slot_map = self.slot_map.read(); - slot_map.iter().filter_map(|(key, slot)| slot.as_table_entry(key)).collect() - } -} - -impl QueryStorageMassOps for DerivedStorage -where - Q: QueryFunction, -{ - fn purge(&self) { - *self.slot_map.write() = Default::default(); - } -} - -impl DerivedQueryStorageOps for DerivedStorage -where - Q: QueryFunction, - Q::Value: Eq, -{ - fn invalidate(&self, runtime: &mut Runtime, key: &S) - where - S: Eq + Hash, - Q::Key: Borrow, - { - runtime.with_incremented_revision(|new_revision| { - let map_read = self.slot_map.read(); - - if let Some(slot) = map_read.get(key) { - if let Some(durability) = slot.invalidate(new_revision) { - return Some(durability); - } - } - - None - }) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/derived/slot.rs b/src/tools/rust-analyzer/crates/salsa/src/derived/slot.rs deleted file mode 100644 index de7a3976074..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/derived/slot.rs +++ /dev/null @@ -1,775 +0,0 @@ -use crate::debug::TableEntry; -use crate::durability::Durability; -use crate::plumbing::{DatabaseOps, QueryFunction}; -use crate::revision::Revision; -use crate::runtime::local_state::ActiveQueryGuard; -use crate::runtime::local_state::QueryRevisions; -use crate::runtime::Runtime; -use crate::runtime::RuntimeId; -use crate::runtime::StampedValue; -use crate::runtime::WaitResult; -use crate::Cycle; -use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb}; -use parking_lot::{RawRwLock, RwLock}; -use std::ops::Deref; -use std::sync::atomic::{AtomicBool, Ordering}; -use tracing::{debug, info}; - -pub(super) struct Slot -where - Q: QueryFunction, -{ - key_index: u32, - // FIXME: Yeet this - group_index: u16, - state: RwLock>, -} - -/// Defines the "current state" of query's memoized results. -enum QueryState -where - Q: QueryFunction, -{ - NotComputed, - - /// The runtime with the given id is currently computing the - /// result of this query. - InProgress { - id: RuntimeId, - - /// Set to true if any other queries are blocked, - /// waiting for this query to complete. - anyone_waiting: AtomicBool, - }, - - /// We have computed the query already, and here is the result. - Memoized(Memo), -} - -struct Memo { - /// The result of the query, if we decide to memoize it. - value: V, - - /// Last revision when this memo was verified; this begins - /// as the current revision. - pub(crate) verified_at: Revision, - - /// Revision information - revisions: QueryRevisions, -} - -/// Return value of `probe` helper. -enum ProbeState { - /// Another thread was active but has completed. - /// Try again! - Retry, - - /// No entry for this key at all. - NotComputed(G), - - /// There is an entry, but its contents have not been - /// verified in this revision. - Stale(G), - - /// There is an entry which has been verified, - /// and it has the following value-- or, we blocked - /// on another thread, and that resulted in a cycle. - UpToDate(V), -} - -/// Return value of `maybe_changed_after_probe` helper. -enum MaybeChangedSinceProbeState { - /// Another thread was active but has completed. - /// Try again! - Retry, - - /// Value may have changed in the given revision. - ChangedAt(Revision), - - /// There is a stale cache entry that has not been - /// verified in this revision, so we can't say. - Stale(G), -} - -impl Slot -where - Q: QueryFunction, - Q::Value: Eq, -{ - pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self { - Self { - key_index: database_key_index.key_index, - group_index: database_key_index.group_index, - state: RwLock::new(QueryState::NotComputed), - } - } - - pub(super) fn database_key_index(&self) -> DatabaseKeyIndex { - DatabaseKeyIndex { - group_index: self.group_index, - query_index: Q::QUERY_INDEX, - key_index: self.key_index, - } - } - - pub(super) fn read( - &self, - db: &>::DynDb, - key: &Q::Key, - ) -> StampedValue { - let runtime = db.salsa_runtime(); - - // NB: We don't need to worry about people modifying the - // revision out from under our feet. Either `db` is a frozen - // database, in which case there is a lock, or the mutator - // thread is the current thread, and it will be prevented from - // doing any `set` invocations while the query function runs. - let revision_now = runtime.current_revision(); - - info!("{:?}: invoked at {:?}", self, revision_now,); - - // First, do a check with a read-lock. - loop { - match self.probe(db, self.state.read(), runtime, revision_now) { - ProbeState::UpToDate(v) => return v, - ProbeState::Stale(..) | ProbeState::NotComputed(..) => break, - ProbeState::Retry => continue, - } - } - - self.read_upgrade(db, key, revision_now) - } - - /// Second phase of a read operation: acquires an upgradable-read - /// and -- if needed -- validates whether inputs have changed, - /// recomputes value, etc. This is invoked after our initial probe - /// shows a potentially out of date value. - fn read_upgrade( - &self, - db: &>::DynDb, - key: &Q::Key, - revision_now: Revision, - ) -> StampedValue { - let runtime = db.salsa_runtime(); - - debug!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,); - - // Check with an upgradable read to see if there is a value - // already. (This permits other readers but prevents anyone - // else from running `read_upgrade` at the same time.) - let mut old_memo = loop { - match self.probe(db, self.state.upgradable_read(), runtime, revision_now) { - ProbeState::UpToDate(v) => return v, - ProbeState::Stale(state) | ProbeState::NotComputed(state) => { - type RwLockUpgradableReadGuard<'a, T> = - lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>; - - let mut state = RwLockUpgradableReadGuard::upgrade(state); - match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) { - QueryState::Memoized(old_memo) => break Some(old_memo), - QueryState::InProgress { .. } => unreachable!(), - QueryState::NotComputed => break None, - } - } - ProbeState::Retry => continue, - } - }; - - let panic_guard = PanicGuard::new(self, runtime); - let active_query = runtime.push_query(self.database_key_index()); - - // If we have an old-value, it *may* now be stale, since there - // has been a new revision since the last time we checked. So, - // first things first, let's walk over each of our previous - // inputs and check whether they are out of date. - if let Some(memo) = &mut old_memo { - if let Some(value) = memo.verify_value(db.ops_database(), revision_now, &active_query) { - info!("{:?}: validated old memoized value", self,); - - db.salsa_event(Event { - runtime_id: runtime.id(), - kind: EventKind::DidValidateMemoizedValue { - database_key: self.database_key_index(), - }, - }); - - panic_guard.proceed(old_memo); - - return value; - } - } - - self.execute(db, runtime, revision_now, active_query, panic_guard, old_memo, key) - } - - fn execute( - &self, - db: &>::DynDb, - runtime: &Runtime, - revision_now: Revision, - active_query: ActiveQueryGuard<'_>, - panic_guard: PanicGuard<'_, Q>, - old_memo: Option>, - key: &Q::Key, - ) -> StampedValue { - tracing::info!("{:?}: executing query", self.database_key_index().debug(db)); - - db.salsa_event(Event { - runtime_id: db.salsa_runtime().id(), - kind: EventKind::WillExecute { database_key: self.database_key_index() }, - }); - - // Query was not previously executed, or value is potentially - // stale, or value is absent. Let's execute! - let value = match Cycle::catch(|| Q::execute(db, key.clone())) { - Ok(v) => v, - Err(cycle) => { - tracing::debug!( - "{:?}: caught cycle {:?}, have strategy {:?}", - self.database_key_index().debug(db), - cycle, - Q::CYCLE_STRATEGY, - ); - match Q::CYCLE_STRATEGY { - crate::plumbing::CycleRecoveryStrategy::Panic => { - panic_guard.proceed(None); - cycle.throw() - } - crate::plumbing::CycleRecoveryStrategy::Fallback => { - if let Some(c) = active_query.take_cycle() { - assert!(c.is(&cycle)); - Q::cycle_fallback(db, &cycle, key) - } else { - // we are not a participant in this cycle - debug_assert!(!cycle - .participant_keys() - .any(|k| k == self.database_key_index())); - cycle.throw() - } - } - } - } - }; - - let mut revisions = active_query.pop(); - - // We assume that query is side-effect free -- that is, does - // not mutate the "inputs" to the query system. Sanity check - // that assumption here, at least to the best of our ability. - assert_eq!( - runtime.current_revision(), - revision_now, - "revision altered during query execution", - ); - - // If the new value is equal to the old one, then it didn't - // really change, even if some of its inputs have. So we can - // "backdate" its `changed_at` revision to be the same as the - // old value. - if let Some(old_memo) = &old_memo { - // Careful: if the value became less durable than it - // used to be, that is a "breaking change" that our - // consumers must be aware of. Becoming *more* durable - // is not. See the test `constant_to_non_constant`. - if revisions.durability >= old_memo.revisions.durability && old_memo.value == value { - debug!( - "read_upgrade({:?}): value is equal, back-dating to {:?}", - self, old_memo.revisions.changed_at, - ); - - assert!(old_memo.revisions.changed_at <= revisions.changed_at); - revisions.changed_at = old_memo.revisions.changed_at; - } - } - - let new_value = StampedValue { - value, - durability: revisions.durability, - changed_at: revisions.changed_at, - }; - - let memo_value = new_value.value.clone(); - - debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,); - - panic_guard.proceed(Some(Memo { value: memo_value, verified_at: revision_now, revisions })); - - new_value - } - - /// Helper for `read` that does a shallow check (not recursive) if we have an up-to-date value. - /// - /// Invoked with the guard `state` corresponding to the `QueryState` of some `Slot` (the guard - /// can be either read or write). Returns a suitable `ProbeState`: - /// - /// - `ProbeState::UpToDate(r)` if the table has an up-to-date value (or we blocked on another - /// thread that produced such a value). - /// - `ProbeState::StaleOrAbsent(g)` if either (a) there is no memo for this key, (b) the memo - /// has no value; or (c) the memo has not been verified at the current revision. - /// - /// Note that in case `ProbeState::UpToDate`, the lock will have been released. - fn probe( - &self, - db: &>::DynDb, - state: StateGuard, - runtime: &Runtime, - revision_now: Revision, - ) -> ProbeState, StateGuard> - where - StateGuard: Deref>, - { - match &*state { - QueryState::NotComputed => ProbeState::NotComputed(state), - - QueryState::InProgress { id, anyone_waiting } => { - let other_id = *id; - - // NB: `Ordering::Relaxed` is sufficient here, - // as there are no loads that are "gated" on this - // value. Everything that is written is also protected - // by a lock that must be acquired. The role of this - // boolean is to decide *whether* to acquire the lock, - // not to gate future atomic reads. - anyone_waiting.store(true, Ordering::Relaxed); - - self.block_on_or_unwind(db, runtime, other_id, state); - - // Other thread completely normally, so our value may be available now. - ProbeState::Retry - } - - QueryState::Memoized(memo) => { - debug!( - "{:?}: found memoized value, verified_at={:?}, changed_at={:?}", - self, memo.verified_at, memo.revisions.changed_at, - ); - - if memo.verified_at < revision_now { - return ProbeState::Stale(state); - } - - let value = &memo.value; - let value = StampedValue { - durability: memo.revisions.durability, - changed_at: memo.revisions.changed_at, - value: value.clone(), - }; - - info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at); - - ProbeState::UpToDate(value) - } - } - } - - pub(super) fn durability(&self, db: &>::DynDb) -> Durability { - match &*self.state.read() { - QueryState::NotComputed => Durability::LOW, - QueryState::InProgress { .. } => panic!("query in progress"), - QueryState::Memoized(memo) => { - if memo.check_durability(db.salsa_runtime()) { - memo.revisions.durability - } else { - Durability::LOW - } - } - } - } - - pub(super) fn as_table_entry(&self, key: &Q::Key) -> Option> { - match &*self.state.read() { - QueryState::NotComputed => None, - QueryState::InProgress { .. } => Some(TableEntry::new(key.clone(), None)), - QueryState::Memoized(memo) => { - Some(TableEntry::new(key.clone(), Some(memo.value.clone()))) - } - } - } - - pub(super) fn invalidate(&self, new_revision: Revision) -> Option { - tracing::debug!("Slot::invalidate(new_revision = {:?})", new_revision); - match &mut *self.state.write() { - QueryState::Memoized(memo) => { - memo.revisions.untracked = true; - memo.revisions.inputs = None; - memo.revisions.changed_at = new_revision; - Some(memo.revisions.durability) - } - QueryState::NotComputed => None, - QueryState::InProgress { .. } => unreachable!(), - } - } - - pub(super) fn maybe_changed_after( - &self, - db: &>::DynDb, - revision: Revision, - key: &Q::Key, - ) -> bool { - let runtime = db.salsa_runtime(); - let revision_now = runtime.current_revision(); - - db.unwind_if_cancelled(); - - debug!( - "maybe_changed_after({:?}) called with revision={:?}, revision_now={:?}", - self, revision, revision_now, - ); - - // Do an initial probe with just the read-lock. - // - // If we find that a cache entry for the value is present - // but hasn't been verified in this revision, we'll have to - // do more. - loop { - match self.maybe_changed_after_probe(db, self.state.read(), runtime, revision_now) { - MaybeChangedSinceProbeState::Retry => continue, - MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision, - MaybeChangedSinceProbeState::Stale(state) => { - drop(state); - return self.maybe_changed_after_upgrade(db, revision, key); - } - } - } - } - - fn maybe_changed_after_probe( - &self, - db: &>::DynDb, - state: StateGuard, - runtime: &Runtime, - revision_now: Revision, - ) -> MaybeChangedSinceProbeState - where - StateGuard: Deref>, - { - match self.probe(db, state, runtime, revision_now) { - ProbeState::Retry => MaybeChangedSinceProbeState::Retry, - - ProbeState::Stale(state) => MaybeChangedSinceProbeState::Stale(state), - - // If we know when value last changed, we can return right away. - // Note that we don't need the actual value to be available. - ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => { - MaybeChangedSinceProbeState::ChangedAt(changed_at) - } - - // If we have nothing cached, then value may have changed. - ProbeState::NotComputed(_) => MaybeChangedSinceProbeState::ChangedAt(revision_now), - } - } - - fn maybe_changed_after_upgrade( - &self, - db: &>::DynDb, - revision: Revision, - key: &Q::Key, - ) -> bool { - let runtime = db.salsa_runtime(); - let revision_now = runtime.current_revision(); - - // Get an upgradable read lock, which permits other reads but no writers. - // Probe again. If the value is stale (needs to be verified), then upgrade - // to a write lock and swap it with InProgress while we work. - let mut old_memo = match self.maybe_changed_after_probe( - db, - self.state.upgradable_read(), - runtime, - revision_now, - ) { - MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision, - - // If another thread was active, then the cache line is going to be - // either verified or cleared out. Just recurse to figure out which. - // Note that we don't need an upgradable read. - MaybeChangedSinceProbeState::Retry => { - return self.maybe_changed_after(db, revision, key) - } - - MaybeChangedSinceProbeState::Stale(state) => { - type RwLockUpgradableReadGuard<'a, T> = - lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>; - - let mut state = RwLockUpgradableReadGuard::upgrade(state); - match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) { - QueryState::Memoized(old_memo) => old_memo, - QueryState::NotComputed | QueryState::InProgress { .. } => unreachable!(), - } - } - }; - - let panic_guard = PanicGuard::new(self, runtime); - let active_query = runtime.push_query(self.database_key_index()); - - if old_memo.verify_revisions(db.ops_database(), revision_now, &active_query) { - let maybe_changed = old_memo.revisions.changed_at > revision; - panic_guard.proceed(Some(old_memo)); - maybe_changed - } else { - // We found that this memoized value may have changed - // but we have an old value. We can re-run the code and - // actually *check* if it has changed. - let StampedValue { changed_at, .. } = self.execute( - db, - runtime, - revision_now, - active_query, - panic_guard, - Some(old_memo), - key, - ); - changed_at > revision - } - } - - /// Helper: see [`Runtime::try_block_on_or_unwind`]. - fn block_on_or_unwind( - &self, - db: &>::DynDb, - runtime: &Runtime, - other_id: RuntimeId, - mutex_guard: MutexGuard, - ) { - runtime.block_on_or_unwind( - db.ops_database(), - self.database_key_index(), - other_id, - mutex_guard, - ) - } -} - -impl QueryState -where - Q: QueryFunction, -{ - fn in_progress(id: RuntimeId) -> Self { - QueryState::InProgress { id, anyone_waiting: Default::default() } - } -} - -struct PanicGuard<'me, Q> -where - Q: QueryFunction, - Q::Value: Eq, -{ - slot: &'me Slot, - runtime: &'me Runtime, -} - -impl<'me, Q> PanicGuard<'me, Q> -where - Q: QueryFunction, - Q::Value: Eq, -{ - fn new(slot: &'me Slot, runtime: &'me Runtime) -> Self { - Self { slot, runtime } - } - - /// Indicates that we have concluded normally (without panicking). - /// If `opt_memo` is some, then this memo is installed as the new - /// memoized value. If `opt_memo` is `None`, then the slot is cleared - /// and has no value. - fn proceed(mut self, opt_memo: Option>) { - self.overwrite_placeholder(WaitResult::Completed, opt_memo); - std::mem::forget(self) - } - - /// Overwrites the `InProgress` placeholder for `key` that we - /// inserted; if others were blocked, waiting for us to finish, - /// then notify them. - fn overwrite_placeholder(&mut self, wait_result: WaitResult, opt_memo: Option>) { - let old_value = { - let mut write = self.slot.state.write(); - match opt_memo { - // Replace the `InProgress` marker that we installed with the new - // memo, thus releasing our unique access to this key. - Some(memo) => std::mem::replace(&mut *write, QueryState::Memoized(memo)), - - // We had installed an `InProgress` marker, but we panicked before - // it could be removed. At this point, we therefore "own" unique - // access to our slot, so we can just remove the key. - None => std::mem::replace(&mut *write, QueryState::NotComputed), - } - }; - - match old_value { - QueryState::InProgress { id, anyone_waiting } => { - assert_eq!(id, self.runtime.id()); - - // NB: As noted on the `store`, `Ordering::Relaxed` is - // sufficient here. This boolean signals us on whether to - // acquire a mutex; the mutex will guarantee that all writes - // we are interested in are visible. - if anyone_waiting.load(Ordering::Relaxed) { - self.runtime - .unblock_queries_blocked_on(self.slot.database_key_index(), wait_result); - } - } - _ => panic!( - "\ -Unexpected panic during query evaluation, aborting the process. - -Please report this bug to https://github.com/salsa-rs/salsa/issues." - ), - } - } -} - -impl<'me, Q> Drop for PanicGuard<'me, Q> -where - Q: QueryFunction, - Q::Value: Eq, -{ - fn drop(&mut self) { - if std::thread::panicking() { - // We panicked before we could proceed and need to remove `key`. - self.overwrite_placeholder(WaitResult::Panicked, None) - } else { - // If no panic occurred, then panic guard ought to be - // "forgotten" and so this Drop code should never run. - panic!(".forget() was not called") - } - } -} - -impl Memo -where - V: Clone, -{ - /// Determines whether the value stored in this memo (if any) is still - /// valid in the current revision. If so, returns a stamped value. - /// - /// If needed, this will walk each dependency and - /// recursively invoke `maybe_changed_after`, which may in turn - /// re-execute the dependency. This can cause cycles to occur, - /// so the current query must be pushed onto the - /// stack to permit cycle detection and recovery: therefore, - /// takes the `active_query` argument as evidence. - fn verify_value( - &mut self, - db: &dyn Database, - revision_now: Revision, - active_query: &ActiveQueryGuard<'_>, - ) -> Option> { - if self.verify_revisions(db, revision_now, active_query) { - Some(StampedValue { - durability: self.revisions.durability, - changed_at: self.revisions.changed_at, - value: self.value.clone(), - }) - } else { - None - } - } - - /// Determines whether the value represented by this memo is still - /// valid in the current revision; note that the value itself is - /// not needed for this check. If needed, this will walk each - /// dependency and recursively invoke `maybe_changed_after`, which - /// may in turn re-execute the dependency. This can cause cycles to occur, - /// so the current query must be pushed onto the - /// stack to permit cycle detection and recovery: therefore, - /// takes the `active_query` argument as evidence. - fn verify_revisions( - &mut self, - db: &dyn Database, - revision_now: Revision, - _active_query: &ActiveQueryGuard<'_>, - ) -> bool { - assert!(self.verified_at != revision_now); - let verified_at = self.verified_at; - - debug!( - "verify_revisions: verified_at={:?}, revision_now={:?}, inputs={:#?}", - verified_at, revision_now, self.revisions.inputs - ); - - if self.check_durability(db.salsa_runtime()) { - return self.mark_value_as_verified(revision_now); - } - - match &self.revisions.inputs { - // We can't validate values that had untracked inputs; just have to - // re-execute. - None if self.revisions.untracked => return false, - None => {} - - // Check whether any of our inputs changed since the - // **last point where we were verified** (not since we - // last changed). This is important: if we have - // memoized values, then an input may have changed in - // revision R2, but we found that *our* value was the - // same regardless, so our change date is still - // R1. But our *verification* date will be R2, and we - // are only interested in finding out whether the - // input changed *again*. - Some(inputs) => { - let changed_input = - inputs.slice.iter().find(|&&input| db.maybe_changed_after(input, verified_at)); - if let Some(input) = changed_input { - debug!("validate_memoized_value: `{:?}` may have changed", input); - - return false; - } - } - }; - - self.mark_value_as_verified(revision_now) - } - - /// True if this memo is known not to have changed based on its durability. - fn check_durability(&self, runtime: &Runtime) -> bool { - let last_changed = runtime.last_changed_revision(self.revisions.durability); - debug!( - "check_durability(last_changed={:?} <= verified_at={:?}) = {:?}", - last_changed, - self.verified_at, - last_changed <= self.verified_at, - ); - last_changed <= self.verified_at - } - - fn mark_value_as_verified(&mut self, revision_now: Revision) -> bool { - self.verified_at = revision_now; - true - } -} - -impl std::fmt::Debug for Slot -where - Q: QueryFunction, -{ - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(fmt, "{:?}", Q::default()) - } -} - -/// Check that `Slot: Send + Sync` as long as -/// `DB::DatabaseData: Send + Sync`, which in turn implies that -/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. -#[allow(dead_code)] -fn check_send_sync() -where - Q: QueryFunction, - - Q::Key: Send + Sync, - Q::Value: Send + Sync, -{ - fn is_send_sync() {} - is_send_sync::>(); -} - -/// Check that `Slot: 'static` as long as -/// `DB::DatabaseData: 'static`, which in turn implies that -/// `Q::Key: 'static`, `Q::Value: 'static`. -#[allow(dead_code)] -fn check_static() -where - Q: QueryFunction + 'static, - Q::Key: 'static, - Q::Value: 'static, -{ - fn is_static() {} - is_static::>(); -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/derived_lru.rs b/src/tools/rust-analyzer/crates/salsa/src/derived_lru.rs deleted file mode 100644 index bdb448e2412..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/derived_lru.rs +++ /dev/null @@ -1,233 +0,0 @@ -use crate::debug::TableEntry; -use crate::durability::Durability; -use crate::hash::FxIndexMap; -use crate::lru::Lru; -use crate::plumbing::DerivedQueryStorageOps; -use crate::plumbing::LruQueryStorageOps; -use crate::plumbing::QueryFunction; -use crate::plumbing::QueryStorageMassOps; -use crate::plumbing::QueryStorageOps; -use crate::runtime::StampedValue; -use crate::Runtime; -use crate::{Database, DatabaseKeyIndex, QueryDb, Revision}; -use parking_lot::RwLock; -use std::borrow::Borrow; -use std::hash::Hash; -use std::marker::PhantomData; -use triomphe::Arc; - -mod slot; -use slot::Slot; - -/// Memoized queries store the result plus a list of the other queries -/// that they invoked. This means we can avoid recomputing them when -/// none of those inputs have changed. -pub type MemoizedStorage = DerivedStorage; - -/// "Dependency" queries just track their dependencies and not the -/// actual value (which they produce on demand). This lessens the -/// storage requirements. -pub type DependencyStorage = DerivedStorage; - -/// Handles storage where the value is 'derived' by executing a -/// function (in contrast to "inputs"). -pub struct DerivedStorage -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - group_index: u16, - lru_list: Lru>, - slot_map: RwLock>>>, - policy: PhantomData, -} - -impl std::panic::RefUnwindSafe for DerivedStorage -where - Q: QueryFunction, - MP: MemoizationPolicy, - Q::Key: std::panic::RefUnwindSafe, - Q::Value: std::panic::RefUnwindSafe, -{ -} - -pub trait MemoizationPolicy: Send + Sync -where - Q: QueryFunction, -{ - fn should_memoize_value(key: &Q::Key) -> bool; - - fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool; -} - -pub enum AlwaysMemoizeValue {} -impl MemoizationPolicy for AlwaysMemoizeValue -where - Q: QueryFunction, - Q::Value: Eq, -{ - fn should_memoize_value(_key: &Q::Key) -> bool { - true - } - - fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool { - old_value == new_value - } -} - -pub enum NeverMemoizeValue {} -impl MemoizationPolicy for NeverMemoizeValue -where - Q: QueryFunction, -{ - fn should_memoize_value(_key: &Q::Key) -> bool { - false - } - - fn memoized_value_eq(_old_value: &Q::Value, _new_value: &Q::Value) -> bool { - panic!("cannot reach since we never memoize") - } -} - -impl DerivedStorage -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - fn slot(&self, key: &Q::Key) -> Arc> { - if let Some(v) = self.slot_map.read().get(key) { - return v.clone(); - } - - let mut write = self.slot_map.write(); - let entry = write.entry(key.clone()); - let key_index = entry.index() as u32; - let database_key_index = DatabaseKeyIndex { - group_index: self.group_index, - query_index: Q::QUERY_INDEX, - key_index, - }; - entry.or_insert_with(|| Arc::new(Slot::new(database_key_index))).clone() - } -} - -impl QueryStorageOps for DerivedStorage -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = Q::CYCLE_STRATEGY; - - fn new(group_index: u16) -> Self { - DerivedStorage { - group_index, - slot_map: RwLock::new(FxIndexMap::default()), - lru_list: Default::default(), - policy: PhantomData, - } - } - - fn fmt_index( - &self, - _db: &>::DynDb, - index: u32, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - let slot_map = self.slot_map.read(); - let key = slot_map.get_index(index as usize).unwrap().0; - write!(fmt, "{}::{}({:?})", std::any::type_name::(), Q::QUERY_NAME, key) - } - - fn maybe_changed_after( - &self, - db: &>::DynDb, - index: u32, - revision: Revision, - ) -> bool { - debug_assert!(revision < db.salsa_runtime().current_revision()); - let (key, slot) = { - let read = self.slot_map.read(); - let Some((key, slot)) = read.get_index(index as usize) else { - return false; - }; - (key.clone(), slot.clone()) - }; - slot.maybe_changed_after(db, revision, &key) - } - - fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { - db.unwind_if_cancelled(); - - let slot = self.slot(key); - let StampedValue { value, durability, changed_at } = slot.read(db, key); - - if let Some(evicted) = self.lru_list.record_use(&slot) { - evicted.evict(); - } - - db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( - slot.database_key_index(), - durability, - changed_at, - ); - - value - } - - fn durability(&self, db: &>::DynDb, key: &Q::Key) -> Durability { - self.slot(key).durability(db) - } - - fn entries(&self, _db: &>::DynDb) -> C - where - C: std::iter::FromIterator>, - { - let slot_map = self.slot_map.read(); - slot_map.iter().filter_map(|(key, slot)| slot.as_table_entry(key)).collect() - } -} - -impl QueryStorageMassOps for DerivedStorage -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - fn purge(&self) { - self.lru_list.purge(); - *self.slot_map.write() = Default::default(); - } -} - -impl LruQueryStorageOps for DerivedStorage -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - fn set_lru_capacity(&self, new_capacity: u16) { - self.lru_list.set_lru_capacity(new_capacity); - } -} - -impl DerivedQueryStorageOps for DerivedStorage -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - fn invalidate(&self, runtime: &mut Runtime, key: &S) - where - S: Eq + Hash, - Q::Key: Borrow, - { - runtime.with_incremented_revision(|new_revision| { - let map_read = self.slot_map.read(); - - if let Some(slot) = map_read.get(key) { - if let Some(durability) = slot.invalidate(new_revision) { - return Some(durability); - } - } - - None - }) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/derived_lru/slot.rs b/src/tools/rust-analyzer/crates/salsa/src/derived_lru/slot.rs deleted file mode 100644 index d0e4b5422b5..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/derived_lru/slot.rs +++ /dev/null @@ -1,845 +0,0 @@ -use crate::debug::TableEntry; -use crate::derived_lru::MemoizationPolicy; -use crate::durability::Durability; -use crate::lru::LruIndex; -use crate::lru::LruNode; -use crate::plumbing::{DatabaseOps, QueryFunction}; -use crate::revision::Revision; -use crate::runtime::local_state::ActiveQueryGuard; -use crate::runtime::local_state::QueryRevisions; -use crate::runtime::Runtime; -use crate::runtime::RuntimeId; -use crate::runtime::StampedValue; -use crate::runtime::WaitResult; -use crate::Cycle; -use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb}; -use parking_lot::{RawRwLock, RwLock}; -use std::marker::PhantomData; -use std::ops::Deref; -use std::sync::atomic::{AtomicBool, Ordering}; -use tracing::{debug, info}; - -pub(super) struct Slot -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - key_index: u32, - group_index: u16, - state: RwLock>, - lru_index: LruIndex, - policy: PhantomData, -} - -/// Defines the "current state" of query's memoized results. -enum QueryState -where - Q: QueryFunction, -{ - NotComputed, - - /// The runtime with the given id is currently computing the - /// result of this query. - InProgress { - id: RuntimeId, - - /// Set to true if any other queries are blocked, - /// waiting for this query to complete. - anyone_waiting: AtomicBool, - }, - - /// We have computed the query already, and here is the result. - Memoized(Memo), -} - -struct Memo { - /// The result of the query, if we decide to memoize it. - value: Option, - - /// Last revision when this memo was verified; this begins - /// as the current revision. - pub(crate) verified_at: Revision, - - /// Revision information - revisions: QueryRevisions, -} - -/// Return value of `probe` helper. -enum ProbeState { - /// Another thread was active but has completed. - /// Try again! - Retry, - - /// No entry for this key at all. - NotComputed(G), - - /// There is an entry, but its contents have not been - /// verified in this revision. - Stale(G), - - /// There is an entry, and it has been verified - /// in this revision, but it has no cached - /// value. The `Revision` is the revision where the - /// value last changed (if we were to recompute it). - NoValue(G, Revision), - - /// There is an entry which has been verified, - /// and it has the following value-- or, we blocked - /// on another thread, and that resulted in a cycle. - UpToDate(V), -} - -/// Return value of `maybe_changed_after_probe` helper. -enum MaybeChangedSinceProbeState { - /// Another thread was active but has completed. - /// Try again! - Retry, - - /// Value may have changed in the given revision. - ChangedAt(Revision), - - /// There is a stale cache entry that has not been - /// verified in this revision, so we can't say. - Stale(G), -} - -impl Slot -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self { - Self { - key_index: database_key_index.key_index, - group_index: database_key_index.group_index, - state: RwLock::new(QueryState::NotComputed), - lru_index: LruIndex::default(), - policy: PhantomData, - } - } - - pub(super) fn database_key_index(&self) -> DatabaseKeyIndex { - DatabaseKeyIndex { - group_index: self.group_index, - query_index: Q::QUERY_INDEX, - key_index: self.key_index, - } - } - - pub(super) fn read( - &self, - db: &>::DynDb, - key: &Q::Key, - ) -> StampedValue { - let runtime = db.salsa_runtime(); - - // NB: We don't need to worry about people modifying the - // revision out from under our feet. Either `db` is a frozen - // database, in which case there is a lock, or the mutator - // thread is the current thread, and it will be prevented from - // doing any `set` invocations while the query function runs. - let revision_now = runtime.current_revision(); - - info!("{:?}: invoked at {:?}", self, revision_now,); - - // First, do a check with a read-lock. - loop { - match self.probe(db, self.state.read(), runtime, revision_now) { - ProbeState::UpToDate(v) => return v, - ProbeState::Stale(..) | ProbeState::NoValue(..) | ProbeState::NotComputed(..) => { - break - } - ProbeState::Retry => continue, - } - } - - self.read_upgrade(db, key, revision_now) - } - - /// Second phase of a read operation: acquires an upgradable-read - /// and -- if needed -- validates whether inputs have changed, - /// recomputes value, etc. This is invoked after our initial probe - /// shows a potentially out of date value. - fn read_upgrade( - &self, - db: &>::DynDb, - key: &Q::Key, - revision_now: Revision, - ) -> StampedValue { - let runtime = db.salsa_runtime(); - - debug!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,); - - // Check with an upgradable read to see if there is a value - // already. (This permits other readers but prevents anyone - // else from running `read_upgrade` at the same time.) - let mut old_memo = loop { - match self.probe(db, self.state.upgradable_read(), runtime, revision_now) { - ProbeState::UpToDate(v) => return v, - ProbeState::Stale(state) - | ProbeState::NotComputed(state) - | ProbeState::NoValue(state, _) => { - type RwLockUpgradableReadGuard<'a, T> = - lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>; - - let mut state = RwLockUpgradableReadGuard::upgrade(state); - match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) { - QueryState::Memoized(old_memo) => break Some(old_memo), - QueryState::InProgress { .. } => unreachable!(), - QueryState::NotComputed => break None, - } - } - ProbeState::Retry => continue, - } - }; - - let panic_guard = PanicGuard::new(self, runtime); - let active_query = runtime.push_query(self.database_key_index()); - - // If we have an old-value, it *may* now be stale, since there - // has been a new revision since the last time we checked. So, - // first things first, let's walk over each of our previous - // inputs and check whether they are out of date. - if let Some(memo) = &mut old_memo { - if let Some(value) = memo.verify_value(db.ops_database(), revision_now, &active_query) { - info!("{:?}: validated old memoized value", self,); - - db.salsa_event(Event { - runtime_id: runtime.id(), - kind: EventKind::DidValidateMemoizedValue { - database_key: self.database_key_index(), - }, - }); - - panic_guard.proceed(old_memo); - - return value; - } - } - - self.execute(db, runtime, revision_now, active_query, panic_guard, old_memo, key) - } - - fn execute( - &self, - db: &>::DynDb, - runtime: &Runtime, - revision_now: Revision, - active_query: ActiveQueryGuard<'_>, - panic_guard: PanicGuard<'_, Q, MP>, - old_memo: Option>, - key: &Q::Key, - ) -> StampedValue { - tracing::info!("{:?}: executing query", self.database_key_index().debug(db)); - - db.salsa_event(Event { - runtime_id: db.salsa_runtime().id(), - kind: EventKind::WillExecute { database_key: self.database_key_index() }, - }); - - // Query was not previously executed, or value is potentially - // stale, or value is absent. Let's execute! - let value = match Cycle::catch(|| Q::execute(db, key.clone())) { - Ok(v) => v, - Err(cycle) => { - tracing::debug!( - "{:?}: caught cycle {:?}, have strategy {:?}", - self.database_key_index().debug(db), - cycle, - Q::CYCLE_STRATEGY, - ); - match Q::CYCLE_STRATEGY { - crate::plumbing::CycleRecoveryStrategy::Panic => { - panic_guard.proceed(None); - cycle.throw() - } - crate::plumbing::CycleRecoveryStrategy::Fallback => { - if let Some(c) = active_query.take_cycle() { - assert!(c.is(&cycle)); - Q::cycle_fallback(db, &cycle, key) - } else { - // we are not a participant in this cycle - debug_assert!(!cycle - .participant_keys() - .any(|k| k == self.database_key_index())); - cycle.throw() - } - } - } - } - }; - - let mut revisions = active_query.pop(); - - // We assume that query is side-effect free -- that is, does - // not mutate the "inputs" to the query system. Sanity check - // that assumption here, at least to the best of our ability. - assert_eq!( - runtime.current_revision(), - revision_now, - "revision altered during query execution", - ); - - // If the new value is equal to the old one, then it didn't - // really change, even if some of its inputs have. So we can - // "backdate" its `changed_at` revision to be the same as the - // old value. - if let Some(old_memo) = &old_memo { - if let Some(old_value) = &old_memo.value { - // Careful: if the value became less durable than it - // used to be, that is a "breaking change" that our - // consumers must be aware of. Becoming *more* durable - // is not. See the test `constant_to_non_constant`. - if revisions.durability >= old_memo.revisions.durability - && MP::memoized_value_eq(old_value, &value) - { - debug!( - "read_upgrade({:?}): value is equal, back-dating to {:?}", - self, old_memo.revisions.changed_at, - ); - - assert!(old_memo.revisions.changed_at <= revisions.changed_at); - revisions.changed_at = old_memo.revisions.changed_at; - } - } - } - - let new_value = StampedValue { - value, - durability: revisions.durability, - changed_at: revisions.changed_at, - }; - - let memo_value = - if self.should_memoize_value(key) { Some(new_value.value.clone()) } else { None }; - - debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,); - - panic_guard.proceed(Some(Memo { value: memo_value, verified_at: revision_now, revisions })); - - new_value - } - - /// Helper for `read` that does a shallow check (not recursive) if we have an up-to-date value. - /// - /// Invoked with the guard `state` corresponding to the `QueryState` of some `Slot` (the guard - /// can be either read or write). Returns a suitable `ProbeState`: - /// - /// - `ProbeState::UpToDate(r)` if the table has an up-to-date value (or we blocked on another - /// thread that produced such a value). - /// - `ProbeState::StaleOrAbsent(g)` if either (a) there is no memo for this key, (b) the memo - /// has no value; or (c) the memo has not been verified at the current revision. - /// - /// Note that in case `ProbeState::UpToDate`, the lock will have been released. - fn probe( - &self, - db: &>::DynDb, - state: StateGuard, - runtime: &Runtime, - revision_now: Revision, - ) -> ProbeState, StateGuard> - where - StateGuard: Deref>, - { - match &*state { - QueryState::NotComputed => ProbeState::NotComputed(state), - - QueryState::InProgress { id, anyone_waiting } => { - let other_id = *id; - - // NB: `Ordering::Relaxed` is sufficient here, - // as there are no loads that are "gated" on this - // value. Everything that is written is also protected - // by a lock that must be acquired. The role of this - // boolean is to decide *whether* to acquire the lock, - // not to gate future atomic reads. - anyone_waiting.store(true, Ordering::Relaxed); - - self.block_on_or_unwind(db, runtime, other_id, state); - - // Other thread completely normally, so our value may be available now. - ProbeState::Retry - } - - QueryState::Memoized(memo) => { - debug!( - "{:?}: found memoized value, verified_at={:?}, changed_at={:?}", - self, memo.verified_at, memo.revisions.changed_at, - ); - - if memo.verified_at < revision_now { - return ProbeState::Stale(state); - } - - if let Some(value) = &memo.value { - let value = StampedValue { - durability: memo.revisions.durability, - changed_at: memo.revisions.changed_at, - value: value.clone(), - }; - - info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at); - - ProbeState::UpToDate(value) - } else { - let changed_at = memo.revisions.changed_at; - ProbeState::NoValue(state, changed_at) - } - } - } - } - - pub(super) fn durability(&self, db: &>::DynDb) -> Durability { - match &*self.state.read() { - QueryState::NotComputed => Durability::LOW, - QueryState::InProgress { .. } => panic!("query in progress"), - QueryState::Memoized(memo) => { - if memo.check_durability(db.salsa_runtime()) { - memo.revisions.durability - } else { - Durability::LOW - } - } - } - } - - pub(super) fn as_table_entry(&self, key: &Q::Key) -> Option> { - match &*self.state.read() { - QueryState::NotComputed => None, - QueryState::InProgress { .. } => Some(TableEntry::new(key.clone(), None)), - QueryState::Memoized(memo) => Some(TableEntry::new(key.clone(), memo.value.clone())), - } - } - - pub(super) fn evict(&self) { - let mut state = self.state.write(); - if let QueryState::Memoized(memo) = &mut *state { - // Evicting a value with an untracked input could - // lead to inconsistencies. Note that we can't check - // `has_untracked_input` when we add the value to the cache, - // because inputs can become untracked in the next revision. - if memo.has_untracked_input() { - return; - } - memo.value = None; - } - } - - pub(super) fn invalidate(&self, new_revision: Revision) -> Option { - tracing::debug!("Slot::invalidate(new_revision = {:?})", new_revision); - match &mut *self.state.write() { - QueryState::Memoized(memo) => { - memo.revisions.untracked = true; - memo.revisions.inputs = None; - memo.revisions.changed_at = new_revision; - Some(memo.revisions.durability) - } - QueryState::NotComputed => None, - QueryState::InProgress { .. } => unreachable!(), - } - } - - pub(super) fn maybe_changed_after( - &self, - db: &>::DynDb, - revision: Revision, - key: &Q::Key, - ) -> bool { - let runtime = db.salsa_runtime(); - let revision_now = runtime.current_revision(); - - db.unwind_if_cancelled(); - - debug!( - "maybe_changed_after({:?}) called with revision={:?}, revision_now={:?}", - self, revision, revision_now, - ); - - // Do an initial probe with just the read-lock. - // - // If we find that a cache entry for the value is present - // but hasn't been verified in this revision, we'll have to - // do more. - loop { - match self.maybe_changed_after_probe(db, self.state.read(), runtime, revision_now) { - MaybeChangedSinceProbeState::Retry => continue, - MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision, - MaybeChangedSinceProbeState::Stale(state) => { - drop(state); - return self.maybe_changed_after_upgrade(db, revision, key); - } - } - } - } - - fn maybe_changed_after_probe( - &self, - db: &>::DynDb, - state: StateGuard, - runtime: &Runtime, - revision_now: Revision, - ) -> MaybeChangedSinceProbeState - where - StateGuard: Deref>, - { - match self.probe(db, state, runtime, revision_now) { - ProbeState::Retry => MaybeChangedSinceProbeState::Retry, - - ProbeState::Stale(state) => MaybeChangedSinceProbeState::Stale(state), - - // If we know when value last changed, we can return right away. - // Note that we don't need the actual value to be available. - ProbeState::NoValue(_, changed_at) - | ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => { - MaybeChangedSinceProbeState::ChangedAt(changed_at) - } - - // If we have nothing cached, then value may have changed. - ProbeState::NotComputed(_) => MaybeChangedSinceProbeState::ChangedAt(revision_now), - } - } - - fn maybe_changed_after_upgrade( - &self, - db: &>::DynDb, - revision: Revision, - key: &Q::Key, - ) -> bool { - let runtime = db.salsa_runtime(); - let revision_now = runtime.current_revision(); - - // Get an upgradable read lock, which permits other reads but no writers. - // Probe again. If the value is stale (needs to be verified), then upgrade - // to a write lock and swap it with InProgress while we work. - let mut old_memo = match self.maybe_changed_after_probe( - db, - self.state.upgradable_read(), - runtime, - revision_now, - ) { - MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision, - - // If another thread was active, then the cache line is going to be - // either verified or cleared out. Just recurse to figure out which. - // Note that we don't need an upgradable read. - MaybeChangedSinceProbeState::Retry => { - return self.maybe_changed_after(db, revision, key) - } - - MaybeChangedSinceProbeState::Stale(state) => { - type RwLockUpgradableReadGuard<'a, T> = - lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>; - - let mut state = RwLockUpgradableReadGuard::upgrade(state); - match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) { - QueryState::Memoized(old_memo) => old_memo, - QueryState::NotComputed | QueryState::InProgress { .. } => unreachable!(), - } - } - }; - - let panic_guard = PanicGuard::new(self, runtime); - let active_query = runtime.push_query(self.database_key_index()); - - if old_memo.verify_revisions(db.ops_database(), revision_now, &active_query) { - let maybe_changed = old_memo.revisions.changed_at > revision; - panic_guard.proceed(Some(old_memo)); - maybe_changed - } else if old_memo.value.is_some() { - // We found that this memoized value may have changed - // but we have an old value. We can re-run the code and - // actually *check* if it has changed. - let StampedValue { changed_at, .. } = self.execute( - db, - runtime, - revision_now, - active_query, - panic_guard, - Some(old_memo), - key, - ); - changed_at > revision - } else { - // We found that inputs to this memoized value may have chanced - // but we don't have an old value to compare against or re-use. - // No choice but to drop the memo and say that its value may have changed. - panic_guard.proceed(None); - true - } - } - - /// Helper: see [`Runtime::try_block_on_or_unwind`]. - fn block_on_or_unwind( - &self, - db: &>::DynDb, - runtime: &Runtime, - other_id: RuntimeId, - mutex_guard: MutexGuard, - ) { - runtime.block_on_or_unwind( - db.ops_database(), - self.database_key_index(), - other_id, - mutex_guard, - ) - } - - fn should_memoize_value(&self, key: &Q::Key) -> bool { - MP::should_memoize_value(key) - } -} - -impl QueryState -where - Q: QueryFunction, -{ - fn in_progress(id: RuntimeId) -> Self { - QueryState::InProgress { id, anyone_waiting: Default::default() } - } -} - -struct PanicGuard<'me, Q, MP> -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - slot: &'me Slot, - runtime: &'me Runtime, -} - -impl<'me, Q, MP> PanicGuard<'me, Q, MP> -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - fn new(slot: &'me Slot, runtime: &'me Runtime) -> Self { - Self { slot, runtime } - } - - /// Indicates that we have concluded normally (without panicking). - /// If `opt_memo` is some, then this memo is installed as the new - /// memoized value. If `opt_memo` is `None`, then the slot is cleared - /// and has no value. - fn proceed(mut self, opt_memo: Option>) { - self.overwrite_placeholder(WaitResult::Completed, opt_memo); - std::mem::forget(self) - } - - /// Overwrites the `InProgress` placeholder for `key` that we - /// inserted; if others were blocked, waiting for us to finish, - /// then notify them. - fn overwrite_placeholder(&mut self, wait_result: WaitResult, opt_memo: Option>) { - let old_value = { - let mut write = self.slot.state.write(); - match opt_memo { - // Replace the `InProgress` marker that we installed with the new - // memo, thus releasing our unique access to this key. - Some(memo) => std::mem::replace(&mut *write, QueryState::Memoized(memo)), - - // We had installed an `InProgress` marker, but we panicked before - // it could be removed. At this point, we therefore "own" unique - // access to our slot, so we can just remove the key. - None => std::mem::replace(&mut *write, QueryState::NotComputed), - } - }; - - match old_value { - QueryState::InProgress { id, anyone_waiting } => { - assert_eq!(id, self.runtime.id()); - - // NB: As noted on the `store`, `Ordering::Relaxed` is - // sufficient here. This boolean signals us on whether to - // acquire a mutex; the mutex will guarantee that all writes - // we are interested in are visible. - if anyone_waiting.load(Ordering::Relaxed) { - self.runtime - .unblock_queries_blocked_on(self.slot.database_key_index(), wait_result); - } - } - _ => panic!( - "\ -Unexpected panic during query evaluation, aborting the process. - -Please report this bug to https://github.com/salsa-rs/salsa/issues." - ), - } - } -} - -impl<'me, Q, MP> Drop for PanicGuard<'me, Q, MP> -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - fn drop(&mut self) { - if std::thread::panicking() { - // We panicked before we could proceed and need to remove `key`. - self.overwrite_placeholder(WaitResult::Panicked, None) - } else { - // If no panic occurred, then panic guard ought to be - // "forgotten" and so this Drop code should never run. - panic!(".forget() was not called") - } - } -} - -impl Memo -where - V: Clone, -{ - /// Determines whether the value stored in this memo (if any) is still - /// valid in the current revision. If so, returns a stamped value. - /// - /// If needed, this will walk each dependency and - /// recursively invoke `maybe_changed_after`, which may in turn - /// re-execute the dependency. This can cause cycles to occur, - /// so the current query must be pushed onto the - /// stack to permit cycle detection and recovery: therefore, - /// takes the `active_query` argument as evidence. - fn verify_value( - &mut self, - db: &dyn Database, - revision_now: Revision, - active_query: &ActiveQueryGuard<'_>, - ) -> Option> { - // If we don't have a memoized value, nothing to validate. - if self.value.is_none() { - return None; - } - if self.verify_revisions(db, revision_now, active_query) { - self.value.clone().map(|value| StampedValue { - durability: self.revisions.durability, - changed_at: self.revisions.changed_at, - value, - }) - } else { - None - } - } - - /// Determines whether the value represented by this memo is still - /// valid in the current revision; note that the value itself is - /// not needed for this check. If needed, this will walk each - /// dependency and recursively invoke `maybe_changed_after`, which - /// may in turn re-execute the dependency. This can cause cycles to occur, - /// so the current query must be pushed onto the - /// stack to permit cycle detection and recovery: therefore, - /// takes the `active_query` argument as evidence. - fn verify_revisions( - &mut self, - db: &dyn Database, - revision_now: Revision, - _active_query: &ActiveQueryGuard<'_>, - ) -> bool { - assert!(self.verified_at != revision_now); - let verified_at = self.verified_at; - - debug!( - "verify_revisions: verified_at={:?}, revision_now={:?}, inputs={:#?}", - verified_at, revision_now, self.revisions.inputs - ); - - if self.check_durability(db.salsa_runtime()) { - return self.mark_value_as_verified(revision_now); - } - - match &self.revisions.inputs { - // We can't validate values that had untracked inputs; just have to - // re-execute. - None if self.revisions.untracked => return false, - None => {} - - // Check whether any of our inputs changed since the - // **last point where we were verified** (not since we - // last changed). This is important: if we have - // memoized values, then an input may have changed in - // revision R2, but we found that *our* value was the - // same regardless, so our change date is still - // R1. But our *verification* date will be R2, and we - // are only interested in finding out whether the - // input changed *again*. - Some(inputs) => { - let changed_input = - inputs.slice.iter().find(|&&input| db.maybe_changed_after(input, verified_at)); - if let Some(input) = changed_input { - debug!("validate_memoized_value: `{:?}` may have changed", input); - - return false; - } - } - }; - - self.mark_value_as_verified(revision_now) - } - - /// True if this memo is known not to have changed based on its durability. - fn check_durability(&self, runtime: &Runtime) -> bool { - let last_changed = runtime.last_changed_revision(self.revisions.durability); - debug!( - "check_durability(last_changed={:?} <= verified_at={:?}) = {:?}", - last_changed, - self.verified_at, - last_changed <= self.verified_at, - ); - last_changed <= self.verified_at - } - - fn mark_value_as_verified(&mut self, revision_now: Revision) -> bool { - self.verified_at = revision_now; - true - } - - fn has_untracked_input(&self) -> bool { - self.revisions.untracked - } -} - -impl std::fmt::Debug for Slot -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(fmt, "{:?}", Q::default()) - } -} - -impl LruNode for Slot -where - Q: QueryFunction, - MP: MemoizationPolicy, -{ - fn lru_index(&self) -> &LruIndex { - &self.lru_index - } -} - -/// Check that `Slot: Send + Sync` as long as -/// `DB::DatabaseData: Send + Sync`, which in turn implies that -/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. -#[allow(dead_code)] -fn check_send_sync() -where - Q: QueryFunction, - MP: MemoizationPolicy, - Q::Key: Send + Sync, - Q::Value: Send + Sync, -{ - fn is_send_sync() {} - is_send_sync::>(); -} - -/// Check that `Slot: 'static` as long as -/// `DB::DatabaseData: 'static`, which in turn implies that -/// `Q::Key: 'static`, `Q::Value: 'static`. -#[allow(dead_code)] -fn check_static() -where - Q: QueryFunction + 'static, - MP: MemoizationPolicy + 'static, - Q::Key: 'static, - Q::Value: 'static, -{ - fn is_static() {} - is_static::>(); -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/durability.rs b/src/tools/rust-analyzer/crates/salsa/src/durability.rs deleted file mode 100644 index 7b8e6840fc9..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/durability.rs +++ /dev/null @@ -1,49 +0,0 @@ -/// Describes how likely a value is to change -- how "durable" it is. -/// By default, inputs have `Durability::LOW` and interned values have -/// `Durability::HIGH`. But inputs can be explicitly set with other -/// durabilities. -/// -/// We use durabilities to optimize the work of "revalidating" a query -/// after some input has changed. Ordinarily, in a new revision, -/// queries have to trace all their inputs back to the base inputs to -/// determine if any of those inputs have changed. But if we know that -/// the only changes were to inputs of low durability (the common -/// case), and we know that the query only used inputs of medium -/// durability or higher, then we can skip that enumeration. -/// -/// Typically, one assigns low durabilites to inputs that the user is -/// frequently editing. Medium or high durabilities are used for -/// configuration, the source from library crates, or other things -/// that are unlikely to be edited. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Durability(u8); - -impl Durability { - /// Low durability: things that change frequently. - /// - /// Example: part of the crate being edited - pub const LOW: Durability = Durability(0); - - /// Medium durability: things that change sometimes, but rarely. - /// - /// Example: a Cargo.toml file - pub const MEDIUM: Durability = Durability(1); - - /// High durability: things that are not expected to change under - /// common usage. - /// - /// Example: the standard library or something from crates.io - pub const HIGH: Durability = Durability(2); - - /// The maximum possible durability; equivalent to HIGH but - /// "conceptually" distinct (i.e., if we add more durability - /// levels, this could change). - pub(crate) const MAX: Durability = Self::HIGH; - - /// Number of durability levels. - pub(crate) const LEN: usize = Self::MAX.index() + 1; - - pub(crate) const fn index(self) -> usize { - self.0 as usize - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/hash.rs b/src/tools/rust-analyzer/crates/salsa/src/hash.rs deleted file mode 100644 index 3b2d7df3fbe..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/hash.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub(crate) type FxHasher = std::hash::BuildHasherDefault; -pub(crate) type FxIndexSet = indexmap::IndexSet; -pub(crate) type FxIndexMap = indexmap::IndexMap; diff --git a/src/tools/rust-analyzer/crates/salsa/src/input.rs b/src/tools/rust-analyzer/crates/salsa/src/input.rs deleted file mode 100644 index f04f48e3bab..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/input.rs +++ /dev/null @@ -1,371 +0,0 @@ -use crate::debug::TableEntry; -use crate::durability::Durability; -use crate::hash::FxIndexMap; -use crate::plumbing::CycleRecoveryStrategy; -use crate::plumbing::InputQueryStorageOps; -use crate::plumbing::QueryStorageMassOps; -use crate::plumbing::QueryStorageOps; -use crate::revision::Revision; -use crate::runtime::StampedValue; -use crate::Database; -use crate::Query; -use crate::Runtime; -use crate::{DatabaseKeyIndex, QueryDb}; -use indexmap::map::Entry; -use parking_lot::RwLock; -use std::iter; -use tracing::debug; - -/// Input queries store the result plus a list of the other queries -/// that they invoked. This means we can avoid recomputing them when -/// none of those inputs have changed. -pub struct InputStorage -where - Q: Query, -{ - group_index: u16, - slots: RwLock>>, -} - -struct Slot { - key_index: u32, - stamped_value: RwLock>, -} - -impl std::panic::RefUnwindSafe for InputStorage -where - Q: Query, - Q::Key: std::panic::RefUnwindSafe, - Q::Value: std::panic::RefUnwindSafe, -{ -} - -impl QueryStorageOps for InputStorage -where - Q: Query, -{ - const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; - - fn new(group_index: u16) -> Self { - InputStorage { group_index, slots: Default::default() } - } - - fn fmt_index( - &self, - _db: &>::DynDb, - index: u32, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - let slot_map = self.slots.read(); - let key = slot_map.get_index(index as usize).unwrap().0; - write!(fmt, "{}({:?})", Q::QUERY_NAME, key) - } - - fn maybe_changed_after( - &self, - db: &>::DynDb, - index: u32, - revision: Revision, - ) -> bool { - debug_assert!(revision < db.salsa_runtime().current_revision()); - let slots = &self.slots.read(); - let Some((_, slot)) = slots.get_index(index as usize) else { - return true; - }; - - debug!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,); - - let changed_at = slot.stamped_value.read().changed_at; - - debug!("maybe_changed_after: changed_at = {:?}", changed_at); - - changed_at > revision - } - - fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { - db.unwind_if_cancelled(); - - let slots = &self.slots.read(); - let slot = slots - .get(key) - .unwrap_or_else(|| panic!("no value set for {:?}({:?})", Q::default(), key)); - - let StampedValue { value, durability, changed_at } = slot.stamped_value.read().clone(); - - db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( - DatabaseKeyIndex { - group_index: self.group_index, - query_index: Q::QUERY_INDEX, - key_index: slot.key_index, - }, - durability, - changed_at, - ); - - value - } - - fn durability(&self, _db: &>::DynDb, key: &Q::Key) -> Durability { - match self.slots.read().get(key) { - Some(slot) => slot.stamped_value.read().durability, - None => panic!("no value set for {:?}({:?})", Q::default(), key), - } - } - - fn entries(&self, _db: &>::DynDb) -> C - where - C: std::iter::FromIterator>, - { - let slots = self.slots.read(); - slots - .iter() - .map(|(key, slot)| { - TableEntry::new(key.clone(), Some(slot.stamped_value.read().value.clone())) - }) - .collect() - } -} - -impl QueryStorageMassOps for InputStorage -where - Q: Query, -{ - fn purge(&self) { - *self.slots.write() = Default::default(); - } -} - -impl InputQueryStorageOps for InputStorage -where - Q: Query, -{ - fn set(&self, runtime: &mut Runtime, key: &Q::Key, value: Q::Value, durability: Durability) { - tracing::debug!("{:?}({:?}) = {:?} ({:?})", Q::default(), key, value, durability); - - // The value is changing, so we need a new revision (*). We also - // need to update the 'last changed' revision by invoking - // `guard.mark_durability_as_changed`. - // - // CAREFUL: This will block until the global revision lock can - // be acquired. If there are still queries executing, they may - // need to read from this input. Therefore, we wait to acquire - // the lock on `map` until we also hold the global query write - // lock. - // - // (*) Technically, since you can't presently access an input - // for a non-existent key, and you can't enumerate the set of - // keys, we only need a new revision if the key used to - // exist. But we may add such methods in the future and this - // case doesn't generally seem worth optimizing for. - runtime.with_incremented_revision(|next_revision| { - let mut slots = self.slots.write(); - - // Do this *after* we acquire the lock, so that we are not - // racing with somebody else to modify this same cell. - // (Otherwise, someone else might write a *newer* revision - // into the same cell while we block on the lock.) - let stamped_value = StampedValue { value, durability, changed_at: next_revision }; - - match slots.entry(key.clone()) { - Entry::Occupied(entry) => { - let mut slot_stamped_value = entry.get().stamped_value.write(); - let old_durability = slot_stamped_value.durability; - *slot_stamped_value = stamped_value; - Some(old_durability) - } - - Entry::Vacant(entry) => { - let key_index = entry.index() as u32; - entry.insert(Slot { key_index, stamped_value: RwLock::new(stamped_value) }); - None - } - } - }); - } -} - -/// Same as `InputStorage`, but optimized for queries that take no inputs. -pub struct UnitInputStorage -where - Q: Query, -{ - slot: UnitSlot, -} - -struct UnitSlot { - database_key_index: DatabaseKeyIndex, - stamped_value: RwLock>>, -} - -impl std::panic::RefUnwindSafe for UnitInputStorage -where - Q: Query, - Q::Key: std::panic::RefUnwindSafe, - Q::Value: std::panic::RefUnwindSafe, -{ -} - -impl QueryStorageOps for UnitInputStorage -where - Q: Query, -{ - const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; - - fn new(group_index: u16) -> Self { - let database_key_index = - DatabaseKeyIndex { group_index, query_index: Q::QUERY_INDEX, key_index: 0 }; - UnitInputStorage { slot: UnitSlot { database_key_index, stamped_value: RwLock::new(None) } } - } - - fn fmt_index( - &self, - _db: &>::DynDb, - _index: u32, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - write!(fmt, "{}", Q::QUERY_NAME) - } - - fn maybe_changed_after( - &self, - db: &>::DynDb, - _index: u32, - revision: Revision, - ) -> bool { - debug_assert!(revision < db.salsa_runtime().current_revision()); - - debug!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,); - - let Some(value) = &*self.slot.stamped_value.read() else { - return true; - }; - let changed_at = value.changed_at; - - debug!("maybe_changed_after: changed_at = {:?}", changed_at); - - changed_at > revision - } - - fn fetch(&self, db: &>::DynDb, &(): &Q::Key) -> Q::Value { - db.unwind_if_cancelled(); - - let StampedValue { value, durability, changed_at } = self - .slot - .stamped_value - .read() - .clone() - .unwrap_or_else(|| panic!("no value set for {:?}", Q::default())); - - db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( - self.slot.database_key_index, - durability, - changed_at, - ); - - value - } - - fn durability(&self, _db: &>::DynDb, &(): &Q::Key) -> Durability { - match &*self.slot.stamped_value.read() { - Some(stamped_value) => stamped_value.durability, - None => panic!("no value set for {:?}", Q::default(),), - } - } - - fn entries(&self, _db: &>::DynDb) -> C - where - C: std::iter::FromIterator>, - { - iter::once(TableEntry::new( - (), - self.slot.stamped_value.read().as_ref().map(|it| it.value.clone()), - )) - .collect() - } -} - -impl QueryStorageMassOps for UnitInputStorage -where - Q: Query, -{ - fn purge(&self) { - *self.slot.stamped_value.write() = Default::default(); - } -} - -impl InputQueryStorageOps for UnitInputStorage -where - Q: Query, -{ - fn set(&self, runtime: &mut Runtime, (): &Q::Key, value: Q::Value, durability: Durability) { - tracing::debug!("{:?} = {:?} ({:?})", Q::default(), value, durability); - - // The value is changing, so we need a new revision (*). We also - // need to update the 'last changed' revision by invoking - // `guard.mark_durability_as_changed`. - // - // CAREFUL: This will block until the global revision lock can - // be acquired. If there are still queries executing, they may - // need to read from this input. Therefore, we wait to acquire - // the lock on `map` until we also hold the global query write - // lock. - // - // (*) Technically, since you can't presently access an input - // for a non-existent key, and you can't enumerate the set of - // keys, we only need a new revision if the key used to - // exist. But we may add such methods in the future and this - // case doesn't generally seem worth optimizing for. - runtime.with_incremented_revision(|next_revision| { - let mut stamped_value_slot = self.slot.stamped_value.write(); - - // Do this *after* we acquire the lock, so that we are not - // racing with somebody else to modify this same cell. - // (Otherwise, someone else might write a *newer* revision - // into the same cell while we block on the lock.) - let stamped_value = StampedValue { value, durability, changed_at: next_revision }; - - match &mut *stamped_value_slot { - Some(slot_stamped_value) => { - let old_durability = slot_stamped_value.durability; - *slot_stamped_value = stamped_value; - Some(old_durability) - } - - stamped_value_slot @ None => { - *stamped_value_slot = Some(stamped_value); - None - } - } - }); - } -} - -/// Check that `Slot: Send + Sync` as long as -/// `DB::DatabaseData: Send + Sync`, which in turn implies that -/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. -#[allow(dead_code)] -fn check_send_sync() -where - Q: Query, - Q::Key: Send + Sync, - Q::Value: Send + Sync, -{ - fn is_send_sync() {} - is_send_sync::>(); - is_send_sync::>(); -} - -/// Check that `Slot: 'static` as long as -/// `DB::DatabaseData: 'static`, which in turn implies that -/// `Q::Key: 'static`, `Q::Value: 'static`. -#[allow(dead_code)] -fn check_static() -where - Q: Query + 'static, - Q::Key: 'static, - Q::Value: 'static, -{ - fn is_static() {} - is_static::>(); - is_static::>(); -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/intern_id.rs b/src/tools/rust-analyzer/crates/salsa/src/intern_id.rs deleted file mode 100644 index 8e74c100aca..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/intern_id.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::fmt; -use std::num::NonZeroU32; - -/// The "raw-id" is used for interned keys in salsa -- it is basically -/// a newtype'd u32. Typically, it is wrapped in a type of your own -/// devising. For more information about interned keys, see [the -/// interned key RFC][rfc]. -/// -/// # Creating a `InternId` -// -/// InternId values can be constructed using the `From` impls, -/// which are implemented for `u32` and `usize`: -/// -/// ``` -/// # use salsa::InternId; -/// let intern_id1 = InternId::from(22_u32); -/// let intern_id2 = InternId::from(22_usize); -/// assert_eq!(intern_id1, intern_id2); -/// ``` -/// -/// # Converting to a u32 or usize -/// -/// Normally, there should be no need to access the underlying integer -/// in a `InternId`. But if you do need to do so, you can convert to a -/// `usize` using the `as_u32` or `as_usize` methods or the `From` impls. -/// -/// ``` -/// # use salsa::InternId; -/// let intern_id = InternId::from(22_u32); -/// let value = u32::from(intern_id); -/// assert_eq!(value, 22); -/// ``` -/// -/// ## Illegal values -/// -/// Be warned, however, that `InternId` values cannot be created from -/// *arbitrary* values -- in particular large values greater than -/// `InternId::MAX` will panic. Those large values are reserved so that -/// the Rust compiler can use them as sentinel values, which means -/// that (for example) `Option` is represented in a single -/// word. -/// -/// ```should_panic -/// # use salsa::InternId; -/// InternId::from(InternId::MAX); -/// ``` -/// -/// [rfc]: https://github.com/salsa-rs/salsa-rfcs/pull/2 -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InternId { - value: NonZeroU32, -} - -impl InternId { - /// The maximum allowed `InternId`. This value can grow between - /// releases without affecting semver. - pub const MAX: u32 = 0xFFFF_FF00; - - /// Creates a new InternId. - /// - /// # Safety - /// - /// `value` must be less than `MAX` - pub const unsafe fn new_unchecked(value: u32) -> Self { - debug_assert!(value < InternId::MAX); - let value = unsafe { NonZeroU32::new_unchecked(value + 1) }; - InternId { value } - } - - /// Convert this raw-id into a u32 value. - /// - /// ``` - /// # use salsa::InternId; - /// let intern_id = InternId::from(22_u32); - /// let value = intern_id.as_usize(); - /// assert_eq!(value, 22); - /// ``` - pub fn as_u32(self) -> u32 { - self.value.get() - 1 - } - - /// Convert this raw-id into a usize value. - /// - /// ``` - /// # use salsa::InternId; - /// let intern_id = InternId::from(22_u32); - /// let value = intern_id.as_usize(); - /// assert_eq!(value, 22); - /// ``` - pub fn as_usize(self) -> usize { - self.as_u32() as usize - } -} - -impl From for u32 { - fn from(raw: InternId) -> u32 { - raw.as_u32() - } -} - -impl From for usize { - fn from(raw: InternId) -> usize { - raw.as_usize() - } -} - -impl From for InternId { - fn from(id: u32) -> InternId { - assert!(id < InternId::MAX); - unsafe { InternId::new_unchecked(id) } - } -} - -impl From for InternId { - fn from(id: usize) -> InternId { - assert!(id < (InternId::MAX as usize)); - unsafe { InternId::new_unchecked(id as u32) } - } -} - -impl fmt::Debug for InternId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_usize().fmt(f) - } -} - -impl fmt::Display for InternId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_usize().fmt(f) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/interned.rs b/src/tools/rust-analyzer/crates/salsa/src/interned.rs deleted file mode 100644 index 359662ec6b2..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/interned.rs +++ /dev/null @@ -1,510 +0,0 @@ -use crate::debug::TableEntry; -use crate::durability::Durability; -use crate::intern_id::InternId; -use crate::plumbing::CycleRecoveryStrategy; -use crate::plumbing::HasQueryGroup; -use crate::plumbing::QueryStorageMassOps; -use crate::plumbing::QueryStorageOps; -use crate::revision::Revision; -use crate::Query; -use crate::QueryTable; -use crate::{Database, DatabaseKeyIndex, QueryDb}; -use parking_lot::RwLock; -use rustc_hash::FxHashMap; -use std::collections::hash_map::Entry; -use std::fmt::Debug; -use std::hash::Hash; -use triomphe::Arc; - -const INTERN_DURABILITY: Durability = Durability::HIGH; - -/// Handles storage where the value is 'derived' by executing a -/// function (in contrast to "inputs"). -pub struct InternedStorage -where - Q: Query, - Q::Key: InternValue, - Q::Value: InternKey, -{ - group_index: u16, - tables: RwLock, Q::Key>>, -} - -/// Storage for the looking up interned things. -pub struct LookupInternedStorage -where - Q: Query, - Q::Key: InternKey, - Q::Value: InternValue, -{ - phantom: std::marker::PhantomData<(Q::Key, IQ)>, -} - -struct InternTables { - /// Map from the key to the corresponding intern-index. - map: FxHashMap, - - /// For each valid intern-index, stores the interned value. - values: Vec>>, -} - -/// Trait implemented for the "key" that results from a -/// `#[salsa::intern]` query. This is basically meant to be a -/// "newtype"'d `u32`. -pub trait InternKey { - /// Create an instance of the intern-key from a `u32` value. - fn from_intern_id(v: InternId) -> Self; - - /// Extract the `u32` with which the intern-key was created. - fn as_intern_id(&self) -> InternId; -} - -impl InternKey for InternId { - fn from_intern_id(v: InternId) -> InternId { - v - } - - fn as_intern_id(&self) -> InternId { - *self - } -} - -/// Trait implemented for the "value" that is being interned. -pub trait InternValue { - /// They key used to intern this value by. - type Key: Eq + Hash + Debug + Clone; - /// Maps the value to a key that will be used to intern it. - fn into_key(&self) -> Self::Key; - /// Calls the given function with the key that was used to intern this value. - /// - /// This is mainly used to prevent frequent cloning of the key when doing a lookup. - #[inline] - fn with_key T, T>(&self, f: F) -> T { - f(&self.into_key()) - } -} - -impl - InternValue for (A, B) -{ - type Key = Self; - #[inline] - fn into_key(&self) -> Self::Key { - self.clone() - } - #[inline] - fn with_key T, T>(&self, f: F) -> T { - f(self) - } -} - -pub trait InternValueTrivial -where - Self: Eq + Hash + Debug + Clone, -{ -} - -/// Implement [`InternValue`] trivially, that is without actually mapping at all. -impl InternValue for V { - type Key = Self; - #[inline] - fn into_key(&self) -> Self::Key { - self.clone() - } - #[inline] - fn with_key T, T>(&self, f: F) -> T { - f(self) - } -} - -impl InternValueTrivial for String {} - -#[derive(Debug)] -struct Slot { - /// key index for this slot. - key_index: u32, - - /// Value that was interned. - value: V, - - /// When was this intern'd? - /// - /// (This informs the "changed-at" result) - interned_at: Revision, -} - -impl std::panic::RefUnwindSafe for InternedStorage -where - Q: Query, - Q::Key: InternValue, - Q::Key: std::panic::RefUnwindSafe, - Q::Value: InternKey, - Q::Value: std::panic::RefUnwindSafe, -{ -} - -impl InternTables { - /// Returns the slot for the given key. - fn slot_for_key(&self, key: &K) -> Option<(Arc>, InternId)> { - let &index = self.map.get(key)?; - Some((self.slot_for_index(index), index)) - } - - /// Returns the slot at the given index. - fn slot_for_index(&self, index: InternId) -> Arc> { - let slot = &self.values[index.as_usize()]; - slot.clone() - } -} - -impl Default for InternTables -where - K: Eq + Hash, -{ - fn default() -> Self { - Self { map: Default::default(), values: Default::default() } - } -} - -type MappedKey = <::Key as InternValue>::Key; - -impl InternedStorage -where - Q: Query, - Q::Key: InternValue, - Q::Value: InternKey, -{ - /// Creates a new slot. - fn intern_index( - &self, - db: &>::DynDb, - mapped_key: MappedKey, - insert: impl FnOnce(Q::Value) -> Q::Key, - ) -> (Arc>, InternId) { - let revision_now = db.salsa_runtime().current_revision(); - - let mut tables = self.tables.write(); - let tables = &mut *tables; - let entry = match tables.map.entry(mapped_key) { - Entry::Vacant(entry) => entry, - Entry::Occupied(entry) => { - // Somebody inserted this key while we were waiting - // for the write lock. In this case, we don't need to - // update the `accessed_at` field because they should - // have already done so! - let index = *entry.get(); - let slot = &tables.values[index.as_usize()]; - return (slot.clone(), index); - } - }; - - let create_slot = |index: InternId| { - Arc::new(Slot { - key_index: index.as_u32(), - value: insert(Q::Value::from_intern_id(index)), - interned_at: revision_now, - }) - }; - - let index = InternId::from(tables.values.len()); - let slot = create_slot(index); - tables.values.push(slot.clone()); - entry.insert(index); - - (slot, index) - } - - fn intern_check(&self, key: &MappedKey) -> Option<(Arc>, InternId)> { - self.tables.read().slot_for_key(key) - } - - /// Given an index, lookup and clone its value, updating the - /// `accessed_at` time if necessary. - fn lookup_value(&self, index: InternId) -> Arc> { - self.tables.read().slot_for_index(index) - } - - fn fetch_or_insert( - &self, - db: &>::DynDb, - key: MappedKey, - insert: impl FnOnce(Q::Value) -> Q::Key, - ) -> Q::Value { - db.unwind_if_cancelled(); - let (slot, index) = match self.intern_check(&key) { - Some(i) => i, - None => self.intern_index(db, key, insert), - }; - let changed_at = slot.interned_at; - db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( - DatabaseKeyIndex { - group_index: self.group_index, - query_index: Q::QUERY_INDEX, - key_index: slot.key_index, - }, - INTERN_DURABILITY, - changed_at, - ); - ::from_intern_id(index) - } -} - -impl QueryStorageOps for InternedStorage -where - Q: Query, - Q::Key: InternValue, - Q::Value: InternKey, -{ - const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; - - fn new(group_index: u16) -> Self { - InternedStorage { group_index, tables: RwLock::new(InternTables::default()) } - } - - fn fmt_index( - &self, - _db: &>::DynDb, - index: u32, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - let intern_id = InternId::from(index); - let slot = self.lookup_value(intern_id); - write!(fmt, "{}({:?})", Q::QUERY_NAME, slot.value) - } - - fn maybe_changed_after( - &self, - db: &>::DynDb, - input: u32, - revision: Revision, - ) -> bool { - debug_assert!(revision < db.salsa_runtime().current_revision()); - let intern_id = InternId::from(input); - let slot = self.lookup_value(intern_id); - slot.maybe_changed_after(revision) - } - - fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { - db.unwind_if_cancelled(); - - let (slot, index) = match key.with_key(|key| self.intern_check(key)) { - Some(i) => i, - None => self.intern_index(db, key.into_key(), |_| key.clone()), - }; - let changed_at = slot.interned_at; - db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( - DatabaseKeyIndex { - group_index: self.group_index, - query_index: Q::QUERY_INDEX, - key_index: slot.key_index, - }, - INTERN_DURABILITY, - changed_at, - ); - ::from_intern_id(index) - } - - fn durability(&self, _db: &>::DynDb, _key: &Q::Key) -> Durability { - INTERN_DURABILITY - } - - fn entries(&self, _db: &>::DynDb) -> C - where - C: std::iter::FromIterator>, - { - let tables = self.tables.read(); - tables - .map - .values() - .map(|index| { - TableEntry::new( - tables.values[index.as_usize()].value.clone(), - Some(::from_intern_id(*index)), - ) - }) - .collect() - } -} - -impl QueryStorageMassOps for InternedStorage -where - Q: Query, - Q::Key: InternValue, - Q::Value: InternKey, -{ - fn purge(&self) { - *self.tables.write() = Default::default(); - } -} - -// Workaround for -// ``` -// IQ: for<'d> QueryDb< -// 'd, -// DynDb = >::DynDb, -// Group = >::Group, -// GroupStorage = >::GroupStorage, -// >, -// ``` -// not working to make rustc know DynDb, Group and GroupStorage being the same in `Q` and `IQ` -#[doc(hidden)] -pub trait EqualDynDb<'d, IQ>: QueryDb<'d> -where - IQ: QueryDb<'d>, -{ - fn convert_db(d: &Self::DynDb) -> &IQ::DynDb; - fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage; -} - -impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q -where - Q: QueryDb<'d, DynDb = IQ::DynDb, Group = IQ::Group, GroupStorage = IQ::GroupStorage>, - Q::DynDb: HasQueryGroup, - IQ: QueryDb<'d>, -{ - fn convert_db(d: &Self::DynDb) -> &IQ::DynDb { - d - } - fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage { - d - } -} - -impl QueryStorageOps for LookupInternedStorage -where - Q: Query, - Q::Key: InternKey, - Q::Value: InternValue, - IQ: Query>, - for<'d> Q: EqualDynDb<'d, IQ>, -{ - const CYCLE_STRATEGY: CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; - - fn new(_group_index: u16) -> Self { - LookupInternedStorage { phantom: std::marker::PhantomData } - } - - fn fmt_index( - &self, - db: &>::DynDb, - index: u32, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - let group_storage = - <>::DynDb as HasQueryGroup>::group_storage(db); - let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); - interned_storage.fmt_index(Q::convert_db(db), index, fmt) - } - - fn maybe_changed_after( - &self, - db: &>::DynDb, - input: u32, - revision: Revision, - ) -> bool { - let group_storage = - <>::DynDb as HasQueryGroup>::group_storage(db); - let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); - interned_storage.maybe_changed_after(Q::convert_db(db), input, revision) - } - - fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value { - let index = key.as_intern_id(); - let group_storage = - <>::DynDb as HasQueryGroup>::group_storage(db); - let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); - let slot = interned_storage.lookup_value(index); - let value = slot.value.clone(); - let interned_at = slot.interned_at; - db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( - DatabaseKeyIndex { - group_index: interned_storage.group_index, - query_index: Q::QUERY_INDEX, - key_index: slot.key_index, - }, - INTERN_DURABILITY, - interned_at, - ); - value - } - - fn durability(&self, _db: &>::DynDb, _key: &Q::Key) -> Durability { - INTERN_DURABILITY - } - - fn entries(&self, db: &>::DynDb) -> C - where - C: std::iter::FromIterator>, - { - let group_storage = - <>::DynDb as HasQueryGroup>::group_storage(db); - let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); - let tables = interned_storage.tables.read(); - tables - .map - .values() - .map(|index| { - TableEntry::new( - ::from_intern_id(*index), - Some(tables.values[index.as_usize()].value.clone()), - ) - }) - .collect() - } -} - -impl QueryStorageMassOps for LookupInternedStorage -where - Q: Query, - Q::Key: InternKey, - Q::Value: InternValue, - IQ: Query, -{ - fn purge(&self) {} -} - -impl Slot { - fn maybe_changed_after(&self, revision: Revision) -> bool { - self.interned_at > revision - } -} - -/// Check that `Slot: Send + Sync` as long as -/// `DB::DatabaseData: Send + Sync`, which in turn implies that -/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. -#[allow(dead_code)] -fn check_send_sync() -where - K: Send + Sync, -{ - fn is_send_sync() {} - is_send_sync::>(); -} - -/// Check that `Slot: 'static` as long as -/// `DB::DatabaseData: 'static`, which in turn implies that -/// `Q::Key: 'static`, `Q::Value: 'static`. -#[allow(dead_code)] -fn check_static() -where - K: 'static, -{ - fn is_static() {} - is_static::>(); -} - -impl<'me, Q> QueryTable<'me, Q> -where - Q: Query>, - Q::Key: InternValue, - Q::Value: InternKey, -{ - /// Fetches the intern id for the given key or inserts it if it does not exist. - pub fn get_or_insert( - &self, - key: MappedKey, - insert: impl FnOnce(Q::Value) -> Q::Key, - ) -> Q::Value { - self.storage.fetch_or_insert(self.db, key, insert) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/lib.rs b/src/tools/rust-analyzer/crates/salsa/src/lib.rs deleted file mode 100644 index 48d6dc2e387..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/lib.rs +++ /dev/null @@ -1,750 +0,0 @@ -#![allow(clippy::type_complexity)] -#![allow(clippy::question_mark)] -#![allow(missing_docs)] -#![warn(rust_2018_idioms)] - -//! The salsa crate is a crate for incremental recomputation. It -//! permits you to define a "database" of queries with both inputs and -//! values derived from those inputs; as you set the inputs, you can -//! re-execute the derived queries and it will try to re-use results -//! from previous invocations as appropriate. - -mod derived; -mod derived_lru; -mod durability; -mod hash; -mod input; -mod intern_id; -mod interned; -mod lru; -mod revision; -mod runtime; -mod storage; - -pub mod debug; -/// Items in this module are public for implementation reasons, -/// and are exempt from the SemVer guarantees. -#[doc(hidden)] -pub mod plumbing; - -use crate::plumbing::CycleRecoveryStrategy; -use crate::plumbing::DerivedQueryStorageOps; -use crate::plumbing::InputQueryStorageOps; -use crate::plumbing::LruQueryStorageOps; -use crate::plumbing::QueryStorageMassOps; -use crate::plumbing::QueryStorageOps; -pub use crate::revision::Revision; -use std::fmt::{self, Debug}; -use std::hash::Hash; -use std::panic::AssertUnwindSafe; -use std::panic::{self, UnwindSafe}; - -pub use crate::durability::Durability; -pub use crate::intern_id::InternId; -pub use crate::interned::{InternKey, InternValue, InternValueTrivial}; -pub use crate::runtime::Runtime; -pub use crate::runtime::RuntimeId; -pub use crate::storage::Storage; - -/// The base trait which your "query context" must implement. Gives -/// access to the salsa runtime, which you must embed into your query -/// context (along with whatever other state you may require). -pub trait Database: plumbing::DatabaseOps { - /// This function is invoked at key points in the salsa - /// runtime. It permits the database to be customized and to - /// inject logging or other custom behavior. - fn salsa_event(&self, event_fn: Event) { - _ = event_fn; - } - - /// Starts unwinding the stack if the current revision is cancelled. - /// - /// This method can be called by query implementations that perform - /// potentially expensive computations, in order to speed up propagation of - /// cancellation. - /// - /// Cancellation will automatically be triggered by salsa on any query - /// invocation. - /// - /// This method should not be overridden by `Database` implementors. A - /// `salsa_event` is emitted when this method is called, so that should be - /// used instead. - #[inline] - fn unwind_if_cancelled(&self) { - let runtime = self.salsa_runtime(); - self.salsa_event(Event { - runtime_id: runtime.id(), - kind: EventKind::WillCheckCancellation, - }); - - let current_revision = runtime.current_revision(); - let pending_revision = runtime.pending_revision(); - tracing::debug!( - "unwind_if_cancelled: current_revision={:?}, pending_revision={:?}", - current_revision, - pending_revision - ); - if pending_revision > current_revision { - runtime.unwind_cancelled(); - } - } - - /// Gives access to the underlying salsa runtime. - /// - /// This method should not be overridden by `Database` implementors. - fn salsa_runtime(&self) -> &Runtime { - self.ops_salsa_runtime() - } - - /// A "synthetic write" causes the system to act *as though* some - /// input of durability `durability` has changed. This is mostly - /// useful for profiling scenarios. - /// - /// **WARNING:** Just like an ordinary write, this method triggers - /// cancellation. If you invoke it while a snapshot exists, it - /// will block until that snapshot is dropped -- if that snapshot - /// is owned by the current thread, this could trigger deadlock. - fn synthetic_write(&mut self, durability: Durability) { - plumbing::DatabaseOps::synthetic_write(self, durability) - } -} - -/// The `Event` struct identifies various notable things that can -/// occur during salsa execution. Instances of this struct are given -/// to `salsa_event`. -pub struct Event { - /// The id of the snapshot that triggered the event. Usually - /// 1-to-1 with a thread, as well. - pub runtime_id: RuntimeId, - - /// What sort of event was it. - pub kind: EventKind, -} - -impl Event { - /// Returns a type that gives a user-readable debug output. - /// Use like `println!("{:?}", index.debug(db))`. - pub fn debug<'me, D>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me - where - D: ?Sized + plumbing::DatabaseOps, - { - EventDebug { event: self, db } - } -} - -impl fmt::Debug for Event { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("Event") - .field("runtime_id", &self.runtime_id) - .field("kind", &self.kind) - .finish() - } -} - -struct EventDebug<'me, D: ?Sized> -where - D: plumbing::DatabaseOps, -{ - event: &'me Event, - db: &'me D, -} - -impl<'me, D: ?Sized> fmt::Debug for EventDebug<'me, D> -where - D: plumbing::DatabaseOps, -{ - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("Event") - .field("runtime_id", &self.event.runtime_id) - .field("kind", &self.event.kind.debug(self.db)) - .finish() - } -} - -/// An enum identifying the various kinds of events that can occur. -pub enum EventKind { - /// Occurs when we found that all inputs to a memoized value are - /// up-to-date and hence the value can be re-used without - /// executing the closure. - /// - /// Executes before the "re-used" value is returned. - DidValidateMemoizedValue { - /// The database-key for the affected value. Implements `Debug`. - database_key: DatabaseKeyIndex, - }, - - /// Indicates that another thread (with id `other_runtime_id`) is processing the - /// given query (`database_key`), so we will block until they - /// finish. - /// - /// Executes after we have registered with the other thread but - /// before they have answered us. - /// - /// (NB: you can find the `id` of the current thread via the - /// `salsa_runtime`) - WillBlockOn { - /// The id of the runtime we will block on. - other_runtime_id: RuntimeId, - - /// The database-key for the affected value. Implements `Debug`. - database_key: DatabaseKeyIndex, - }, - - /// Indicates that the function for this query will be executed. - /// This is either because it has never executed before or because - /// its inputs may be out of date. - WillExecute { - /// The database-key for the affected value. Implements `Debug`. - database_key: DatabaseKeyIndex, - }, - - /// Indicates that `unwind_if_cancelled` was called and salsa will check if - /// the current revision has been cancelled. - WillCheckCancellation, -} - -impl EventKind { - /// Returns a type that gives a user-readable debug output. - /// Use like `println!("{:?}", index.debug(db))`. - pub fn debug<'me, D>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me - where - D: ?Sized + plumbing::DatabaseOps, - { - EventKindDebug { kind: self, db } - } -} - -impl fmt::Debug for EventKind { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - EventKind::DidValidateMemoizedValue { database_key } => fmt - .debug_struct("DidValidateMemoizedValue") - .field("database_key", database_key) - .finish(), - EventKind::WillBlockOn { other_runtime_id, database_key } => fmt - .debug_struct("WillBlockOn") - .field("other_runtime_id", other_runtime_id) - .field("database_key", database_key) - .finish(), - EventKind::WillExecute { database_key } => { - fmt.debug_struct("WillExecute").field("database_key", database_key).finish() - } - EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(), - } - } -} - -struct EventKindDebug<'me, D: ?Sized> -where - D: plumbing::DatabaseOps, -{ - kind: &'me EventKind, - db: &'me D, -} - -impl<'me, D: ?Sized> fmt::Debug for EventKindDebug<'me, D> -where - D: plumbing::DatabaseOps, -{ - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.kind { - EventKind::DidValidateMemoizedValue { database_key } => fmt - .debug_struct("DidValidateMemoizedValue") - .field("database_key", &database_key.debug(self.db)) - .finish(), - EventKind::WillBlockOn { other_runtime_id, database_key } => fmt - .debug_struct("WillBlockOn") - .field("other_runtime_id", &other_runtime_id) - .field("database_key", &database_key.debug(self.db)) - .finish(), - EventKind::WillExecute { database_key } => fmt - .debug_struct("WillExecute") - .field("database_key", &database_key.debug(self.db)) - .finish(), - EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(), - } - } -} - -/// Indicates a database that also supports parallel query -/// evaluation. All of Salsa's base query support is capable of -/// parallel execution, but for it to work, your query key/value types -/// must also be `Send`, as must any additional data in your database. -pub trait ParallelDatabase: Database + Send { - /// Creates a second handle to the database that holds the - /// database fixed at a particular revision. So long as this - /// "frozen" handle exists, any attempt to [`set`] an input will - /// block. - /// - /// [`set`]: struct.QueryTable.html#method.set - /// - /// This is the method you are meant to use most of the time in a - /// parallel setting where modifications may arise asynchronously - /// (e.g., a language server). In this context, it is common to - /// wish to "fork off" a snapshot of the database performing some - /// series of queries in parallel and arranging the results. Using - /// this method for that purpose ensures that those queries will - /// see a consistent view of the database (it is also advisable - /// for those queries to use the [`Database::unwind_if_cancelled`] - /// method to check for cancellation). - /// - /// # Panics - /// - /// It is not permitted to create a snapshot from inside of a - /// query. Attepting to do so will panic. - /// - /// # Deadlock warning - /// - /// The intended pattern for snapshots is that, once created, they - /// are sent to another thread and used from there. As such, the - /// `snapshot` acquires a "read lock" on the database -- - /// therefore, so long as the `snapshot` is not dropped, any - /// attempt to `set` a value in the database will block. If the - /// `snapshot` is owned by the same thread that is attempting to - /// `set`, this will cause a problem. - /// - /// # How to implement this - /// - /// Typically, this method will create a second copy of your - /// database type (`MyDatabaseType`, in the example below), - /// cloning over each of the fields from `self` into this new - /// copy. For the field that stores the salsa runtime, you should - /// use [the `Runtime::snapshot` method][rfm] to create a snapshot of the - /// runtime. Finally, package up the result using `Snapshot::new`, - /// which is a simple wrapper type that only gives `&self` access - /// to the database within (thus preventing the use of methods - /// that may mutate the inputs): - /// - /// [rfm]: struct.Runtime.html#method.snapshot - /// - /// ```rust,ignore - /// impl ParallelDatabase for MyDatabaseType { - /// fn snapshot(&self) -> Snapshot { - /// Snapshot::new( - /// MyDatabaseType { - /// runtime: self.runtime.snapshot(self), - /// other_field: self.other_field.clone(), - /// } - /// ) - /// } - /// } - /// ``` - fn snapshot(&self) -> Snapshot; -} - -/// Simple wrapper struct that takes ownership of a database `DB` and -/// only gives `&self` access to it. See [the `snapshot` method][fm] -/// for more details. -/// -/// [fm]: trait.ParallelDatabase.html#method.snapshot -#[derive(Debug)] -pub struct Snapshot -where - DB: ParallelDatabase, -{ - db: DB, -} - -impl Snapshot -where - DB: ParallelDatabase, -{ - /// Creates a `Snapshot` that wraps the given database handle - /// `db`. From this point forward, only shared references to `db` - /// will be possible. - pub fn new(db: DB) -> Self { - Snapshot { db } - } -} - -impl std::ops::Deref for Snapshot -where - DB: ParallelDatabase, -{ - type Target = DB; - - fn deref(&self) -> &DB { - &self.db - } -} - -/// An integer that uniquely identifies a particular query instance within the -/// database. Used to track dependencies between queries. Fully ordered and -/// equatable but those orderings are arbitrary, and meant to be used only for -/// inserting into maps and the like. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct DatabaseKeyIndex { - group_index: u16, - query_index: u16, - key_index: u32, -} - -impl DatabaseKeyIndex { - /// Returns the index of the query group containing this key. - #[inline] - pub fn group_index(self) -> u16 { - self.group_index - } - - /// Returns the index of the query within its query group. - #[inline] - pub fn query_index(self) -> u16 { - self.query_index - } - - /// Returns the index of this particular query key within the query. - #[inline] - pub fn key_index(self) -> u32 { - self.key_index - } - - /// Returns a type that gives a user-readable debug output. - /// Use like `println!("{:?}", index.debug(db))`. - pub fn debug(self, db: &D) -> impl std::fmt::Debug + '_ - where - D: ?Sized + plumbing::DatabaseOps, - { - DatabaseKeyIndexDebug { index: self, db } - } -} - -/// Helper type for `DatabaseKeyIndex::debug` -struct DatabaseKeyIndexDebug<'me, D: ?Sized> -where - D: plumbing::DatabaseOps, -{ - index: DatabaseKeyIndex, - db: &'me D, -} - -impl std::fmt::Debug for DatabaseKeyIndexDebug<'_, D> -where - D: plumbing::DatabaseOps, -{ - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.db.fmt_index(self.index, fmt) - } -} - -/// Trait implements by all of the "special types" associated with -/// each of your queries. -/// -/// Base trait of `Query` that has a lifetime parameter to allow the `DynDb` to be non-'static. -pub trait QueryDb<'d>: Sized { - /// Dyn version of the associated trait for this query group. - type DynDb: ?Sized + Database + HasQueryGroup + 'd; - - /// Associate query group struct. - type Group: plumbing::QueryGroup; - - /// Generated struct that contains storage for all queries in a group. - type GroupStorage; -} - -/// Trait implements by all of the "special types" associated with -/// each of your queries. -pub trait Query: Debug + Default + Sized + for<'d> QueryDb<'d> { - /// Type that you give as a parameter -- for queries with zero - /// or more than one input, this will be a tuple. - type Key: Clone + Debug + Hash + Eq; - - /// What value does the query return? - type Value: Clone + Debug; - - /// Internal struct storing the values for the query. - // type Storage: plumbing::QueryStorageOps; - type Storage; - - /// A unique index identifying this query within the group. - const QUERY_INDEX: u16; - - /// Name of the query method (e.g., `foo`) - const QUERY_NAME: &'static str; - - /// Extract storage for this query from the storage for its group. - fn query_storage<'a>( - group_storage: &'a >::GroupStorage, - ) -> &'a std::sync::Arc; - - /// Extract storage for this query from the storage for its group. - fn query_storage_mut<'a>( - group_storage: &'a >::GroupStorage, - ) -> &'a std::sync::Arc; -} - -/// Return value from [the `query` method] on `Database`. -/// Gives access to various less common operations on queries. -/// -/// [the `query` method]: trait.Database.html#method.query -pub struct QueryTable<'me, Q> -where - Q: Query, -{ - db: &'me >::DynDb, - storage: &'me Q::Storage, -} - -impl<'me, Q> QueryTable<'me, Q> -where - Q: Query, - Q::Storage: QueryStorageOps, -{ - /// Constructs a new `QueryTable`. - pub fn new(db: &'me >::DynDb, storage: &'me Q::Storage) -> Self { - Self { db, storage } - } - - /// Execute the query on a given input. Usually it's easier to - /// invoke the trait method directly. Note that for variadic - /// queries (those with no inputs, or those with more than one - /// input) the key will be a tuple. - pub fn get(&self, key: Q::Key) -> Q::Value { - self.storage.fetch(self.db, &key) - } - - /// Completely clears the storage for this query. - /// - /// This method breaks internal invariants of salsa, so any further queries - /// might return nonsense results. It is useful only in very specific - /// circumstances -- for example, when one wants to observe which values - /// dropped together with the table - pub fn purge(&self) - where - Q::Storage: plumbing::QueryStorageMassOps, - { - self.storage.purge(); - } - - pub fn storage(&self) -> &::Storage { - self.storage - } -} - -/// Return value from [the `query_mut` method] on `Database`. -/// Gives access to the `set` method, notably, that is used to -/// set the value of an input query. -/// -/// [the `query_mut` method]: trait.Database.html#method.query_mut -pub struct QueryTableMut<'me, Q> -where - Q: Query + 'me, -{ - runtime: &'me mut Runtime, - storage: &'me Q::Storage, -} - -impl<'me, Q> QueryTableMut<'me, Q> -where - Q: Query, -{ - /// Constructs a new `QueryTableMut`. - pub fn new(runtime: &'me mut Runtime, storage: &'me Q::Storage) -> Self { - Self { runtime, storage } - } - - /// Assign a value to an "input query". Must be used outside of - /// an active query computation. - /// - /// If you are using `snapshot`, see the notes on blocking - /// and cancellation on [the `query_mut` method]. - /// - /// [the `query_mut` method]: trait.Database.html#method.query_mut - pub fn set(&mut self, key: Q::Key, value: Q::Value) - where - Q::Storage: plumbing::InputQueryStorageOps, - { - self.set_with_durability(key, value, Durability::LOW); - } - - /// Assign a value to an "input query", with the additional - /// promise that this value will **never change**. Must be used - /// outside of an active query computation. - /// - /// If you are using `snapshot`, see the notes on blocking - /// and cancellation on [the `query_mut` method]. - /// - /// [the `query_mut` method]: trait.Database.html#method.query_mut - pub fn set_with_durability(&mut self, key: Q::Key, value: Q::Value, durability: Durability) - where - Q::Storage: plumbing::InputQueryStorageOps, - { - self.storage.set(self.runtime, &key, value, durability); - } - - /// Sets the size of LRU cache of values for this query table. - /// - /// That is, at most `cap` values will be preset in the table at the same - /// time. This helps with keeping maximum memory usage under control, at the - /// cost of potential extra recalculations of evicted values. - /// - /// If `cap` is zero, all values are preserved, this is the default. - pub fn set_lru_capacity(&self, cap: u16) - where - Q::Storage: plumbing::LruQueryStorageOps, - { - self.storage.set_lru_capacity(cap); - } - - /// Marks the computed value as outdated. - /// - /// This causes salsa to re-execute the query function on the next access to - /// the query, even if all dependencies are up to date. - /// - /// This is most commonly used as part of the [on-demand input - /// pattern](https://salsa-rs.github.io/salsa/common_patterns/on_demand_inputs.html). - pub fn invalidate(&mut self, key: &Q::Key) - where - Q::Storage: plumbing::DerivedQueryStorageOps, - { - self.storage.invalidate(self.runtime, key) - } -} - -/// A panic payload indicating that execution of a salsa query was cancelled. -/// -/// This can occur for a few reasons: -/// * -/// * -/// * -#[derive(Debug)] -#[non_exhaustive] -pub enum Cancelled { - /// The query was operating on revision R, but there is a pending write to move to revision R+1. - #[non_exhaustive] - PendingWrite, - - /// The query was blocked on another thread, and that thread panicked. - #[non_exhaustive] - PropagatedPanic, -} - -impl Cancelled { - fn throw(self) -> ! { - // We use resume and not panic here to avoid running the panic - // hook (that is, to avoid collecting and printing backtrace). - std::panic::resume_unwind(Box::new(self)); - } - - /// Runs `f`, and catches any salsa cancellation. - pub fn catch(f: F) -> Result - where - F: FnOnce() -> T + UnwindSafe, - { - match panic::catch_unwind(f) { - Ok(t) => Ok(t), - Err(payload) => match payload.downcast() { - Ok(cancelled) => Err(*cancelled), - Err(payload) => panic::resume_unwind(payload), - }, - } - } -} - -impl std::fmt::Display for Cancelled { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let why = match self { - Cancelled::PendingWrite => "pending write", - Cancelled::PropagatedPanic => "propagated panic", - }; - f.write_str("cancelled because of ")?; - f.write_str(why) - } -} - -impl std::error::Error for Cancelled {} - -/// Captures the participants of a cycle that occurred when executing a query. -/// -/// This type is meant to be used to help give meaningful error messages to the -/// user or to help salsa developers figure out why their program is resulting -/// in a computation cycle. -/// -/// It is used in a few ways: -/// -/// * During [cycle recovery](https://https://salsa-rs.github.io/salsa/cycles/fallback.html), -/// where it is given to the fallback function. -/// * As the panic value when an unexpected cycle (i.e., a cycle where one or more participants -/// lacks cycle recovery information) occurs. -/// -/// You can read more about cycle handling in -/// the [salsa book](https://https://salsa-rs.github.io/salsa/cycles.html). -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Cycle { - participants: plumbing::CycleParticipants, -} - -impl Cycle { - pub(crate) fn new(participants: plumbing::CycleParticipants) -> Self { - Self { participants } - } - - /// True if two `Cycle` values represent the same cycle. - pub(crate) fn is(&self, cycle: &Cycle) -> bool { - triomphe::Arc::ptr_eq(&self.participants, &cycle.participants) - } - - pub(crate) fn throw(self) -> ! { - tracing::debug!("throwing cycle {:?}", self); - std::panic::resume_unwind(Box::new(self)) - } - - pub(crate) fn catch(execute: impl FnOnce() -> T) -> Result { - match std::panic::catch_unwind(AssertUnwindSafe(execute)) { - Ok(v) => Ok(v), - Err(err) => match err.downcast::() { - Ok(cycle) => Err(*cycle), - Err(other) => std::panic::resume_unwind(other), - }, - } - } - - /// Iterate over the [`DatabaseKeyIndex`] for each query participating - /// in the cycle. The start point of this iteration within the cycle - /// is arbitrary but deterministic, but the ordering is otherwise determined - /// by the execution. - pub fn participant_keys(&self) -> impl Iterator + '_ { - self.participants.iter().copied() - } - - /// Returns a vector with the debug information for - /// all the participants in the cycle. - pub fn all_participants(&self, db: &DB) -> Vec { - self.participant_keys().map(|d| format!("{:?}", d.debug(db))).collect() - } - - /// Returns a vector with the debug information for - /// those participants in the cycle that lacked recovery - /// information. - pub fn unexpected_participants(&self, db: &DB) -> Vec { - self.participant_keys() - .filter(|&d| db.cycle_recovery_strategy(d) == CycleRecoveryStrategy::Panic) - .map(|d| format!("{:?}", d.debug(db))) - .collect() - } - - /// Returns a "debug" view onto this strict that can be used to print out information. - pub fn debug<'me, DB: ?Sized + Database>(&'me self, db: &'me DB) -> impl std::fmt::Debug + 'me { - struct UnexpectedCycleDebug<'me> { - c: &'me Cycle, - db: &'me dyn Database, - } - - impl<'me> std::fmt::Debug for UnexpectedCycleDebug<'me> { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt.debug_struct("UnexpectedCycle") - .field("all_participants", &self.c.all_participants(self.db)) - .field("unexpected_participants", &self.c.unexpected_participants(self.db)) - .finish() - } - } - - UnexpectedCycleDebug { c: self, db: db.ops_database() } - } -} - -// Re-export the procedural macros. -#[allow(unused_imports)] -#[macro_use] -extern crate salsa_macros; -use plumbing::HasQueryGroup; -pub use salsa_macros::*; diff --git a/src/tools/rust-analyzer/crates/salsa/src/lru.rs b/src/tools/rust-analyzer/crates/salsa/src/lru.rs deleted file mode 100644 index a6f96beeab1..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/lru.rs +++ /dev/null @@ -1,325 +0,0 @@ -use oorandom::Rand64; -use parking_lot::Mutex; -use std::fmt::Debug; -use std::sync::atomic::AtomicU16; -use std::sync::atomic::Ordering; -use triomphe::Arc; - -/// A simple and approximate concurrent lru list. -/// -/// We assume but do not verify that each node is only used with one -/// list. If this is not the case, it is not *unsafe*, but panics and -/// weird results will ensue. -/// -/// Each "node" in the list is of type `Node` and must implement -/// `LruNode`, which is a trait that gives access to a field that -/// stores the index in the list. This index gives us a rough idea of -/// how recently the node has been used. -#[derive(Debug)] -pub(crate) struct Lru -where - Node: LruNode, -{ - green_zone: AtomicU16, - data: Mutex>, -} - -#[derive(Debug)] -struct LruData { - end_red_zone: u16, - end_yellow_zone: u16, - end_green_zone: u16, - rng: Rand64, - entries: Vec>, -} - -pub(crate) trait LruNode: Sized + Debug { - fn lru_index(&self) -> &LruIndex; -} - -#[derive(Debug)] -pub(crate) struct LruIndex { - /// Index in the appropriate LRU list, or std::u16::MAX if not a - /// member. - index: AtomicU16, -} - -impl Default for Lru -where - Node: LruNode, -{ - fn default() -> Self { - Lru::new() - } -} - -// We always use a fixed seed for our randomness so that we have -// predictable results. -const LRU_SEED: &str = "Hello, Rustaceans"; - -impl Lru -where - Node: LruNode, -{ - /// Creates a new LRU list where LRU caching is disabled. - pub(crate) fn new() -> Self { - Self::with_seed(LRU_SEED) - } - - #[cfg_attr(not(test), allow(dead_code))] - fn with_seed(seed: &str) -> Self { - Lru { green_zone: AtomicU16::new(0), data: Mutex::new(LruData::with_seed(seed)) } - } - - /// Adjust the total number of nodes permitted to have a value at - /// once. If `len` is zero, this disables LRU caching completely. - pub(crate) fn set_lru_capacity(&self, len: u16) { - let mut data = self.data.lock(); - - // We require each zone to have at least 1 slot. Therefore, - // the length cannot be just 1 or 2. - if len == 0 { - self.green_zone.store(0, Ordering::Release); - data.resize(0, 0, 0); - } else { - let len = std::cmp::max(len, 3); - - // Top 10% is the green zone. This must be at least length 1. - let green_zone = std::cmp::max(len / 10, 1); - - // Next 20% is the yellow zone. - let yellow_zone = std::cmp::max(len / 5, 1); - - // Remaining 70% is the red zone. - let red_zone = len - yellow_zone - green_zone; - - // We need quick access to the green zone. - self.green_zone.store(green_zone, Ordering::Release); - - // Resize existing array. - data.resize(green_zone, yellow_zone, red_zone); - } - } - - /// Records that `node` was used. This may displace an old node (if the LRU limits are - pub(crate) fn record_use(&self, node: &Arc) -> Option> { - tracing::debug!("record_use(node={:?})", node); - - // Load green zone length and check if the LRU cache is even enabled. - let green_zone = self.green_zone.load(Ordering::Acquire); - tracing::debug!("record_use: green_zone={}", green_zone); - if green_zone == 0 { - return None; - } - - // Find current index of list (if any) and the current length - // of our green zone. - let index = node.lru_index().load(); - tracing::debug!("record_use: index={}", index); - - // Already a member of the list, and in the green zone -- nothing to do! - if index < green_zone { - return None; - } - - self.data.lock().record_use(node) - } - - pub(crate) fn purge(&self) { - self.green_zone.store(0, Ordering::SeqCst); - *self.data.lock() = LruData::with_seed(LRU_SEED); - } -} - -impl LruData -where - Node: LruNode, -{ - fn with_seed(seed_str: &str) -> Self { - Self::with_rng(rng_with_seed(seed_str)) - } - - fn with_rng(rng: Rand64) -> Self { - LruData { end_yellow_zone: 0, end_green_zone: 0, end_red_zone: 0, entries: Vec::new(), rng } - } - - fn green_zone(&self) -> std::ops::Range { - 0..self.end_green_zone - } - - fn yellow_zone(&self) -> std::ops::Range { - self.end_green_zone..self.end_yellow_zone - } - - fn red_zone(&self) -> std::ops::Range { - self.end_yellow_zone..self.end_red_zone - } - - fn resize(&mut self, len_green_zone: u16, len_yellow_zone: u16, len_red_zone: u16) { - self.end_green_zone = len_green_zone; - self.end_yellow_zone = self.end_green_zone + len_yellow_zone; - self.end_red_zone = self.end_yellow_zone + len_red_zone; - let entries = - std::mem::replace(&mut self.entries, Vec::with_capacity(self.end_red_zone as usize)); - - tracing::debug!("green_zone = {:?}", self.green_zone()); - tracing::debug!("yellow_zone = {:?}", self.yellow_zone()); - tracing::debug!("red_zone = {:?}", self.red_zone()); - - // We expect to resize when the LRU cache is basically empty. - // So just forget all the old LRU indices to start. - for entry in entries { - entry.lru_index().clear(); - } - } - - /// Records that a node was used. If it is already a member of the - /// LRU list, it is promoted to the green zone (unless it's - /// already there). Otherwise, it is added to the list first and - /// *then* promoted to the green zone. Adding a new node to the - /// list may displace an old member of the red zone, in which case - /// that is returned. - fn record_use(&mut self, node: &Arc) -> Option> { - tracing::debug!("record_use(node={:?})", node); - - // NB: When this is invoked, we have typically already loaded - // the LRU index (to check if it is in green zone). But that - // check was done outside the lock and -- for all we know -- - // the index may have changed since. So we always reload. - let index = node.lru_index().load(); - - if index < self.end_green_zone { - None - } else if index < self.end_yellow_zone { - self.promote_yellow_to_green(node, index); - None - } else if index < self.end_red_zone { - self.promote_red_to_green(node, index); - None - } else { - self.insert_new(node) - } - } - - /// Inserts a node that is not yet a member of the LRU list. If - /// the list is at capacity, this can displace an existing member. - fn insert_new(&mut self, node: &Arc) -> Option> { - debug_assert!(!node.lru_index().is_in_lru()); - - // Easy case: we still have capacity. Push it, and then promote - // it up to the appropriate zone. - let len = self.entries.len() as u16; - if len < self.end_red_zone { - self.entries.push(node.clone()); - node.lru_index().store(len); - tracing::debug!("inserted node {:?} at {}", node, len); - return self.record_use(node); - } - - // Harder case: no capacity. Create some by evicting somebody from red - // zone and then promoting. - let victim_index = self.pick_index(self.red_zone()); - let victim_node = std::mem::replace(&mut self.entries[victim_index as usize], node.clone()); - tracing::debug!("evicting red node {:?} from {}", victim_node, victim_index); - victim_node.lru_index().clear(); - self.promote_red_to_green(node, victim_index); - Some(victim_node) - } - - /// Promotes the node `node`, stored at `red_index` (in the red - /// zone), into a green index, demoting yellow/green nodes at - /// random. - /// - /// NB: It is not required that `node.lru_index()` is up-to-date - /// when entering this method. - fn promote_red_to_green(&mut self, node: &Arc, red_index: u16) { - debug_assert!(self.red_zone().contains(&red_index)); - - // Pick a yellow at random and switch places with it. - // - // Subtle: we do not update `node.lru_index` *yet* -- we're - // going to invoke `self.promote_yellow` next, and it will get - // updated then. - let yellow_index = self.pick_index(self.yellow_zone()); - tracing::debug!( - "demoting yellow node {:?} from {} to red at {}", - self.entries[yellow_index as usize], - yellow_index, - red_index, - ); - self.entries.swap(yellow_index as usize, red_index as usize); - self.entries[red_index as usize].lru_index().store(red_index); - - // Now move ourselves up into the green zone. - self.promote_yellow_to_green(node, yellow_index); - } - - /// Promotes the node `node`, stored at `yellow_index` (in the - /// yellow zone), into a green index, demoting a green node at - /// random to replace it. - /// - /// NB: It is not required that `node.lru_index()` is up-to-date - /// when entering this method. - fn promote_yellow_to_green(&mut self, node: &Arc, yellow_index: u16) { - debug_assert!(self.yellow_zone().contains(&yellow_index)); - - // Pick a yellow at random and switch places with it. - let green_index = self.pick_index(self.green_zone()); - tracing::debug!( - "demoting green node {:?} from {} to yellow at {}", - self.entries[green_index as usize], - green_index, - yellow_index - ); - self.entries.swap(green_index as usize, yellow_index as usize); - self.entries[yellow_index as usize].lru_index().store(yellow_index); - node.lru_index().store(green_index); - - tracing::debug!("promoted {:?} to green index {}", node, green_index); - } - - fn pick_index(&mut self, zone: std::ops::Range) -> u16 { - let end_index = std::cmp::min(zone.end, self.entries.len() as u16); - self.rng.rand_range(zone.start as u64..end_index as u64) as u16 - } -} - -impl Default for LruIndex { - fn default() -> Self { - Self { index: AtomicU16::new(u16::MAX) } - } -} - -impl LruIndex { - fn load(&self) -> u16 { - self.index.load(Ordering::Acquire) // see note on ordering below - } - - fn store(&self, value: u16) { - self.index.store(value, Ordering::Release) // see note on ordering below - } - - fn clear(&self) { - self.store(u16::MAX); - } - - fn is_in_lru(&self) -> bool { - self.load() != u16::MAX - } -} - -fn rng_with_seed(seed_str: &str) -> Rand64 { - let mut seed: [u8; 16] = [0; 16]; - for (i, &b) in seed_str.as_bytes().iter().take(16).enumerate() { - seed[i] = b; - } - Rand64::new(u128::from_le_bytes(seed)) -} - -// A note on ordering: -// -// I chose to use AcqRel for the ordering but I don't think it's -// strictly needed. All writes occur under a lock, so they should be -// ordered w/r/t one another. As for the reads, they can occur -// outside the lock, but they don't themselves enable dependent reads -// -- if the reads are out of bounds, we would acquire a lock. diff --git a/src/tools/rust-analyzer/crates/salsa/src/plumbing.rs b/src/tools/rust-analyzer/crates/salsa/src/plumbing.rs deleted file mode 100644 index e96b9daa979..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/plumbing.rs +++ /dev/null @@ -1,245 +0,0 @@ -#![allow(missing_docs)] - -use crate::debug::TableEntry; -use crate::durability::Durability; -use crate::Cycle; -use crate::Database; -use crate::Query; -use crate::QueryTable; -use crate::QueryTableMut; -use std::borrow::Borrow; -use std::fmt::Debug; -use std::hash::Hash; -use triomphe::Arc; - -pub use crate::derived::MemoizedStorage; -pub use crate::derived_lru::DependencyStorage as LruDependencyStorage; -pub use crate::derived_lru::MemoizedStorage as LruMemoizedStorage; -pub use crate::input::{InputStorage, UnitInputStorage}; -pub use crate::interned::InternedStorage; -pub use crate::interned::LookupInternedStorage; -pub use crate::{revision::Revision, DatabaseKeyIndex, QueryDb, Runtime}; - -/// Defines various associated types. An impl of this -/// should be generated for your query-context type automatically by -/// the `database_storage` macro, so you shouldn't need to mess -/// with this trait directly. -pub trait DatabaseStorageTypes: Database { - /// Defines the "storage type", where all the query data is kept. - /// This type is defined by the `database_storage` macro. - type DatabaseStorage: Default; -} - -/// Internal operations that the runtime uses to operate on the database. -pub trait DatabaseOps { - /// Upcast this type to a `dyn Database`. - fn ops_database(&self) -> &dyn Database; - - /// Gives access to the underlying salsa runtime. - fn ops_salsa_runtime(&self) -> &Runtime; - - /// A "synthetic write" causes the system to act *as though* some - /// input of durability `durability` has changed. This is mostly - /// useful for profiling scenarios. - /// - /// **WARNING:** Just like an ordinary write, this method triggers - /// cancellation. If you invoke it while a snapshot exists, it - /// will block until that snapshot is dropped -- if that snapshot - /// is owned by the current thread, this could trigger deadlock. - fn synthetic_write(&mut self, durability: Durability); - - /// Formats a database key index in a human readable fashion. - fn fmt_index( - &self, - index: DatabaseKeyIndex, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result; - - /// True if the computed value for `input` may have changed since `revision`. - fn maybe_changed_after(&self, input: DatabaseKeyIndex, revision: Revision) -> bool; - - /// Find the `CycleRecoveryStrategy` for a given input. - fn cycle_recovery_strategy(&self, input: DatabaseKeyIndex) -> CycleRecoveryStrategy; - - /// Executes the callback for each kind of query. - fn for_each_query(&self, op: &mut dyn FnMut(&dyn QueryStorageMassOps)); -} - -/// Internal operations performed on the query storage as a whole -/// (note that these ops do not need to know the identity of the -/// query, unlike `QueryStorageOps`). -pub trait QueryStorageMassOps { - fn purge(&self); -} - -pub trait DatabaseKey: Clone + Debug + Eq + Hash {} - -pub trait QueryFunction: Query { - /// See `CycleRecoveryStrategy` - const CYCLE_STRATEGY: CycleRecoveryStrategy; - - fn execute(db: &>::DynDb, key: Self::Key) -> Self::Value; - - fn cycle_fallback( - db: &>::DynDb, - cycle: &Cycle, - key: &Self::Key, - ) -> Self::Value { - let _ = (db, cycle, key); - panic!("query `{:?}` doesn't support cycle fallback", Self::default()) - } -} - -/// Cycle recovery strategy: Is this query capable of recovering from -/// a cycle that results from executing the function? If so, how? -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum CycleRecoveryStrategy { - /// Cannot recover from cycles: panic. - /// - /// This is the default. It is also what happens if a cycle - /// occurs and the queries involved have different recovery - /// strategies. - /// - /// In the case of a failure due to a cycle, the panic - /// value will be XXX (FIXME). - Panic, - - /// Recovers from cycles by storing a sentinel value. - /// - /// This value is computed by the `QueryFunction::cycle_fallback` - /// function. - Fallback, -} - -/// Create a query table, which has access to the storage for the query -/// and offers methods like `get`. -pub fn get_query_table<'me, Q>(db: &'me >::DynDb) -> QueryTable<'me, Q> -where - Q: Query + 'me, - Q::Storage: QueryStorageOps, -{ - let group_storage: &Q::GroupStorage = HasQueryGroup::group_storage(db); - let query_storage: &Q::Storage = Q::query_storage(group_storage); - QueryTable::new(db, query_storage) -} - -/// Create a mutable query table, which has access to the storage -/// for the query and offers methods like `set`. -pub fn get_query_table_mut<'me, Q>(db: &'me mut >::DynDb) -> QueryTableMut<'me, Q> -where - Q: Query, -{ - let (group_storage, runtime) = HasQueryGroup::group_storage_mut(db); - let query_storage = Q::query_storage_mut(group_storage); - QueryTableMut::new(runtime, &**query_storage) -} - -pub trait QueryGroup: Sized { - type GroupStorage; - - /// Dyn version of the associated database trait. - type DynDb: ?Sized + Database + HasQueryGroup; -} - -/// Trait implemented by a database for each group that it supports. -/// `S` and `K` are the types for *group storage* and *group key*, respectively. -pub trait HasQueryGroup: Database -where - G: QueryGroup, -{ - /// Access the group storage struct from the database. - fn group_storage(&self) -> &G::GroupStorage; - - /// Access the group storage struct from the database. - /// Also returns a ref to the `Runtime`, since otherwise - /// the database is borrowed and one cannot get access to it. - fn group_storage_mut(&mut self) -> (&G::GroupStorage, &mut Runtime); -} - -// ANCHOR:QueryStorageOps -pub trait QueryStorageOps -where - Self: QueryStorageMassOps, - Q: Query, -{ - // ANCHOR_END:QueryStorageOps - - /// See CycleRecoveryStrategy - const CYCLE_STRATEGY: CycleRecoveryStrategy; - - fn new(group_index: u16) -> Self; - - /// Format a database key index in a suitable way. - fn fmt_index( - &self, - db: &>::DynDb, - index: u32, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result; - - // ANCHOR:maybe_changed_after - /// True if the value of `input`, which must be from this query, may have - /// changed after the given revision ended. - /// - /// This function should only be invoked with a revision less than the current - /// revision. - fn maybe_changed_after( - &self, - db: &>::DynDb, - index: u32, - revision: Revision, - ) -> bool; - // ANCHOR_END:maybe_changed_after - - fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy { - Self::CYCLE_STRATEGY - } - - // ANCHOR:fetch - /// Execute the query, returning the result (often, the result - /// will be memoized). This is the "main method" for - /// queries. - /// - /// Returns `Err` in the event of a cycle, meaning that computing - /// the value for this `key` is recursively attempting to fetch - /// itself. - fn fetch(&self, db: &>::DynDb, key: &Q::Key) -> Q::Value; - // ANCHOR_END:fetch - - /// Returns the durability associated with a given key. - fn durability(&self, db: &>::DynDb, key: &Q::Key) -> Durability; - - /// Get the (current) set of the entries in the query storage - fn entries(&self, db: &>::DynDb) -> C - where - C: std::iter::FromIterator>; -} - -/// An optional trait that is implemented for "user mutable" storage: -/// that is, storage whose value is not derived from other storage but -/// is set independently. -pub trait InputQueryStorageOps -where - Q: Query, -{ - fn set(&self, runtime: &mut Runtime, key: &Q::Key, new_value: Q::Value, durability: Durability); -} - -/// An optional trait that is implemented for "user mutable" storage: -/// that is, storage whose value is not derived from other storage but -/// is set independently. -pub trait LruQueryStorageOps { - fn set_lru_capacity(&self, new_capacity: u16); -} - -pub trait DerivedQueryStorageOps -where - Q: Query, -{ - fn invalidate(&self, runtime: &mut Runtime, key: &S) - where - S: Eq + Hash, - Q::Key: Borrow; -} - -pub type CycleParticipants = Arc>; diff --git a/src/tools/rust-analyzer/crates/salsa/src/revision.rs b/src/tools/rust-analyzer/crates/salsa/src/revision.rs deleted file mode 100644 index 7f4c333fb19..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/revision.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::num::NonZeroU32; -use std::sync::atomic::{AtomicU32, Ordering}; - -/// Value of the initial revision, as a u32. We don't use 0 -/// because we want to use a `NonZeroU32`. -const START: u32 = 1; - -/// A unique identifier for the current version of the database; each -/// time an input is changed, the revision number is incremented. -/// `Revision` is used internally to track which values may need to be -/// recomputed, but is not something you should have to interact with -/// directly as a user of salsa. -#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Revision { - generation: NonZeroU32, -} - -impl Revision { - pub(crate) fn start() -> Self { - Self::from(START) - } - - pub(crate) fn from(g: u32) -> Self { - Self { generation: NonZeroU32::new(g).unwrap() } - } - - pub(crate) fn next(self) -> Revision { - Self::from(self.generation.get() + 1) - } - - fn as_u32(self) -> u32 { - self.generation.get() - } -} - -impl std::fmt::Debug for Revision { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(fmt, "R{}", self.generation) - } -} - -#[derive(Debug)] -pub(crate) struct AtomicRevision { - data: AtomicU32, -} - -impl AtomicRevision { - pub(crate) const fn start() -> Self { - Self { data: AtomicU32::new(START) } - } - - pub(crate) fn load(&self) -> Revision { - Revision::from(self.data.load(Ordering::SeqCst)) - } - - pub(crate) fn store(&self, r: Revision) { - self.data.store(r.as_u32(), Ordering::SeqCst); - } - - /// Increment by 1, returning previous value. - pub(crate) fn fetch_then_increment(&self) -> Revision { - let v = self.data.fetch_add(1, Ordering::SeqCst); - assert!(v != u32::MAX, "revision overflow"); - Revision::from(v) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/runtime.rs b/src/tools/rust-analyzer/crates/salsa/src/runtime.rs deleted file mode 100644 index 5fe5f4b46d3..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/runtime.rs +++ /dev/null @@ -1,668 +0,0 @@ -use crate::durability::Durability; -use crate::hash::FxIndexSet; -use crate::plumbing::CycleRecoveryStrategy; -use crate::revision::{AtomicRevision, Revision}; -use crate::{Cancelled, Cycle, Database, DatabaseKeyIndex, Event, EventKind}; -use itertools::Itertools; -use parking_lot::lock_api::{RawRwLock, RawRwLockRecursive}; -use parking_lot::{Mutex, RwLock}; -use std::hash::Hash; -use std::panic::panic_any; -use std::sync::atomic::{AtomicU32, Ordering}; -use tracing::debug; -use triomphe::{Arc, ThinArc}; - -mod dependency_graph; -use dependency_graph::DependencyGraph; - -pub(crate) mod local_state; -use local_state::LocalState; - -use self::local_state::{ActiveQueryGuard, QueryRevisions}; - -/// The salsa runtime stores the storage for all queries as well as -/// tracking the query stack and dependencies between cycles. -/// -/// Each new runtime you create (e.g., via `Runtime::new` or -/// `Runtime::default`) will have an independent set of query storage -/// associated with it. Normally, therefore, you only do this once, at -/// the start of your application. -pub struct Runtime { - /// Our unique runtime id. - id: RuntimeId, - - /// If this is a "forked" runtime, then the `revision_guard` will - /// be `Some`; this guard holds a read-lock on the global query - /// lock. - revision_guard: Option, - - /// Local state that is specific to this runtime (thread). - local_state: LocalState, - - /// Shared state that is accessible via all runtimes. - shared_state: Arc, -} - -#[derive(Clone, Debug)] -pub(crate) enum WaitResult { - Completed, - Panicked, - Cycle(Cycle), -} - -impl Default for Runtime { - fn default() -> Self { - Runtime { - id: RuntimeId { counter: 0 }, - revision_guard: None, - shared_state: Default::default(), - local_state: Default::default(), - } - } -} - -impl std::fmt::Debug for Runtime { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt.debug_struct("Runtime") - .field("id", &self.id()) - .field("forked", &self.revision_guard.is_some()) - .field("shared_state", &self.shared_state) - .finish() - } -} - -impl Runtime { - /// Create a new runtime; equivalent to `Self::default`. This is - /// used when creating a new database. - pub fn new() -> Self { - Self::default() - } - - /// See [`crate::storage::Storage::snapshot`]. - pub(crate) fn snapshot(&self) -> Self { - if self.local_state.query_in_progress() { - panic!("it is not legal to `snapshot` during a query (see salsa-rs/salsa#80)"); - } - - let revision_guard = RevisionGuard::new(&self.shared_state); - - let id = RuntimeId { counter: self.shared_state.next_id.fetch_add(1, Ordering::SeqCst) }; - - Runtime { - id, - revision_guard: Some(revision_guard), - shared_state: self.shared_state.clone(), - local_state: Default::default(), - } - } - - /// A "synthetic write" causes the system to act *as though* some - /// input of durability `durability` has changed. This is mostly - /// useful for profiling scenarios. - /// - /// **WARNING:** Just like an ordinary write, this method triggers - /// cancellation. If you invoke it while a snapshot exists, it - /// will block until that snapshot is dropped -- if that snapshot - /// is owned by the current thread, this could trigger deadlock. - pub fn synthetic_write(&mut self, durability: Durability) { - self.with_incremented_revision(|_next_revision| Some(durability)); - } - - /// The unique identifier attached to this `SalsaRuntime`. Each - /// snapshotted runtime has a distinct identifier. - #[inline] - pub fn id(&self) -> RuntimeId { - self.id - } - - /// Returns the database-key for the query that this thread is - /// actively executing (if any). - pub fn active_query(&self) -> Option { - self.local_state.active_query() - } - - /// Read current value of the revision counter. - #[inline] - pub(crate) fn current_revision(&self) -> Revision { - self.shared_state.revisions[0].load() - } - - /// The revision in which values with durability `d` may have last - /// changed. For D0, this is just the current revision. But for - /// higher levels of durability, this value may lag behind the - /// current revision. If we encounter a value of durability Di, - /// then, we can check this function to get a "bound" on when the - /// value may have changed, which allows us to skip walking its - /// dependencies. - #[inline] - pub(crate) fn last_changed_revision(&self, d: Durability) -> Revision { - self.shared_state.revisions[d.index()].load() - } - - /// Read current value of the revision counter. - #[inline] - pub(crate) fn pending_revision(&self) -> Revision { - self.shared_state.pending_revision.load() - } - - #[cold] - pub(crate) fn unwind_cancelled(&self) { - self.report_untracked_read(); - Cancelled::PendingWrite.throw(); - } - - /// Acquires the **global query write lock** (ensuring that no queries are - /// executing) and then increments the current revision counter; invokes - /// `op` with the global query write lock still held. - /// - /// While we wait to acquire the global query write lock, this method will - /// also increment `pending_revision_increments`, thus signalling to queries - /// that their results are "cancelled" and they should abort as expeditiously - /// as possible. - /// - /// The `op` closure should actually perform the writes needed. It is given - /// the new revision as an argument, and its return value indicates whether - /// any pre-existing value was modified: - /// - /// - returning `None` means that no pre-existing value was modified (this - /// could occur e.g. when setting some key on an input that was never set - /// before) - /// - returning `Some(d)` indicates that a pre-existing value was modified - /// and it had the durability `d`. This will update the records for when - /// values with each durability were modified. - /// - /// Note that, given our writer model, we can assume that only one thread is - /// attempting to increment the global revision at a time. - pub(crate) fn with_incremented_revision(&mut self, op: F) - where - F: FnOnce(Revision) -> Option, - { - tracing::debug!("increment_revision()"); - - if !self.permits_increment() { - panic!("increment_revision invoked during a query computation"); - } - - // Set the `pending_revision` field so that people - // know current revision is cancelled. - let current_revision = self.shared_state.pending_revision.fetch_then_increment(); - - // To modify the revision, we need the lock. - let shared_state = self.shared_state.clone(); - let _lock = shared_state.query_lock.write(); - - let old_revision = self.shared_state.revisions[0].fetch_then_increment(); - assert_eq!(current_revision, old_revision); - - let new_revision = current_revision.next(); - - debug!("increment_revision: incremented to {:?}", new_revision); - - if let Some(d) = op(new_revision) { - for rev in &self.shared_state.revisions[1..=d.index()] { - rev.store(new_revision); - } - } - } - - pub(crate) fn permits_increment(&self) -> bool { - self.revision_guard.is_none() && !self.local_state.query_in_progress() - } - - #[inline] - pub(crate) fn push_query(&self, database_key_index: DatabaseKeyIndex) -> ActiveQueryGuard<'_> { - self.local_state.push_query(database_key_index) - } - - /// Reports that the currently active query read the result from - /// another query. - /// - /// Also checks whether the "cycle participant" flag is set on - /// the current stack frame -- if so, panics with `CycleParticipant` - /// value, which should be caught by the code executing the query. - /// - /// # Parameters - /// - /// - `database_key`: the query whose result was read - /// - `changed_revision`: the last revision in which the result of that - /// query had changed - pub(crate) fn report_query_read_and_unwind_if_cycle_resulted( - &self, - input: DatabaseKeyIndex, - durability: Durability, - changed_at: Revision, - ) { - self.local_state - .report_query_read_and_unwind_if_cycle_resulted(input, durability, changed_at); - } - - /// Reports that the query depends on some state unknown to salsa. - /// - /// Queries which report untracked reads will be re-executed in the next - /// revision. - pub fn report_untracked_read(&self) { - self.local_state.report_untracked_read(self.current_revision()); - } - - /// Acts as though the current query had read an input with the given durability; this will force the current query's durability to be at most `durability`. - /// - /// This is mostly useful to control the durability level for [on-demand inputs](https://salsa-rs.github.io/salsa/common_patterns/on_demand_inputs.html). - pub fn report_synthetic_read(&self, durability: Durability) { - let changed_at = self.last_changed_revision(durability); - self.local_state.report_synthetic_read(durability, changed_at); - } - - /// Handles a cycle in the dependency graph that was detected when the - /// current thread tried to block on `database_key_index` which is being - /// executed by `to_id`. If this function returns, then `to_id` no longer - /// depends on the current thread, and so we should continue executing - /// as normal. Otherwise, the function will throw a `Cycle` which is expected - /// to be caught by some frame on our stack. This occurs either if there is - /// a frame on our stack with cycle recovery (possibly the top one!) or if there - /// is no cycle recovery at all. - fn unblock_cycle_and_maybe_throw( - &self, - db: &dyn Database, - dg: &mut DependencyGraph, - database_key_index: DatabaseKeyIndex, - to_id: RuntimeId, - ) { - debug!("unblock_cycle_and_maybe_throw(database_key={:?})", database_key_index); - - let mut from_stack = self.local_state.take_query_stack(); - let from_id = self.id(); - - // Make a "dummy stack frame". As we iterate through the cycle, we will collect the - // inputs from each participant. Then, if we are participating in cycle recovery, we - // will propagate those results to all participants. - let mut cycle_query = ActiveQuery::new(database_key_index); - - // Identify the cycle participants: - let cycle = { - let mut v = vec![]; - dg.for_each_cycle_participant( - from_id, - &mut from_stack, - database_key_index, - to_id, - |aqs| { - aqs.iter_mut().for_each(|aq| { - cycle_query.add_from(aq); - v.push(aq.database_key_index); - }); - }, - ); - - // We want to give the participants in a deterministic order - // (at least for this execution, not necessarily across executions), - // no matter where it started on the stack. Find the minimum - // key and rotate it to the front. - let index = v.iter().position_min().unwrap_or_default(); - v.rotate_left(index); - - // No need to store extra memory. - v.shrink_to_fit(); - - Cycle::new(Arc::new(v)) - }; - debug!("cycle {:?}, cycle_query {:#?}", cycle.debug(db), cycle_query,); - - // We can remove the cycle participants from the list of dependencies; - // they are a strongly connected component (SCC) and we only care about - // dependencies to things outside the SCC that control whether it will - // form again. - cycle_query.remove_cycle_participants(&cycle); - - // Mark each cycle participant that has recovery set, along with - // any frames that come after them on the same thread. Those frames - // are going to be unwound so that fallback can occur. - dg.for_each_cycle_participant(from_id, &mut from_stack, database_key_index, to_id, |aqs| { - aqs.iter_mut() - .skip_while(|aq| match db.cycle_recovery_strategy(aq.database_key_index) { - CycleRecoveryStrategy::Panic => true, - CycleRecoveryStrategy::Fallback => false, - }) - .for_each(|aq| { - debug!("marking {:?} for fallback", aq.database_key_index.debug(db)); - aq.take_inputs_from(&cycle_query); - assert!(aq.cycle.is_none()); - aq.cycle = Some(cycle.clone()); - }); - }); - - // Unblock every thread that has cycle recovery with a `WaitResult::Cycle`. - // They will throw the cycle, which will be caught by the frame that has - // cycle recovery so that it can execute that recovery. - let (me_recovered, others_recovered) = - dg.maybe_unblock_runtimes_in_cycle(from_id, &from_stack, database_key_index, to_id); - - self.local_state.restore_query_stack(from_stack); - - if me_recovered { - // If the current thread has recovery, we want to throw - // so that it can begin. - cycle.throw() - } else if others_recovered { - // If other threads have recovery but we didn't: return and we will block on them. - } else { - // if nobody has recover, then we panic - panic_any(cycle); - } - } - - /// Block until `other_id` completes executing `database_key`; - /// panic or unwind in the case of a cycle. - /// - /// `query_mutex_guard` is the guard for the current query's state; - /// it will be dropped after we have successfully registered the - /// dependency. - /// - /// # Propagating panics - /// - /// If the thread `other_id` panics, then our thread is considered - /// cancelled, so this function will panic with a `Cancelled` value. - /// - /// # Cycle handling - /// - /// If the thread `other_id` already depends on the current thread, - /// and hence there is a cycle in the query graph, then this function - /// will unwind instead of returning normally. The method of unwinding - /// depends on the [`Self::mutual_cycle_recovery_strategy`] - /// of the cycle participants: - /// - /// * [`CycleRecoveryStrategy::Panic`]: panic with the [`Cycle`] as the value. - /// * [`CycleRecoveryStrategy::Fallback`]: initiate unwinding with [`CycleParticipant::unwind`]. - pub(crate) fn block_on_or_unwind( - &self, - db: &dyn Database, - database_key: DatabaseKeyIndex, - other_id: RuntimeId, - query_mutex_guard: QueryMutexGuard, - ) { - let mut dg = self.shared_state.dependency_graph.lock(); - - if dg.depends_on(other_id, self.id()) { - self.unblock_cycle_and_maybe_throw(db, &mut dg, database_key, other_id); - - // If the above fn returns, then (via cycle recovery) it has unblocked the - // cycle, so we can continue. - assert!(!dg.depends_on(other_id, self.id())); - } - - db.salsa_event(Event { - runtime_id: self.id(), - kind: EventKind::WillBlockOn { other_runtime_id: other_id, database_key }, - }); - - let stack = self.local_state.take_query_stack(); - - let (stack, result) = DependencyGraph::block_on( - dg, - self.id(), - database_key, - other_id, - stack, - query_mutex_guard, - ); - - self.local_state.restore_query_stack(stack); - - match result { - WaitResult::Completed => (), - - // If the other thread panicked, then we consider this thread - // cancelled. The assumption is that the panic will be detected - // by the other thread and responded to appropriately. - WaitResult::Panicked => Cancelled::PropagatedPanic.throw(), - - WaitResult::Cycle(c) => c.throw(), - } - } - - /// Invoked when this runtime completed computing `database_key` with - /// the given result `wait_result` (`wait_result` should be `None` if - /// computing `database_key` panicked and could not complete). - /// This function unblocks any dependent queries and allows them - /// to continue executing. - pub(crate) fn unblock_queries_blocked_on( - &self, - database_key: DatabaseKeyIndex, - wait_result: WaitResult, - ) { - self.shared_state - .dependency_graph - .lock() - .unblock_runtimes_blocked_on(database_key, wait_result); - } -} - -/// State that will be common to all threads (when we support multiple threads) -struct SharedState { - /// Stores the next id to use for a snapshotted runtime (starts at 1). - next_id: AtomicU32, - - /// Whenever derived queries are executing, they acquire this lock - /// in read mode. Mutating inputs (and thus creating a new - /// revision) requires a write lock (thus guaranteeing that no - /// derived queries are in progress). Note that this is not needed - /// to prevent **race conditions** -- the revision counter itself - /// is stored in an `AtomicUsize` so it can be cheaply read - /// without acquiring the lock. Rather, the `query_lock` is used - /// to ensure a higher-level consistency property. - query_lock: RwLock<()>, - - /// This is typically equal to `revision` -- set to `revision+1` - /// when a new revision is pending (which implies that the current - /// revision is cancelled). - pending_revision: AtomicRevision, - - /// Stores the "last change" revision for values of each Durability. - /// This vector is always of length at least 1 (for Durability 0) - /// but its total length depends on the number of Durabilities. The - /// element at index 0 is special as it represents the "current - /// revision". In general, we have the invariant that revisions - /// in here are *declining* -- that is, `revisions[i] >= - /// revisions[i + 1]`, for all `i`. This is because when you - /// modify a value with durability D, that implies that values - /// with durability less than D may have changed too. - revisions: [AtomicRevision; Durability::LEN], - - /// The dependency graph tracks which runtimes are blocked on one - /// another, waiting for queries to terminate. - dependency_graph: Mutex, -} - -impl std::panic::RefUnwindSafe for SharedState {} - -impl Default for SharedState { - fn default() -> Self { - #[allow(clippy::declare_interior_mutable_const)] - const START: AtomicRevision = AtomicRevision::start(); - SharedState { - next_id: AtomicU32::new(1), - query_lock: Default::default(), - revisions: [START; Durability::LEN], - pending_revision: START, - dependency_graph: Default::default(), - } - } -} - -impl std::fmt::Debug for SharedState { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let query_lock = if self.query_lock.is_locked_exclusive() { - "" - } else if self.query_lock.is_locked() { - "" - } else { - "" - }; - fmt.debug_struct("SharedState") - .field("query_lock", &query_lock) - .field("revisions", &self.revisions) - .field("pending_revision", &self.pending_revision) - .finish() - } -} - -#[derive(Debug)] -struct ActiveQuery { - /// What query is executing - database_key_index: DatabaseKeyIndex, - - /// Minimum durability of inputs observed so far. - durability: Durability, - - /// Maximum revision of all inputs observed. If we observe an - /// untracked read, this will be set to the most recent revision. - changed_at: Revision, - - /// Set of subqueries that were accessed thus far, or `None` if - /// there was an untracked the read. - dependencies: Option>, - - /// Stores the entire cycle, if one is found and this query is part of it. - cycle: Option, -} - -impl ActiveQuery { - fn new(database_key_index: DatabaseKeyIndex) -> Self { - ActiveQuery { - database_key_index, - durability: Durability::MAX, - changed_at: Revision::start(), - dependencies: Some(FxIndexSet::default()), - cycle: None, - } - } - - fn add_read(&mut self, input: DatabaseKeyIndex, durability: Durability, revision: Revision) { - if let Some(set) = &mut self.dependencies { - set.insert(input); - } - - self.durability = self.durability.min(durability); - self.changed_at = self.changed_at.max(revision); - } - - fn add_untracked_read(&mut self, changed_at: Revision) { - self.dependencies = None; - self.durability = Durability::LOW; - self.changed_at = changed_at; - } - - fn add_synthetic_read(&mut self, durability: Durability, revision: Revision) { - self.dependencies = None; - self.durability = self.durability.min(durability); - self.changed_at = self.changed_at.max(revision); - } - - pub(crate) fn revisions(&self) -> QueryRevisions { - let (inputs, untracked) = match &self.dependencies { - None => (None, true), - - Some(dependencies) => ( - if dependencies.is_empty() { - None - } else { - Some(ThinArc::from_header_and_iter((), dependencies.iter().copied())) - }, - false, - ), - }; - - QueryRevisions { - changed_at: self.changed_at, - inputs, - untracked, - durability: self.durability, - } - } - - /// Adds any dependencies from `other` into `self`. - /// Used during cycle recovery, see [`Runtime::create_cycle_error`]. - fn add_from(&mut self, other: &ActiveQuery) { - self.changed_at = self.changed_at.max(other.changed_at); - self.durability = self.durability.min(other.durability); - if let Some(other_dependencies) = &other.dependencies { - if let Some(my_dependencies) = &mut self.dependencies { - my_dependencies.extend(other_dependencies.iter().copied()); - } - } else { - self.dependencies = None; - } - } - - /// Removes the participants in `cycle` from my dependencies. - /// Used during cycle recovery, see [`Runtime::create_cycle_error`]. - fn remove_cycle_participants(&mut self, cycle: &Cycle) { - if let Some(my_dependencies) = &mut self.dependencies { - for p in cycle.participant_keys() { - my_dependencies.swap_remove(&p); - } - } - } - - /// Copy the changed-at, durability, and dependencies from `cycle_query`. - /// Used during cycle recovery, see [`Runtime::create_cycle_error`]. - pub(crate) fn take_inputs_from(&mut self, cycle_query: &ActiveQuery) { - self.changed_at = cycle_query.changed_at; - self.durability = cycle_query.durability; - self.dependencies.clone_from(&cycle_query.dependencies); - } -} - -/// A unique identifier for a particular runtime. Each time you create -/// a snapshot, a fresh `RuntimeId` is generated. Once a snapshot is -/// complete, its `RuntimeId` may potentially be re-used. -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct RuntimeId { - counter: u32, -} - -#[derive(Clone, Debug)] -pub(crate) struct StampedValue { - pub(crate) value: V, - pub(crate) durability: Durability, - pub(crate) changed_at: Revision, -} - -struct RevisionGuard { - shared_state: Arc, -} - -impl RevisionGuard { - fn new(shared_state: &Arc) -> Self { - // Subtle: we use a "recursive" lock here so that it is not an - // error to acquire a read-lock when one is already held (this - // happens when a query uses `snapshot` to spawn off parallel - // workers, for example). - // - // This has the side-effect that we are responsible to ensure - // that people contending for the write lock do not starve, - // but this is what we achieve via the cancellation mechanism. - // - // (In particular, since we only ever have one "mutating - // handle" to the database, the only contention for the global - // query lock occurs when there are "futures" evaluating - // queries in parallel, and those futures hold a read-lock - // already, so the starvation problem is more about them bring - // themselves to a close, versus preventing other people from - // *starting* work). - unsafe { - shared_state.query_lock.raw().lock_shared_recursive(); - } - - Self { shared_state: shared_state.clone() } - } -} - -impl Drop for RevisionGuard { - fn drop(&mut self) { - // Release our read-lock without using RAII. As documented in - // `Snapshot::new` above, this requires the unsafe keyword. - unsafe { - self.shared_state.query_lock.raw().unlock_shared(); - } - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/runtime/dependency_graph.rs b/src/tools/rust-analyzer/crates/salsa/src/runtime/dependency_graph.rs deleted file mode 100644 index ed1d499f637..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/runtime/dependency_graph.rs +++ /dev/null @@ -1,250 +0,0 @@ -use triomphe::Arc; - -use crate::{DatabaseKeyIndex, RuntimeId}; -use parking_lot::{Condvar, MutexGuard}; -use rustc_hash::FxHashMap; -use smallvec::SmallVec; - -use super::{ActiveQuery, WaitResult}; - -type QueryStack = Vec; - -#[derive(Debug, Default)] -pub(super) struct DependencyGraph { - /// A `(K -> V)` pair in this map indicates that the runtime - /// `K` is blocked on some query executing in the runtime `V`. - /// This encodes a graph that must be acyclic (or else deadlock - /// will result). - edges: FxHashMap, - - /// Encodes the `RuntimeId` that are blocked waiting for the result - /// of a given query. - query_dependents: FxHashMap>, - - /// When a key K completes which had dependent queries Qs blocked on it, - /// it stores its `WaitResult` here. As they wake up, each query Q in Qs will - /// come here to fetch their results. - wait_results: FxHashMap, -} - -#[derive(Debug)] -struct Edge { - blocked_on_id: RuntimeId, - blocked_on_key: DatabaseKeyIndex, - stack: QueryStack, - - /// Signalled whenever a query with dependents completes. - /// Allows those dependents to check if they are ready to unblock. - condvar: Arc, -} - -impl DependencyGraph { - /// True if `from_id` depends on `to_id`. - /// - /// (i.e., there is a path from `from_id` to `to_id` in the graph.) - pub(super) fn depends_on(&mut self, from_id: RuntimeId, to_id: RuntimeId) -> bool { - let mut p = from_id; - while let Some(q) = self.edges.get(&p).map(|edge| edge.blocked_on_id) { - if q == to_id { - return true; - } - - p = q; - } - p == to_id - } - - /// Invokes `closure` with a `&mut ActiveQuery` for each query that participates in the cycle. - /// The cycle runs as follows: - /// - /// 1. The runtime `from_id`, which has the stack `from_stack`, would like to invoke `database_key`... - /// 2. ...but `database_key` is already being executed by `to_id`... - /// 3. ...and `to_id` is transitively dependent on something which is present on `from_stack`. - pub(super) fn for_each_cycle_participant( - &mut self, - from_id: RuntimeId, - from_stack: &mut QueryStack, - database_key: DatabaseKeyIndex, - to_id: RuntimeId, - mut closure: impl FnMut(&mut [ActiveQuery]), - ) { - debug_assert!(self.depends_on(to_id, from_id)); - - // To understand this algorithm, consider this [drawing](https://is.gd/TGLI9v): - // - // database_key = QB2 - // from_id = A - // to_id = B - // from_stack = [QA1, QA2, QA3] - // - // self.edges[B] = { C, QC2, [QB1..QB3] } - // self.edges[C] = { A, QA2, [QC1..QC3] } - // - // The cyclic - // edge we have - // failed to add. - // : - // A : B C - // : - // QA1 v QB1 QC1 - // ┌► QA2 ┌──► QB2 ┌─► QC2 - // │ QA3 ───┘ QB3 ──┘ QC3 ───┐ - // │ │ - // └───────────────────────────────┘ - // - // Final output: [QB2, QB3, QC2, QC3, QA2, QA3] - - let mut id = to_id; - let mut key = database_key; - while id != from_id { - // Looking at the diagram above, the idea is to - // take the edge from `to_id` starting at `key` - // (inclusive) and down to the end. We can then - // load up the next thread (i.e., we start at B/QB2, - // and then load up the dependency on C/QC2). - let edge = self.edges.get_mut(&id).unwrap(); - let prefix = edge.stack.iter_mut().take_while(|p| p.database_key_index != key).count(); - closure(&mut edge.stack[prefix..]); - id = edge.blocked_on_id; - key = edge.blocked_on_key; - } - - // Finally, we copy in the results from `from_stack`. - let prefix = from_stack.iter_mut().take_while(|p| p.database_key_index != key).count(); - closure(&mut from_stack[prefix..]); - } - - /// Unblock each blocked runtime (excluding the current one) if some - /// query executing in that runtime is participating in cycle fallback. - /// - /// Returns a boolean (Current, Others) where: - /// * Current is true if the current runtime has cycle participants - /// with fallback; - /// * Others is true if other runtimes were unblocked. - pub(super) fn maybe_unblock_runtimes_in_cycle( - &mut self, - from_id: RuntimeId, - from_stack: &QueryStack, - database_key: DatabaseKeyIndex, - to_id: RuntimeId, - ) -> (bool, bool) { - // See diagram in `for_each_cycle_participant`. - let mut id = to_id; - let mut key = database_key; - let mut others_unblocked = false; - while id != from_id { - let edge = self.edges.get(&id).unwrap(); - let prefix = edge.stack.iter().take_while(|p| p.database_key_index != key).count(); - let next_id = edge.blocked_on_id; - let next_key = edge.blocked_on_key; - - if let Some(cycle) = edge.stack[prefix..].iter().rev().find_map(|aq| aq.cycle.clone()) { - // Remove `id` from the list of runtimes blocked on `next_key`: - self.query_dependents.get_mut(&next_key).unwrap().retain(|r| *r != id); - - // Unblock runtime so that it can resume execution once lock is released: - self.unblock_runtime(id, WaitResult::Cycle(cycle)); - - others_unblocked = true; - } - - id = next_id; - key = next_key; - } - - let prefix = from_stack.iter().take_while(|p| p.database_key_index != key).count(); - let this_unblocked = from_stack[prefix..].iter().any(|aq| aq.cycle.is_some()); - - (this_unblocked, others_unblocked) - } - - /// Modifies the graph so that `from_id` is blocked - /// on `database_key`, which is being computed by - /// `to_id`. - /// - /// For this to be reasonable, the lock on the - /// results table for `database_key` must be held. - /// This ensures that computing `database_key` doesn't - /// complete before `block_on` executes. - /// - /// Preconditions: - /// * No path from `to_id` to `from_id` - /// (i.e., `me.depends_on(to_id, from_id)` is false) - /// * `held_mutex` is a read lock (or stronger) on `database_key` - pub(super) fn block_on( - mut me: MutexGuard<'_, Self>, - from_id: RuntimeId, - database_key: DatabaseKeyIndex, - to_id: RuntimeId, - from_stack: QueryStack, - query_mutex_guard: QueryMutexGuard, - ) -> (QueryStack, WaitResult) { - let condvar = me.add_edge(from_id, database_key, to_id, from_stack); - - // Release the mutex that prevents `database_key` - // from completing, now that the edge has been added. - drop(query_mutex_guard); - - loop { - if let Some(stack_and_result) = me.wait_results.remove(&from_id) { - debug_assert!(!me.edges.contains_key(&from_id)); - return stack_and_result; - } - condvar.wait(&mut me); - } - } - - /// Helper for `block_on`: performs actual graph modification - /// to add a dependency edge from `from_id` to `to_id`, which is - /// computing `database_key`. - fn add_edge( - &mut self, - from_id: RuntimeId, - database_key: DatabaseKeyIndex, - to_id: RuntimeId, - from_stack: QueryStack, - ) -> Arc { - assert_ne!(from_id, to_id); - debug_assert!(!self.edges.contains_key(&from_id)); - debug_assert!(!self.depends_on(to_id, from_id)); - - let condvar = Arc::new(Condvar::new()); - self.edges.insert( - from_id, - Edge { - blocked_on_id: to_id, - blocked_on_key: database_key, - stack: from_stack, - condvar: condvar.clone(), - }, - ); - self.query_dependents.entry(database_key).or_default().push(from_id); - condvar - } - - /// Invoked when runtime `to_id` completes executing - /// `database_key`. - pub(super) fn unblock_runtimes_blocked_on( - &mut self, - database_key: DatabaseKeyIndex, - wait_result: WaitResult, - ) { - let dependents = self.query_dependents.remove(&database_key).unwrap_or_default(); - - for from_id in dependents { - self.unblock_runtime(from_id, wait_result.clone()); - } - } - - /// Unblock the runtime with the given id with the given wait-result. - /// This will cause it resume execution (though it will have to grab - /// the lock on this data structure first, to recover the wait result). - fn unblock_runtime(&mut self, id: RuntimeId, wait_result: WaitResult) { - let edge = self.edges.remove(&id).expect("not blocked"); - self.wait_results.insert(id, (edge.stack, wait_result)); - - // Now that we have inserted the `wait_results`, - // notify the thread. - edge.condvar.notify_one(); - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/runtime/local_state.rs b/src/tools/rust-analyzer/crates/salsa/src/runtime/local_state.rs deleted file mode 100644 index 73869671886..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/runtime/local_state.rs +++ /dev/null @@ -1,204 +0,0 @@ -use tracing::debug; -use triomphe::ThinArc; - -use crate::durability::Durability; -use crate::runtime::ActiveQuery; -use crate::runtime::Revision; -use crate::Cycle; -use crate::DatabaseKeyIndex; -use std::cell::RefCell; - -/// State that is specific to a single execution thread. -/// -/// Internally, this type uses ref-cells. -/// -/// **Note also that all mutations to the database handle (and hence -/// to the local-state) must be undone during unwinding.** -pub(super) struct LocalState { - /// Vector of active queries. - /// - /// This is normally `Some`, but it is set to `None` - /// while the query is blocked waiting for a result. - /// - /// Unwinding note: pushes onto this vector must be popped -- even - /// during unwinding. - query_stack: RefCell>>, -} - -/// Summarizes "all the inputs that a query used" -#[derive(Debug, Clone)] -pub(crate) struct QueryRevisions { - /// The most revision in which some input changed. - pub(crate) changed_at: Revision, - - /// Minimum durability of the inputs to this query. - pub(crate) durability: Durability, - - /// Whether the input is untracked. - /// Invariant: if `untracked`, `inputs` is `None`. - /// Why is this encoded like this and not a proper enum? Struct size, this saves us 8 bytes. - pub(crate) untracked: bool, - - /// The inputs that went into our query, if we are tracking them. - pub(crate) inputs: Option>, -} - -impl Default for LocalState { - fn default() -> Self { - LocalState { query_stack: RefCell::new(Some(Vec::new())) } - } -} - -impl LocalState { - #[inline] - pub(super) fn push_query(&self, database_key_index: DatabaseKeyIndex) -> ActiveQueryGuard<'_> { - let mut query_stack = self.query_stack.borrow_mut(); - let query_stack = query_stack.as_mut().expect("local stack taken"); - query_stack.push(ActiveQuery::new(database_key_index)); - ActiveQueryGuard { local_state: self, database_key_index, push_len: query_stack.len() } - } - - fn with_query_stack(&self, c: impl FnOnce(&mut Vec) -> R) -> R { - c(self.query_stack.borrow_mut().as_mut().expect("query stack taken")) - } - - pub(super) fn query_in_progress(&self) -> bool { - self.with_query_stack(|stack| !stack.is_empty()) - } - - pub(super) fn active_query(&self) -> Option { - self.with_query_stack(|stack| { - stack.last().map(|active_query| active_query.database_key_index) - }) - } - - pub(super) fn report_query_read_and_unwind_if_cycle_resulted( - &self, - input: DatabaseKeyIndex, - durability: Durability, - changed_at: Revision, - ) { - debug!( - "report_query_read_and_unwind_if_cycle_resulted(input={:?}, durability={:?}, changed_at={:?})", - input, durability, changed_at - ); - self.with_query_stack(|stack| { - if let Some(top_query) = stack.last_mut() { - top_query.add_read(input, durability, changed_at); - - // We are a cycle participant: - // - // C0 --> ... --> Ci --> Ci+1 -> ... -> Cn --> C0 - // ^ ^ - // : | - // This edge -----+ | - // | - // | - // N0 - // - // In this case, the value we have just read from `Ci+1` - // is actually the cycle fallback value and not especially - // interesting. We unwind now with `CycleParticipant` to avoid - // executing the rest of our query function. This unwinding - // will be caught and our own fallback value will be used. - // - // Note that `Ci+1` may` have *other* callers who are not - // participants in the cycle (e.g., N0 in the graph above). - // They will not have the `cycle` marker set in their - // stack frames, so they will just read the fallback value - // from `Ci+1` and continue on their merry way. - if let Some(cycle) = &top_query.cycle { - cycle.clone().throw() - } - } - }) - } - - pub(super) fn report_untracked_read(&self, current_revision: Revision) { - self.with_query_stack(|stack| { - if let Some(top_query) = stack.last_mut() { - top_query.add_untracked_read(current_revision); - } - }) - } - - /// Update the top query on the stack to act as though it read a value - /// of durability `durability` which changed in `revision`. - pub(super) fn report_synthetic_read(&self, durability: Durability, revision: Revision) { - self.with_query_stack(|stack| { - if let Some(top_query) = stack.last_mut() { - top_query.add_synthetic_read(durability, revision); - } - }) - } - - /// Takes the query stack and returns it. This is used when - /// the current thread is blocking. The stack must be restored - /// with [`Self::restore_query_stack`] when the thread unblocks. - pub(super) fn take_query_stack(&self) -> Vec { - self.query_stack.take().expect("query stack already taken") - } - - /// Restores a query stack taken with [`Self::take_query_stack`] once - /// the thread unblocks. - pub(super) fn restore_query_stack(&self, stack: Vec) { - assert!(self.query_stack.borrow().is_none(), "query stack not taken"); - self.query_stack.replace(Some(stack)); - } -} - -impl std::panic::RefUnwindSafe for LocalState {} - -/// When a query is pushed onto the `active_query` stack, this guard -/// is returned to represent its slot. The guard can be used to pop -/// the query from the stack -- in the case of unwinding, the guard's -/// destructor will also remove the query. -pub(crate) struct ActiveQueryGuard<'me> { - local_state: &'me LocalState, - push_len: usize, - database_key_index: DatabaseKeyIndex, -} - -impl ActiveQueryGuard<'_> { - fn pop_helper(&self) -> ActiveQuery { - self.local_state.with_query_stack(|stack| { - // Sanity check: pushes and pops should be balanced. - assert_eq!(stack.len(), self.push_len); - debug_assert_eq!(stack.last().unwrap().database_key_index, self.database_key_index); - stack.pop().unwrap() - }) - } - - /// Invoked when the query has successfully completed execution. - pub(super) fn complete(self) -> ActiveQuery { - let query = self.pop_helper(); - std::mem::forget(self); - query - } - - /// Pops an active query from the stack. Returns the [`QueryRevisions`] - /// which summarizes the other queries that were accessed during this - /// query's execution. - #[inline] - pub(crate) fn pop(self) -> QueryRevisions { - // Extract accumulated inputs. - let popped_query = self.complete(); - - // If this frame were a cycle participant, it would have unwound. - assert!(popped_query.cycle.is_none()); - - popped_query.revisions() - } - - /// If the active query is registered as a cycle participant, remove and - /// return that cycle. - pub(crate) fn take_cycle(&self) -> Option { - self.local_state.with_query_stack(|stack| stack.last_mut()?.cycle.take()) - } -} - -impl Drop for ActiveQueryGuard<'_> { - fn drop(&mut self) { - self.pop_helper(); - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/src/storage.rs b/src/tools/rust-analyzer/crates/salsa/src/storage.rs deleted file mode 100644 index e0acf44041b..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/src/storage.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{plumbing::DatabaseStorageTypes, Runtime}; -use triomphe::Arc; - -/// Stores the cached results and dependency information for all the queries -/// defined on your salsa database. Also embeds a [`Runtime`] which is used to -/// manage query execution. Every database must include a `storage: -/// Storage` field. -pub struct Storage { - query_store: Arc, - runtime: Runtime, -} - -impl Default for Storage { - fn default() -> Self { - Self { query_store: Default::default(), runtime: Default::default() } - } -} - -impl Storage { - /// Gives access to the underlying salsa runtime. - pub fn salsa_runtime(&self) -> &Runtime { - &self.runtime - } - - /// Gives access to the underlying salsa runtime. - pub fn salsa_runtime_mut(&mut self) -> &mut Runtime { - &mut self.runtime - } - - /// Access the query storage tables. Not meant to be used directly by end - /// users. - pub fn query_store(&self) -> &DB::DatabaseStorage { - &self.query_store - } - - /// Access the query storage tables. Not meant to be used directly by end - /// users. - pub fn query_store_mut(&mut self) -> (&DB::DatabaseStorage, &mut Runtime) { - (&self.query_store, &mut self.runtime) - } - - /// Returns a "snapshotted" storage, suitable for use in a forked database. - /// This snapshot hold a read-lock on the global state, which means that any - /// attempt to `set` an input will block until the forked runtime is - /// dropped. See `ParallelDatabase::snapshot` for more information. - /// - /// **Warning.** This second handle is intended to be used from a separate - /// thread. Using two database handles from the **same thread** can lead to - /// deadlock. - pub fn snapshot(&self) -> Self { - Storage { query_store: self.query_store.clone(), runtime: self.runtime.snapshot() } - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/cycles.rs b/src/tools/rust-analyzer/crates/salsa/tests/cycles.rs deleted file mode 100644 index e9bddfc630e..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/cycles.rs +++ /dev/null @@ -1,492 +0,0 @@ -use std::panic::UnwindSafe; - -use expect_test::expect; -use salsa::{Durability, ParallelDatabase, Snapshot}; - -// Axes: -// -// Threading -// * Intra-thread -// * Cross-thread -- part of cycle is on one thread, part on another -// -// Recovery strategies: -// * Panic -// * Fallback -// * Mixed -- multiple strategies within cycle participants -// -// Across revisions: -// * N/A -- only one revision -// * Present in new revision, not old -// * Present in old revision, not new -// * Present in both revisions -// -// Dependencies -// * Tracked -// * Untracked -- cycle participant(s) contain untracked reads -// -// Layers -// * Direct -- cycle participant is directly invoked from test -// * Indirect -- invoked a query that invokes the cycle -// -// -// | Thread | Recovery | Old, New | Dep style | Layers | Test Name | -// | ------ | -------- | -------- | --------- | ------ | --------- | -// | Intra | Panic | N/A | Tracked | direct | cycle_memoized | -// | Intra | Panic | N/A | Untracked | direct | cycle_volatile | -// | Intra | Fallback | N/A | Tracked | direct | cycle_cycle | -// | Intra | Fallback | N/A | Tracked | indirect | inner_cycle | -// | Intra | Fallback | Both | Tracked | direct | cycle_revalidate | -// | Intra | Fallback | New | Tracked | direct | cycle_appears | -// | Intra | Fallback | Old | Tracked | direct | cycle_disappears | -// | Intra | Fallback | Old | Tracked | direct | cycle_disappears_durability | -// | Intra | Mixed | N/A | Tracked | direct | cycle_mixed_1 | -// | Intra | Mixed | N/A | Tracked | direct | cycle_mixed_2 | -// | Cross | Fallback | N/A | Tracked | both | parallel/cycles.rs: recover_parallel_cycle | -// | Cross | Panic | N/A | Tracked | both | parallel/cycles.rs: panic_parallel_cycle | - -#[derive(PartialEq, Eq, Hash, Clone, Debug)] -struct Error { - cycle: Vec, -} - -#[salsa::database(GroupStruct)] -#[derive(Default)] -struct DatabaseImpl { - storage: salsa::Storage, -} - -impl salsa::Database for DatabaseImpl {} - -impl ParallelDatabase for DatabaseImpl { - fn snapshot(&self) -> Snapshot { - Snapshot::new(DatabaseImpl { storage: self.storage.snapshot() }) - } -} - -/// The queries A, B, and C in `Database` can be configured -/// to invoke one another in arbitrary ways using this -/// enum. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum CycleQuery { - None, - A, - B, - C, - AthenC, -} - -#[salsa::query_group(GroupStruct)] -trait Database: salsa::Database { - // `a` and `b` depend on each other and form a cycle - fn memoized_a(&self) -> (); - fn memoized_b(&self) -> (); - fn volatile_a(&self) -> (); - fn volatile_b(&self) -> (); - - #[salsa::input] - fn a_invokes(&self) -> CycleQuery; - - #[salsa::input] - fn b_invokes(&self) -> CycleQuery; - - #[salsa::input] - fn c_invokes(&self) -> CycleQuery; - - #[salsa::cycle(recover_a)] - fn cycle_a(&self) -> Result<(), Error>; - - #[salsa::cycle(recover_b)] - fn cycle_b(&self) -> Result<(), Error>; - - fn cycle_c(&self) -> Result<(), Error>; -} - -fn recover_a(db: &dyn Database, cycle: &salsa::Cycle) -> Result<(), Error> { - Err(Error { cycle: cycle.all_participants(db) }) -} - -fn recover_b(db: &dyn Database, cycle: &salsa::Cycle) -> Result<(), Error> { - Err(Error { cycle: cycle.all_participants(db) }) -} - -fn memoized_a(db: &dyn Database) { - db.memoized_b() -} - -fn memoized_b(db: &dyn Database) { - db.memoized_a() -} - -fn volatile_a(db: &dyn Database) { - db.salsa_runtime().report_untracked_read(); - db.volatile_b() -} - -fn volatile_b(db: &dyn Database) { - db.salsa_runtime().report_untracked_read(); - db.volatile_a() -} - -impl CycleQuery { - fn invoke(self, db: &dyn Database) -> Result<(), Error> { - match self { - CycleQuery::A => db.cycle_a(), - CycleQuery::B => db.cycle_b(), - CycleQuery::C => db.cycle_c(), - CycleQuery::AthenC => { - let _ = db.cycle_a(); - db.cycle_c() - } - CycleQuery::None => Ok(()), - } - } -} - -fn cycle_a(db: &dyn Database) -> Result<(), Error> { - db.a_invokes().invoke(db) -} - -fn cycle_b(db: &dyn Database) -> Result<(), Error> { - db.b_invokes().invoke(db) -} - -fn cycle_c(db: &dyn Database) -> Result<(), Error> { - db.c_invokes().invoke(db) -} - -#[track_caller] -fn extract_cycle(f: impl FnOnce() + UnwindSafe) -> salsa::Cycle { - let v = std::panic::catch_unwind(f); - if let Err(d) = &v { - if let Some(cycle) = d.downcast_ref::() { - return cycle.clone(); - } - } - panic!("unexpected value: {v:?}") -} - -#[test] -fn cycle_memoized() { - let db = DatabaseImpl::default(); - let cycle = extract_cycle(|| db.memoized_a()); - expect![[r#" - [ - "cycles::MemoizedAQuery::memoized_a(())", - "cycles::MemoizedBQuery::memoized_b(())", - ] - "#]] - .assert_debug_eq(&cycle.unexpected_participants(&db)); -} - -#[test] -fn cycle_volatile() { - let db = DatabaseImpl::default(); - let cycle = extract_cycle(|| db.volatile_a()); - expect![[r#" - [ - "cycles::VolatileAQuery::volatile_a(())", - "cycles::VolatileBQuery::volatile_b(())", - ] - "#]] - .assert_debug_eq(&cycle.unexpected_participants(&db)); -} - -#[test] -fn cycle_cycle() { - let mut query = DatabaseImpl::default(); - - // A --> B - // ^ | - // +-----+ - - query.set_a_invokes(CycleQuery::B); - query.set_b_invokes(CycleQuery::A); - - assert!(query.cycle_a().is_err()); -} - -#[test] -fn inner_cycle() { - let mut query = DatabaseImpl::default(); - - // A --> B <-- C - // ^ | - // +-----+ - - query.set_a_invokes(CycleQuery::B); - query.set_b_invokes(CycleQuery::A); - query.set_c_invokes(CycleQuery::B); - - let err = query.cycle_c(); - assert!(err.is_err()); - let cycle = err.unwrap_err().cycle; - expect![[r#" - [ - "cycles::CycleAQuery::cycle_a(())", - "cycles::CycleBQuery::cycle_b(())", - ] - "#]] - .assert_debug_eq(&cycle); -} - -#[test] -fn cycle_revalidate() { - let mut db = DatabaseImpl::default(); - - // A --> B - // ^ | - // +-----+ - db.set_a_invokes(CycleQuery::B); - db.set_b_invokes(CycleQuery::A); - - assert!(db.cycle_a().is_err()); - db.set_b_invokes(CycleQuery::A); // same value as default - assert!(db.cycle_a().is_err()); -} - -#[test] -fn cycle_revalidate_unchanged_twice() { - let mut db = DatabaseImpl::default(); - - // A --> B - // ^ | - // +-----+ - db.set_a_invokes(CycleQuery::B); - db.set_b_invokes(CycleQuery::A); - - assert!(db.cycle_a().is_err()); - db.set_c_invokes(CycleQuery::A); // force new revisi5on - - // on this run - expect![[r#" - Err( - Error { - cycle: [ - "cycles::CycleAQuery::cycle_a(())", - "cycles::CycleBQuery::cycle_b(())", - ], - }, - ) - "#]] - .assert_debug_eq(&db.cycle_a()); -} - -#[test] -fn cycle_appears() { - let mut db = DatabaseImpl::default(); - - // A --> B - db.set_a_invokes(CycleQuery::B); - db.set_b_invokes(CycleQuery::None); - assert!(db.cycle_a().is_ok()); - - // A --> B - // ^ | - // +-----+ - db.set_b_invokes(CycleQuery::A); - tracing::debug!("Set Cycle Leaf"); - assert!(db.cycle_a().is_err()); -} - -#[test] -fn cycle_disappears() { - let mut db = DatabaseImpl::default(); - - // A --> B - // ^ | - // +-----+ - db.set_a_invokes(CycleQuery::B); - db.set_b_invokes(CycleQuery::A); - assert!(db.cycle_a().is_err()); - - // A --> B - db.set_b_invokes(CycleQuery::None); - assert!(db.cycle_a().is_ok()); -} - -/// A variant on `cycle_disappears` in which the values of -/// `a_invokes` and `b_invokes` are set with durability values. -/// If we are not careful, this could cause us to overlook -/// the fact that the cycle will no longer occur. -#[test] -fn cycle_disappears_durability() { - let mut db = DatabaseImpl::default(); - db.set_a_invokes_with_durability(CycleQuery::B, Durability::LOW); - db.set_b_invokes_with_durability(CycleQuery::A, Durability::HIGH); - - let res = db.cycle_a(); - assert!(res.is_err()); - - // At this point, `a` read `LOW` input, and `b` read `HIGH` input. However, - // because `b` participates in the same cycle as `a`, its final durability - // should be `LOW`. - // - // Check that setting a `LOW` input causes us to re-execute `b` query, and - // observe that the cycle goes away. - db.set_a_invokes_with_durability(CycleQuery::None, Durability::LOW); - - let res = db.cycle_b(); - assert!(res.is_ok()); -} - -#[test] -fn cycle_mixed_1() { - let mut db = DatabaseImpl::default(); - // A --> B <-- C - // | ^ - // +-----+ - db.set_a_invokes(CycleQuery::B); - db.set_b_invokes(CycleQuery::C); - db.set_c_invokes(CycleQuery::B); - - let u = db.cycle_c(); - expect![[r#" - Err( - Error { - cycle: [ - "cycles::CycleBQuery::cycle_b(())", - "cycles::CycleCQuery::cycle_c(())", - ], - }, - ) - "#]] - .assert_debug_eq(&u); -} - -#[test] -fn cycle_mixed_2() { - let mut db = DatabaseImpl::default(); - - // Configuration: - // - // A --> B --> C - // ^ | - // +-----------+ - db.set_a_invokes(CycleQuery::B); - db.set_b_invokes(CycleQuery::C); - db.set_c_invokes(CycleQuery::A); - - let u = db.cycle_a(); - expect![[r#" - Err( - Error { - cycle: [ - "cycles::CycleAQuery::cycle_a(())", - "cycles::CycleBQuery::cycle_b(())", - "cycles::CycleCQuery::cycle_c(())", - ], - }, - ) - "#]] - .assert_debug_eq(&u); -} - -#[test] -fn cycle_deterministic_order() { - // No matter whether we start from A or B, we get the same set of participants: - let db = || { - let mut db = DatabaseImpl::default(); - // A --> B - // ^ | - // +-----+ - db.set_a_invokes(CycleQuery::B); - db.set_b_invokes(CycleQuery::A); - db - }; - let a = db().cycle_a(); - let b = db().cycle_b(); - expect![[r#" - ( - Err( - Error { - cycle: [ - "cycles::CycleAQuery::cycle_a(())", - "cycles::CycleBQuery::cycle_b(())", - ], - }, - ), - Err( - Error { - cycle: [ - "cycles::CycleAQuery::cycle_a(())", - "cycles::CycleBQuery::cycle_b(())", - ], - }, - ), - ) - "#]] - .assert_debug_eq(&(a, b)); -} - -#[test] -fn cycle_multiple() { - // No matter whether we start from A or B, we get the same set of participants: - let mut db = DatabaseImpl::default(); - - // Configuration: - // - // A --> B <-- C - // ^ | ^ - // +-----+ | - // | | - // +-----+ - // - // Here, conceptually, B encounters a cycle with A and then - // recovers. - db.set_a_invokes(CycleQuery::B); - db.set_b_invokes(CycleQuery::AthenC); - db.set_c_invokes(CycleQuery::B); - - let c = db.cycle_c(); - let b = db.cycle_b(); - let a = db.cycle_a(); - expect![[r#" - ( - Err( - Error { - cycle: [ - "cycles::CycleAQuery::cycle_a(())", - "cycles::CycleBQuery::cycle_b(())", - ], - }, - ), - Err( - Error { - cycle: [ - "cycles::CycleAQuery::cycle_a(())", - "cycles::CycleBQuery::cycle_b(())", - ], - }, - ), - Err( - Error { - cycle: [ - "cycles::CycleAQuery::cycle_a(())", - "cycles::CycleBQuery::cycle_b(())", - ], - }, - ), - ) - "#]] - .assert_debug_eq(&(a, b, c)); -} - -#[test] -fn cycle_recovery_set_but_not_participating() { - let mut db = DatabaseImpl::default(); - - // A --> C -+ - // ^ | - // +--+ - db.set_a_invokes(CycleQuery::C); - db.set_c_invokes(CycleQuery::C); - - // Here we expect C to panic and A not to recover: - let r = extract_cycle(|| drop(db.cycle_a())); - expect![[r#" - [ - "cycles::CycleCQuery::cycle_c(())", - ] - "#]] - .assert_debug_eq(&r.all_participants(&db)); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/dyn_trait.rs b/src/tools/rust-analyzer/crates/salsa/tests/dyn_trait.rs deleted file mode 100644 index 09ebc5c4ce4..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/dyn_trait.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Test that you can implement a query using a `dyn Trait` setup. - -#[salsa::database(DynTraitStorage)] -#[derive(Default)] -struct DynTraitDatabase { - storage: salsa::Storage, -} - -impl salsa::Database for DynTraitDatabase {} - -#[salsa::query_group(DynTraitStorage)] -trait DynTrait { - #[salsa::input] - fn input(&self, x: u32) -> u32; - - fn output(&self, x: u32) -> u32; -} - -fn output(db: &dyn DynTrait, x: u32) -> u32 { - db.input(x) * 2 -} - -#[test] -fn dyn_trait() { - let mut query = DynTraitDatabase::default(); - query.set_input(22, 23); - assert_eq!(query.output(22), 46); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/incremental/constants.rs b/src/tools/rust-analyzer/crates/salsa/tests/incremental/constants.rs deleted file mode 100644 index 32bfbc4564b..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/incremental/constants.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::implementation::{TestContext, TestContextImpl}; -use salsa::debug::DebugQueryTable; -use salsa::Durability; - -#[salsa::query_group(Constants)] -pub(crate) trait ConstantsDatabase: TestContext { - #[salsa::input] - fn input(&self, key: char) -> usize; - - fn add(&self, key1: char, key2: char) -> usize; - - fn add3(&self, key1: char, key2: char, key3: char) -> usize; -} - -fn add(db: &dyn ConstantsDatabase, key1: char, key2: char) -> usize { - db.log().add(format!("add({key1}, {key2})")); - db.input(key1) + db.input(key2) -} - -fn add3(db: &dyn ConstantsDatabase, key1: char, key2: char, key3: char) -> usize { - db.log().add(format!("add3({key1}, {key2}, {key3})")); - db.add(key1, key2) + db.input(key3) -} - -// Test we can assign a constant and things will be correctly -// recomputed afterwards. -#[test] -fn invalidate_constant() { - let db = &mut TestContextImpl::default(); - db.set_input_with_durability('a', 44, Durability::HIGH); - db.set_input_with_durability('b', 22, Durability::HIGH); - assert_eq!(db.add('a', 'b'), 66); - - db.set_input_with_durability('a', 66, Durability::HIGH); - assert_eq!(db.add('a', 'b'), 88); -} - -#[test] -fn invalidate_constant_1() { - let db = &mut TestContextImpl::default(); - - // Not constant: - db.set_input('a', 44); - assert_eq!(db.add('a', 'a'), 88); - - // Becomes constant: - db.set_input_with_durability('a', 44, Durability::HIGH); - assert_eq!(db.add('a', 'a'), 88); - - // Invalidates: - db.set_input_with_durability('a', 33, Durability::HIGH); - assert_eq!(db.add('a', 'a'), 66); -} - -// Test cases where we assign same value to 'a' after declaring it a -// constant. -#[test] -fn set_after_constant_same_value() { - let db = &mut TestContextImpl::default(); - db.set_input_with_durability('a', 44, Durability::HIGH); - db.set_input_with_durability('a', 44, Durability::HIGH); - db.set_input('a', 44); -} - -#[test] -fn not_constant() { - let mut db = TestContextImpl::default(); - - db.set_input('a', 22); - db.set_input('b', 44); - assert_eq!(db.add('a', 'b'), 66); - assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b'))); -} - -#[test] -fn durability() { - let mut db = TestContextImpl::default(); - - db.set_input_with_durability('a', 22, Durability::HIGH); - db.set_input_with_durability('b', 44, Durability::HIGH); - assert_eq!(db.add('a', 'b'), 66); - assert_eq!(Durability::HIGH, AddQuery.in_db(&db).durability(('a', 'b'))); -} - -#[test] -fn mixed_constant() { - let mut db = TestContextImpl::default(); - - db.set_input_with_durability('a', 22, Durability::HIGH); - db.set_input('b', 44); - assert_eq!(db.add('a', 'b'), 66); - assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b'))); -} - -#[test] -fn becomes_constant_with_change() { - let mut db = TestContextImpl::default(); - - db.set_input('a', 22); - db.set_input('b', 44); - assert_eq!(db.add('a', 'b'), 66); - assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b'))); - - db.set_input_with_durability('a', 23, Durability::HIGH); - assert_eq!(db.add('a', 'b'), 67); - assert_eq!(Durability::LOW, AddQuery.in_db(&db).durability(('a', 'b'))); - - db.set_input_with_durability('b', 45, Durability::HIGH); - assert_eq!(db.add('a', 'b'), 68); - assert_eq!(Durability::HIGH, AddQuery.in_db(&db).durability(('a', 'b'))); - - db.set_input_with_durability('b', 45, Durability::MEDIUM); - assert_eq!(db.add('a', 'b'), 68); - assert_eq!(Durability::MEDIUM, AddQuery.in_db(&db).durability(('a', 'b'))); -} - -// Test a subtle case in which an input changes from constant to -// non-constant, but its value doesn't change. If we're not careful, -// this can cause us to incorrectly consider derived values as still -// being constant. -#[test] -fn constant_to_non_constant() { - let mut db = TestContextImpl::default(); - - db.set_input_with_durability('a', 11, Durability::HIGH); - db.set_input_with_durability('b', 22, Durability::HIGH); - db.set_input_with_durability('c', 33, Durability::HIGH); - - // Here, `add3` invokes `add`, which yields 33. Both calls are - // constant. - assert_eq!(db.add3('a', 'b', 'c'), 66); - - db.set_input('a', 11); - - // Here, `add3` invokes `add`, which *still* yields 33, but which - // is no longer constant. Since value didn't change, we might - // preserve `add3` unchanged, not noticing that it is no longer - // constant. - assert_eq!(db.add3('a', 'b', 'c'), 66); - - // In that case, we would not get the correct result here, when - // 'a' changes *again*. - db.set_input('a', 22); - assert_eq!(db.add3('a', 'b', 'c'), 77); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/incremental/counter.rs b/src/tools/rust-analyzer/crates/salsa/tests/incremental/counter.rs deleted file mode 100644 index c04857e24c9..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/incremental/counter.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::cell::Cell; - -#[derive(Default)] -pub(crate) struct Counter { - value: Cell, -} - -impl Counter { - pub(crate) fn increment(&self) -> usize { - let v = self.value.get(); - self.value.set(v + 1); - v - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/incremental/implementation.rs b/src/tools/rust-analyzer/crates/salsa/tests/incremental/implementation.rs deleted file mode 100644 index 84349134415..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/incremental/implementation.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::constants; -use crate::counter::Counter; -use crate::log::Log; -use crate::memoized_dep_inputs; -use crate::memoized_inputs; -use crate::memoized_volatile; - -pub(crate) trait TestContext: salsa::Database { - fn clock(&self) -> &Counter; - fn log(&self) -> &Log; -} - -#[salsa::database( - constants::Constants, - memoized_dep_inputs::MemoizedDepInputs, - memoized_inputs::MemoizedInputs, - memoized_volatile::MemoizedVolatile -)] -#[derive(Default)] -pub(crate) struct TestContextImpl { - storage: salsa::Storage, - clock: Counter, - log: Log, -} - -impl TestContextImpl { - #[track_caller] - pub(crate) fn assert_log(&self, expected_log: &[&str]) { - let expected_text = &format!("{expected_log:#?}"); - let actual_text = &format!("{:#?}", self.log().take()); - - if expected_text == actual_text { - return; - } - - #[allow(clippy::print_stdout)] - for diff in dissimilar::diff(expected_text, actual_text) { - match diff { - dissimilar::Chunk::Delete(l) => println!("-{l}"), - dissimilar::Chunk::Equal(l) => println!(" {l}"), - dissimilar::Chunk::Insert(r) => println!("+{r}"), - } - } - - panic!("incorrect log results"); - } -} - -impl TestContext for TestContextImpl { - fn clock(&self) -> &Counter { - &self.clock - } - - fn log(&self) -> &Log { - &self.log - } -} - -impl salsa::Database for TestContextImpl {} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/incremental/log.rs b/src/tools/rust-analyzer/crates/salsa/tests/incremental/log.rs deleted file mode 100644 index 1ee57fe667d..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/incremental/log.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::cell::RefCell; - -#[derive(Default)] -pub(crate) struct Log { - data: RefCell>, -} - -impl Log { - pub(crate) fn add(&self, text: impl Into) { - self.data.borrow_mut().push(text.into()); - } - - pub(crate) fn take(&self) -> Vec { - self.data.take() - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/incremental/main.rs b/src/tools/rust-analyzer/crates/salsa/tests/incremental/main.rs deleted file mode 100644 index bcd13c75f71..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/incremental/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod constants; -mod counter; -mod implementation; -mod log; -mod memoized_dep_inputs; -mod memoized_inputs; -mod memoized_volatile; - -fn main() {} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_dep_inputs.rs b/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_dep_inputs.rs deleted file mode 100644 index 4ea33e0c1a0..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_dep_inputs.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::implementation::{TestContext, TestContextImpl}; - -#[salsa::query_group(MemoizedDepInputs)] -pub(crate) trait MemoizedDepInputsContext: TestContext { - fn dep_memoized2(&self) -> usize; - fn dep_memoized1(&self) -> usize; - #[salsa::dependencies] - fn dep_derived1(&self) -> usize; - #[salsa::input] - fn dep_input1(&self) -> usize; - #[salsa::input] - fn dep_input2(&self) -> usize; -} - -fn dep_memoized2(db: &dyn MemoizedDepInputsContext) -> usize { - db.log().add("Memoized2 invoked"); - db.dep_memoized1() -} - -fn dep_memoized1(db: &dyn MemoizedDepInputsContext) -> usize { - db.log().add("Memoized1 invoked"); - db.dep_derived1() * 2 -} - -fn dep_derived1(db: &dyn MemoizedDepInputsContext) -> usize { - db.log().add("Derived1 invoked"); - db.dep_input1() / 2 -} - -#[test] -fn revalidate() { - let db = &mut TestContextImpl::default(); - - db.set_dep_input1(0); - - // Initial run starts from Memoized2: - let v = db.dep_memoized2(); - assert_eq!(v, 0); - db.assert_log(&["Memoized2 invoked", "Memoized1 invoked", "Derived1 invoked"]); - - // After that, we first try to validate Memoized1 but wind up - // running Memoized2. Note that we don't try to validate - // Derived1, so it is invoked by Memoized1. - db.set_dep_input1(44); - let v = db.dep_memoized2(); - assert_eq!(v, 44); - db.assert_log(&["Memoized1 invoked", "Derived1 invoked", "Memoized2 invoked"]); - - // Here validation of Memoized1 succeeds so Memoized2 never runs. - db.set_dep_input1(45); - let v = db.dep_memoized2(); - assert_eq!(v, 44); - db.assert_log(&["Memoized1 invoked", "Derived1 invoked"]); - - // Here, a change to input2 doesn't affect us, so nothing runs. - db.set_dep_input2(45); - let v = db.dep_memoized2(); - assert_eq!(v, 44); - db.assert_log(&[]); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_inputs.rs b/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_inputs.rs deleted file mode 100644 index 53d2ace8871..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_inputs.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::implementation::{TestContext, TestContextImpl}; - -#[salsa::query_group(MemoizedInputs)] -pub(crate) trait MemoizedInputsContext: TestContext { - fn max(&self) -> usize; - #[salsa::input] - fn input1(&self) -> usize; - #[salsa::input] - fn input2(&self) -> usize; -} - -fn max(db: &dyn MemoizedInputsContext) -> usize { - db.log().add("Max invoked"); - std::cmp::max(db.input1(), db.input2()) -} - -#[test] -fn revalidate() { - let db = &mut TestContextImpl::default(); - - db.set_input1(0); - db.set_input2(0); - - let v = db.max(); - assert_eq!(v, 0); - db.assert_log(&["Max invoked"]); - - let v = db.max(); - assert_eq!(v, 0); - db.assert_log(&[]); - - db.set_input1(44); - db.assert_log(&[]); - - let v = db.max(); - assert_eq!(v, 44); - db.assert_log(&["Max invoked"]); - - let v = db.max(); - assert_eq!(v, 44); - db.assert_log(&[]); - - db.set_input1(44); - db.assert_log(&[]); - db.set_input2(66); - db.assert_log(&[]); - db.set_input1(64); - db.assert_log(&[]); - - let v = db.max(); - assert_eq!(v, 66); - db.assert_log(&["Max invoked"]); - - let v = db.max(); - assert_eq!(v, 66); - db.assert_log(&[]); -} - -/// Test that invoking `set` on an input with the same value still -/// triggers a new revision. -#[test] -fn set_after_no_change() { - let db = &mut TestContextImpl::default(); - - db.set_input2(0); - - db.set_input1(44); - let v = db.max(); - assert_eq!(v, 44); - db.assert_log(&["Max invoked"]); - - db.set_input1(44); - let v = db.max(); - assert_eq!(v, 44); - db.assert_log(&["Max invoked"]); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_volatile.rs b/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_volatile.rs deleted file mode 100644 index 3dcc32eece3..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/incremental/memoized_volatile.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::implementation::{TestContext, TestContextImpl}; -use salsa::{Database, Durability}; - -#[salsa::query_group(MemoizedVolatile)] -pub(crate) trait MemoizedVolatileContext: TestContext { - // Queries for testing a "volatile" value wrapped by - // memoization. - fn memoized2(&self) -> usize; - fn memoized1(&self) -> usize; - fn volatile(&self) -> usize; -} - -fn memoized2(db: &dyn MemoizedVolatileContext) -> usize { - db.log().add("Memoized2 invoked"); - db.memoized1() -} - -fn memoized1(db: &dyn MemoizedVolatileContext) -> usize { - db.log().add("Memoized1 invoked"); - let v = db.volatile(); - v / 2 -} - -fn volatile(db: &dyn MemoizedVolatileContext) -> usize { - db.log().add("Volatile invoked"); - db.salsa_runtime().report_untracked_read(); - db.clock().increment() -} - -#[test] -fn volatile_x2() { - let query = TestContextImpl::default(); - - // Invoking volatile twice doesn't execute twice, because volatile - // queries are memoized by default. - query.volatile(); - query.volatile(); - query.assert_log(&["Volatile invoked"]); -} - -/// Test that: -/// -/// - On the first run of R0, we recompute everything. -/// - On the second run of R1, we recompute nothing. -/// - On the first run of R1, we recompute Memoized1 but not Memoized2 (since Memoized1 result -/// did not change). -/// - On the second run of R1, we recompute nothing. -/// - On the first run of R2, we recompute everything (since Memoized1 result *did* change). -#[test] -fn revalidate() { - let mut query = TestContextImpl::default(); - - query.memoized2(); - query.assert_log(&["Memoized2 invoked", "Memoized1 invoked", "Volatile invoked"]); - - query.memoized2(); - query.assert_log(&[]); - - // Second generation: volatile will change (to 1) but memoized1 - // will not (still 0, as 1/2 = 0) - query.synthetic_write(Durability::LOW); - query.memoized2(); - query.assert_log(&["Volatile invoked", "Memoized1 invoked"]); - query.memoized2(); - query.assert_log(&[]); - - // Third generation: volatile will change (to 2) and memoized1 - // will too (to 1). Therefore, after validating that Memoized1 - // changed, we now invoke Memoized2. - query.synthetic_write(Durability::LOW); - - query.memoized2(); - query.assert_log(&["Volatile invoked", "Memoized1 invoked", "Memoized2 invoked"]); - - query.memoized2(); - query.assert_log(&[]); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/interned.rs b/src/tools/rust-analyzer/crates/salsa/tests/interned.rs deleted file mode 100644 index d097e41cfd6..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/interned.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Test that you can implement a query using a `dyn Trait` setup. - -use salsa::InternId; - -#[salsa::database(InternStorage)] -#[derive(Default)] -struct Database { - storage: salsa::Storage, -} - -impl salsa::Database for Database {} - -impl salsa::ParallelDatabase for Database { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(Database { storage: self.storage.snapshot() }) - } -} - -#[salsa::query_group(InternStorage)] -trait Intern { - #[salsa::interned] - fn intern1(&self, x: String) -> InternId; - - #[salsa::interned] - fn intern2(&self, x: String, y: String) -> InternId; - - #[salsa::interned] - fn intern_key(&self, x: String) -> InternKey; -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct InternKey(InternId); - -impl salsa::InternKey for InternKey { - fn from_intern_id(v: InternId) -> Self { - InternKey(v) - } - - fn as_intern_id(&self) -> InternId { - self.0 - } -} - -#[test] -fn test_intern1() { - let db = Database::default(); - let foo0 = db.intern1("foo".to_owned()); - let bar0 = db.intern1("bar".to_owned()); - let foo1 = db.intern1("foo".to_owned()); - let bar1 = db.intern1("bar".to_owned()); - - assert_eq!(foo0, foo1); - assert_eq!(bar0, bar1); - assert_ne!(foo0, bar0); - - assert_eq!("foo".to_owned(), db.lookup_intern1(foo0)); - assert_eq!("bar".to_owned(), db.lookup_intern1(bar0)); -} - -#[test] -fn test_intern2() { - let db = Database::default(); - let foo0 = db.intern2("x".to_owned(), "foo".to_owned()); - let bar0 = db.intern2("x".to_owned(), "bar".to_owned()); - let foo1 = db.intern2("x".to_owned(), "foo".to_owned()); - let bar1 = db.intern2("x".to_owned(), "bar".to_owned()); - - assert_eq!(foo0, foo1); - assert_eq!(bar0, bar1); - assert_ne!(foo0, bar0); - - assert_eq!(("x".to_owned(), "foo".to_owned()), db.lookup_intern2(foo0)); - assert_eq!(("x".to_owned(), "bar".to_owned()), db.lookup_intern2(bar0)); -} - -#[test] -fn test_intern_key() { - let db = Database::default(); - let foo0 = db.intern_key("foo".to_owned()); - let bar0 = db.intern_key("bar".to_owned()); - let foo1 = db.intern_key("foo".to_owned()); - let bar1 = db.intern_key("bar".to_owned()); - - assert_eq!(foo0, foo1); - assert_eq!(bar0, bar1); - assert_ne!(foo0, bar0); - - assert_eq!("foo".to_owned(), db.lookup_intern_key(foo0)); - assert_eq!("bar".to_owned(), db.lookup_intern_key(bar0)); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/lru.rs b/src/tools/rust-analyzer/crates/salsa/tests/lru.rs deleted file mode 100644 index ef98a2c32b4..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/lru.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Test setting LRU actually limits the number of things in the database; -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, -}; - -#[derive(Debug, PartialEq, Eq)] -struct HotPotato(u32); - -static N_POTATOES: AtomicUsize = AtomicUsize::new(0); - -impl HotPotato { - fn new(id: u32) -> HotPotato { - N_POTATOES.fetch_add(1, Ordering::SeqCst); - HotPotato(id) - } -} - -impl Drop for HotPotato { - fn drop(&mut self) { - N_POTATOES.fetch_sub(1, Ordering::SeqCst); - } -} - -#[salsa::query_group(QueryGroupStorage)] -trait QueryGroup: salsa::Database { - #[salsa::lru] - fn get(&self, x: u32) -> Arc; - #[salsa::lru] - fn get_volatile(&self, x: u32) -> usize; -} - -fn get(_db: &dyn QueryGroup, x: u32) -> Arc { - Arc::new(HotPotato::new(x)) -} - -fn get_volatile(db: &dyn QueryGroup, _x: u32) -> usize { - static COUNTER: AtomicUsize = AtomicUsize::new(0); - db.salsa_runtime().report_untracked_read(); - COUNTER.fetch_add(1, Ordering::SeqCst) -} - -#[salsa::database(QueryGroupStorage)] -#[derive(Default)] -struct Database { - storage: salsa::Storage, -} - -impl salsa::Database for Database {} - -#[test] -fn lru_works() { - let mut db = Database::default(); - GetQuery.in_db_mut(&mut db).set_lru_capacity(32); - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 0); - - for i in 0..128u32 { - let p = db.get(i); - assert_eq!(p.0, i) - } - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 32); - - for i in 0..128u32 { - let p = db.get(i); - assert_eq!(p.0, i) - } - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 32); - - GetQuery.in_db_mut(&mut db).set_lru_capacity(32); - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 32); - - GetQuery.in_db_mut(&mut db).set_lru_capacity(64); - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 32); - for i in 0..128u32 { - let p = db.get(i); - assert_eq!(p.0, i) - } - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 64); - - // Special case: setting capacity to zero disables LRU - GetQuery.in_db_mut(&mut db).set_lru_capacity(0); - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 64); - for i in 0..128u32 { - let p = db.get(i); - assert_eq!(p.0, i) - } - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 128); - - drop(db); - assert_eq!(N_POTATOES.load(Ordering::SeqCst), 0); -} - -#[test] -fn lru_doesnt_break_volatile_queries() { - let mut db = Database::default(); - GetVolatileQuery.in_db_mut(&mut db).set_lru_capacity(32); - // Here, we check that we execute each volatile query at most once, despite - // LRU. That does mean that we have more values in DB than the LRU capacity, - // but it's much better than inconsistent results from volatile queries! - for i in (0..3).flat_map(|_| 0..128usize) { - let x = db.get_volatile(i as u32); - assert_eq!(x, i) - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/macros.rs b/src/tools/rust-analyzer/crates/salsa/tests/macros.rs deleted file mode 100644 index 9b07740e7de..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/macros.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[salsa::query_group(MyStruct)] -trait MyDatabase: salsa::Database { - #[salsa::invoke(another_module::another_name)] - fn my_query(&self, key: ()) -> (); -} - -mod another_module { - #[allow(dead_code)] - pub(crate) fn another_name(_: &dyn crate::MyDatabase, (): ()) {} -} - -fn main() {} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/no_send_sync.rs b/src/tools/rust-analyzer/crates/salsa/tests/no_send_sync.rs deleted file mode 100644 index 2a25c437c3e..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/no_send_sync.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::rc::Rc; - -#[salsa::query_group(NoSendSyncStorage)] -trait NoSendSyncDatabase: salsa::Database { - fn no_send_sync_value(&self, key: bool) -> Rc; - fn no_send_sync_key(&self, key: Rc) -> bool; -} - -fn no_send_sync_value(_db: &dyn NoSendSyncDatabase, key: bool) -> Rc { - Rc::new(key) -} - -fn no_send_sync_key(_db: &dyn NoSendSyncDatabase, key: Rc) -> bool { - *key -} - -#[salsa::database(NoSendSyncStorage)] -#[derive(Default)] -struct DatabaseImpl { - storage: salsa::Storage, -} - -impl salsa::Database for DatabaseImpl {} - -#[test] -fn no_send_sync() { - let db = DatabaseImpl::default(); - - assert_eq!(db.no_send_sync_value(true), Rc::new(true)); - assert!(!db.no_send_sync_key(Rc::new(false))); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/on_demand_inputs.rs b/src/tools/rust-analyzer/crates/salsa/tests/on_demand_inputs.rs deleted file mode 100644 index cad594f536f..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/on_demand_inputs.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Test that "on-demand" input pattern works. -//! -//! On-demand inputs are inputs computed lazily on the fly. They are simulated -//! via a b query with zero inputs, which uses `add_synthetic_read` to -//! tweak durability and `invalidate` to clear the input. - -#![allow(clippy::disallowed_types, clippy::type_complexity)] - -use std::{cell::RefCell, collections::HashMap, rc::Rc}; - -use salsa::{Database as _, Durability, EventKind}; - -#[salsa::query_group(QueryGroupStorage)] -trait QueryGroup: salsa::Database + AsRef> { - fn a(&self, x: u32) -> u32; - fn b(&self, x: u32) -> u32; - fn c(&self, x: u32) -> u32; -} - -fn a(db: &dyn QueryGroup, x: u32) -> u32 { - let durability = if x % 2 == 0 { Durability::LOW } else { Durability::HIGH }; - db.salsa_runtime().report_synthetic_read(durability); - let external_state: &HashMap = db.as_ref(); - external_state[&x] -} - -fn b(db: &dyn QueryGroup, x: u32) -> u32 { - db.a(x) -} - -fn c(db: &dyn QueryGroup, x: u32) -> u32 { - db.b(x) -} - -#[salsa::database(QueryGroupStorage)] -#[derive(Default)] -struct Database { - storage: salsa::Storage, - external_state: HashMap, - on_event: Option>, -} - -impl salsa::Database for Database { - fn salsa_event(&self, event: salsa::Event) { - if let Some(cb) = &self.on_event { - cb(self, event) - } - } -} - -impl AsRef> for Database { - fn as_ref(&self) -> &HashMap { - &self.external_state - } -} - -#[test] -fn on_demand_input_works() { - let mut db = Database::default(); - - db.external_state.insert(1, 10); - assert_eq!(db.b(1), 10); - assert_eq!(db.a(1), 10); - - // We changed external state, but haven't signaled about this yet, - // so we expect to see the old answer - db.external_state.insert(1, 92); - assert_eq!(db.b(1), 10); - assert_eq!(db.a(1), 10); - - AQuery.in_db_mut(&mut db).invalidate(&1); - assert_eq!(db.b(1), 92); - assert_eq!(db.a(1), 92); - - // Downstream queries should also be rerun if we call `a` first. - db.external_state.insert(1, 50); - AQuery.in_db_mut(&mut db).invalidate(&1); - assert_eq!(db.a(1), 50); - assert_eq!(db.b(1), 50); -} - -#[test] -fn on_demand_input_durability() { - let mut db = Database::default(); - - let events = Rc::new(RefCell::new(vec![])); - db.on_event = Some(Box::new({ - let events = events.clone(); - move |db, event| { - if let EventKind::WillCheckCancellation = event.kind { - // these events are not interesting - } else { - events.borrow_mut().push(format!("{:?}", event.debug(db))) - } - } - })); - - events.replace(vec![]); - db.external_state.insert(1, 10); - db.external_state.insert(2, 20); - assert_eq!(db.b(1), 10); - assert_eq!(db.b(2), 20); - expect_test::expect![[r#" - RefCell { - value: [ - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::BQuery::b(1) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(1) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::BQuery::b(2) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }", - ], - } - "#]].assert_debug_eq(&events); - - db.synthetic_write(Durability::LOW); - events.replace(vec![]); - assert_eq!(db.c(1), 10); - assert_eq!(db.c(2), 20); - // Re-execute `a(2)` because that has low durability, but not `a(1)` - expect_test::expect![[r#" - RefCell { - value: [ - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::CQuery::c(1) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::BQuery::b(1) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::CQuery::c(2) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::BQuery::b(2) } }", - ], - } - "#]].assert_debug_eq(&events); - - db.synthetic_write(Durability::HIGH); - events.replace(vec![]); - assert_eq!(db.c(1), 10); - assert_eq!(db.c(2), 20); - // Re-execute both `a(1)` and `a(2)`, but we don't re-execute any `b` queries as the - // result didn't actually change. - expect_test::expect![[r#" - RefCell { - value: [ - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(1) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::CQuery::c(1) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }", - "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::CQuery::c(2) } }", - ], - } - "#]].assert_debug_eq(&events); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/panic_safely.rs b/src/tools/rust-analyzer/crates/salsa/tests/panic_safely.rs deleted file mode 100644 index c11ae9c2144..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/panic_safely.rs +++ /dev/null @@ -1,93 +0,0 @@ -use salsa::{Database, ParallelDatabase, Snapshot}; -use std::panic::{self, AssertUnwindSafe}; -use std::sync::atomic::{AtomicU32, Ordering::SeqCst}; - -#[salsa::query_group(PanicSafelyStruct)] -trait PanicSafelyDatabase: salsa::Database { - #[salsa::input] - fn one(&self) -> usize; - - fn panic_safely(&self) -> (); - - fn outer(&self) -> (); -} - -fn panic_safely(db: &dyn PanicSafelyDatabase) { - assert_eq!(db.one(), 1); -} - -static OUTER_CALLS: AtomicU32 = AtomicU32::new(0); - -fn outer(db: &dyn PanicSafelyDatabase) { - OUTER_CALLS.fetch_add(1, SeqCst); - db.panic_safely(); -} - -#[salsa::database(PanicSafelyStruct)] -#[derive(Default)] -struct DatabaseStruct { - storage: salsa::Storage, -} - -impl salsa::Database for DatabaseStruct {} - -impl salsa::ParallelDatabase for DatabaseStruct { - fn snapshot(&self) -> Snapshot { - Snapshot::new(DatabaseStruct { storage: self.storage.snapshot() }) - } -} - -#[test] -fn should_panic_safely() { - let mut db = DatabaseStruct::default(); - db.set_one(0); - - // Invoke `db.panic_safely() without having set `db.one`. `db.one` will - // return 0 and we should catch the panic. - let result = panic::catch_unwind(AssertUnwindSafe({ - let db = db.snapshot(); - move || db.panic_safely() - })); - assert!(result.is_err()); - - // Set `db.one` to 1 and assert ok - db.set_one(1); - let result = panic::catch_unwind(AssertUnwindSafe(|| db.panic_safely())); - assert!(result.is_ok()); - - // Check, that memoized outer is not invalidated by a panic - { - assert_eq!(OUTER_CALLS.load(SeqCst), 0); - db.outer(); - assert_eq!(OUTER_CALLS.load(SeqCst), 1); - - db.set_one(0); - let result = panic::catch_unwind(AssertUnwindSafe(|| db.outer())); - assert!(result.is_err()); - assert_eq!(OUTER_CALLS.load(SeqCst), 1); - - db.set_one(1); - db.outer(); - assert_eq!(OUTER_CALLS.load(SeqCst), 2); - } -} - -#[test] -fn storages_are_unwind_safe() { - fn check_unwind_safe() {} - check_unwind_safe::<&DatabaseStruct>(); -} - -#[test] -fn panics_clear_query_stack() { - let db = DatabaseStruct::default(); - - // Invoke `db.panic_if_not_one() without having set `db.input`. `db.input` - // will default to 0 and we should catch the panic. - let result = panic::catch_unwind(AssertUnwindSafe(|| db.panic_safely())); - assert!(result.is_err()); - - // The database has been poisoned and any attempt to increment the - // revision should panic. - assert_eq!(db.salsa_runtime().active_query(), None); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/cancellation.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/cancellation.rs deleted file mode 100644 index 9a92e5cc1ff..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/cancellation.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::setup::{CancellationFlag, Knobs, ParDatabase, ParDatabaseImpl, WithValue}; -use salsa::{Cancelled, ParallelDatabase}; - -macro_rules! assert_cancelled { - ($thread:expr) => { - match $thread.join() { - Ok(value) => panic!("expected cancellation, got {:?}", value), - Err(payload) => match payload.downcast::() { - Ok(_) => {} - Err(payload) => ::std::panic::resume_unwind(payload), - }, - } - }; -} - -/// Add test where a call to `sum` is cancelled by a simultaneous -/// write. Check that we recompute the result in next revision, even -/// though none of the inputs have changed. -#[test] -fn in_par_get_set_cancellation_immediate() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 100); - db.set_input('b', 10); - db.set_input('c', 1); - db.set_input('d', 0); - - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - move || { - // This will not return until it sees cancellation is - // signaled. - db.knobs().sum_signal_on_entry.with_value(1, || { - db.knobs() - .sum_wait_for_cancellation - .with_value(CancellationFlag::Panic, || db.sum("abc")) - }) - } - }); - - // Wait until we have entered `sum` in the other thread. - db.wait_for(1); - - // Try to set the input. This will signal cancellation. - db.set_input('d', 1000); - - // This should re-compute the value (even though no input has changed). - let thread2 = std::thread::spawn({ - let db = db.snapshot(); - move || db.sum("abc") - }); - - assert_eq!(db.sum("d"), 1000); - assert_cancelled!(thread1); - assert_eq!(thread2.join().unwrap(), 111); -} - -/// Here, we check that `sum`'s cancellation is propagated -/// to `sum2` properly. -#[test] -fn in_par_get_set_cancellation_transitive() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 100); - db.set_input('b', 10); - db.set_input('c', 1); - db.set_input('d', 0); - - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - move || { - // This will not return until it sees cancellation is - // signaled. - db.knobs().sum_signal_on_entry.with_value(1, || { - db.knobs() - .sum_wait_for_cancellation - .with_value(CancellationFlag::Panic, || db.sum2("abc")) - }) - } - }); - - // Wait until we have entered `sum` in the other thread. - db.wait_for(1); - - // Try to set the input. This will signal cancellation. - db.set_input('d', 1000); - - // This should re-compute the value (even though no input has changed). - let thread2 = std::thread::spawn({ - let db = db.snapshot(); - move || db.sum2("abc") - }); - - assert_eq!(db.sum2("d"), 1000); - assert_cancelled!(thread1); - assert_eq!(thread2.join().unwrap(), 111); -} - -/// https://github.com/salsa-rs/salsa/issues/66 -#[test] -fn no_back_dating_in_cancellation() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 1); - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - move || { - // Here we compute a long-chain of queries, - // but the last one gets cancelled. - db.knobs().sum_signal_on_entry.with_value(1, || { - db.knobs() - .sum_wait_for_cancellation - .with_value(CancellationFlag::Panic, || db.sum3("a")) - }) - } - }); - - db.wait_for(1); - - // Set unrelated input to bump revision - db.set_input('b', 2); - - // Here we should recompuet the whole chain again, clearing the cancellation - // state. If we get `usize::max()` here, it is a bug! - assert_eq!(db.sum3("a"), 1); - - assert_cancelled!(thread1); - - db.set_input('a', 3); - db.set_input('a', 4); - assert_eq!(db.sum3("ab"), 6); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/frozen.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/frozen.rs deleted file mode 100644 index 5359a8820e2..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/frozen.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::setup::{ParDatabase, ParDatabaseImpl}; -use crate::signal::Signal; -use salsa::{Database, ParallelDatabase}; -use std::{ - panic::{catch_unwind, AssertUnwindSafe}, - sync::Arc, -}; - -/// Add test where a call to `sum` is cancelled by a simultaneous -/// write. Check that we recompute the result in next revision, even -/// though none of the inputs have changed. -#[test] -fn in_par_get_set_cancellation() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 1); - - let signal = Arc::new(Signal::default()); - - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - let signal = signal.clone(); - move || { - // Check that cancellation flag is not yet set, because - // `set` cannot have been called yet. - catch_unwind(AssertUnwindSafe(|| db.unwind_if_cancelled())).unwrap(); - - // Signal other thread to proceed. - signal.signal(1); - - // Wait for other thread to signal cancellation - catch_unwind(AssertUnwindSafe(|| loop { - db.unwind_if_cancelled(); - std::thread::yield_now(); - })) - .unwrap_err(); - } - }); - - let thread2 = std::thread::spawn({ - move || { - // Wait until thread 1 has asserted that they are not cancelled - // before we invoke `set.` - signal.wait_for(1); - - // This will block until thread1 drops the revision lock. - db.set_input('a', 2); - - db.input('a') - } - }); - - thread1.join().unwrap(); - - let c = thread2.join().unwrap(); - assert_eq!(c, 2); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/independent.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/independent.rs deleted file mode 100644 index bd6ba3bf931..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/independent.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::setup::{ParDatabase, ParDatabaseImpl}; -use salsa::ParallelDatabase; - -/// Test two `sum` queries (on distinct keys) executing in different -/// threads. Really just a test that `snapshot` etc compiles. -#[test] -fn in_par_two_independent_queries() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 100); - db.set_input('b', 10); - db.set_input('c', 1); - db.set_input('d', 200); - db.set_input('e', 20); - db.set_input('f', 2); - - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - move || db.sum("abc") - }); - - let thread2 = std::thread::spawn({ - let db = db.snapshot(); - move || db.sum("def") - }); - - assert_eq!(thread1.join().unwrap(), 111); - assert_eq!(thread2.join().unwrap(), 222); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/main.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/main.rs deleted file mode 100644 index 31c0da18375..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/main.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod setup; - -mod cancellation; -mod frozen; -mod independent; -mod parallel_cycle_all_recover; -mod parallel_cycle_mid_recover; -mod parallel_cycle_none_recover; -mod parallel_cycle_one_recovers; -mod race; -mod signal; -mod stress; -mod true_parallel; diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_all_recover.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_all_recover.rs deleted file mode 100644 index a13ae3418f2..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_all_recover.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Test for cycle recover spread across two threads. -//! See `../cycles.rs` for a complete listing of cycle tests, -//! both intra and cross thread. - -use crate::setup::{Knobs, ParDatabaseImpl}; -use salsa::ParallelDatabase; - -// Recover cycle test: -// -// The pattern is as follows. -// -// Thread A Thread B -// -------- -------- -// a1 b1 -// | wait for stage 1 (blocks) -// signal stage 1 | -// wait for stage 2 (blocks) (unblocked) -// | signal stage 2 -// (unblocked) wait for stage 3 (blocks) -// a2 | -// b1 (blocks -> stage 3) | -// | (unblocked) -// | b2 -// | a1 (cycle detected, recovers) -// | b2 completes, recovers -// | b1 completes, recovers -// a2 sees cycle, recovers -// a1 completes, recovers - -#[test] -fn parallel_cycle_all_recover() { - let db = ParDatabaseImpl::default(); - db.knobs().signal_on_will_block.set(3); - - let thread_a = std::thread::spawn({ - let db = db.snapshot(); - move || db.a1(1) - }); - - let thread_b = std::thread::spawn({ - let db = db.snapshot(); - move || db.b1(1) - }); - - assert_eq!(thread_a.join().unwrap(), 11); - assert_eq!(thread_b.join().unwrap(), 21); -} - -#[salsa::query_group(ParallelCycleAllRecover)] -pub(crate) trait TestDatabase: Knobs { - #[salsa::cycle(recover_a1)] - fn a1(&self, key: i32) -> i32; - - #[salsa::cycle(recover_a2)] - fn a2(&self, key: i32) -> i32; - - #[salsa::cycle(recover_b1)] - fn b1(&self, key: i32) -> i32; - - #[salsa::cycle(recover_b2)] - fn b2(&self, key: i32) -> i32; -} - -fn recover_a1(_db: &dyn TestDatabase, _cycle: &salsa::Cycle, key: &i32) -> i32 { - tracing::debug!("recover_a1"); - key * 10 + 1 -} - -fn recover_a2(_db: &dyn TestDatabase, _cycle: &salsa::Cycle, key: &i32) -> i32 { - tracing::debug!("recover_a2"); - key * 10 + 2 -} - -fn recover_b1(_db: &dyn TestDatabase, _cycle: &salsa::Cycle, key: &i32) -> i32 { - tracing::debug!("recover_b1"); - key * 20 + 1 -} - -fn recover_b2(_db: &dyn TestDatabase, _cycle: &salsa::Cycle, key: &i32) -> i32 { - tracing::debug!("recover_b2"); - key * 20 + 2 -} - -fn a1(db: &dyn TestDatabase, key: i32) -> i32 { - // Wait to create the cycle until both threads have entered - db.signal(1); - db.wait_for(2); - - db.a2(key) -} - -fn a2(db: &dyn TestDatabase, key: i32) -> i32 { - db.b1(key) -} - -fn b1(db: &dyn TestDatabase, key: i32) -> i32 { - // Wait to create the cycle until both threads have entered - db.wait_for(1); - db.signal(2); - - // Wait for thread A to block on this thread - db.wait_for(3); - - db.b2(key) -} - -fn b2(db: &dyn TestDatabase, key: i32) -> i32 { - db.a1(key) -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_mid_recover.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_mid_recover.rs deleted file mode 100644 index 971fe7ab120..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_mid_recover.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Test for cycle recover spread across two threads. -//! See `../cycles.rs` for a complete listing of cycle tests, -//! both intra and cross thread. - -use crate::setup::{Knobs, ParDatabaseImpl}; -use salsa::ParallelDatabase; - -// Recover cycle test: -// -// The pattern is as follows. -// -// Thread A Thread B -// -------- -------- -// a1 b1 -// | wait for stage 1 (blocks) -// signal stage 1 | -// wait for stage 2 (blocks) (unblocked) -// | | -// | b2 -// | b3 -// | a1 (blocks -> stage 2) -// (unblocked) | -// a2 (cycle detected) | -// b3 recovers -// b2 resumes -// b1 panics because bug - -#[test] -fn parallel_cycle_mid_recovers() { - let db = ParDatabaseImpl::default(); - db.knobs().signal_on_will_block.set(2); - - let thread_a = std::thread::spawn({ - let db = db.snapshot(); - move || db.a1(1) - }); - - let thread_b = std::thread::spawn({ - let db = db.snapshot(); - move || db.b1(1) - }); - - // We expect that the recovery function yields - // `1 * 20 + 2`, which is returned (and forwarded) - // to b1, and from there to a2 and a1. - assert_eq!(thread_a.join().unwrap(), 22); - assert_eq!(thread_b.join().unwrap(), 22); -} - -#[salsa::query_group(ParallelCycleMidRecovers)] -pub(crate) trait TestDatabase: Knobs { - fn a1(&self, key: i32) -> i32; - - fn a2(&self, key: i32) -> i32; - - #[salsa::cycle(recover_b1)] - fn b1(&self, key: i32) -> i32; - - fn b2(&self, key: i32) -> i32; - - #[salsa::cycle(recover_b3)] - fn b3(&self, key: i32) -> i32; -} - -fn recover_b1(_db: &dyn TestDatabase, _cycle: &salsa::Cycle, key: &i32) -> i32 { - tracing::debug!("recover_b1"); - key * 20 + 2 -} - -fn recover_b3(_db: &dyn TestDatabase, _cycle: &salsa::Cycle, key: &i32) -> i32 { - tracing::debug!("recover_b1"); - key * 200 + 2 -} - -fn a1(db: &dyn TestDatabase, key: i32) -> i32 { - // tell thread b we have started - db.signal(1); - - // wait for thread b to block on a1 - db.wait_for(2); - - db.a2(key) -} - -fn a2(db: &dyn TestDatabase, key: i32) -> i32 { - // create the cycle - db.b1(key) -} - -fn b1(db: &dyn TestDatabase, key: i32) -> i32 { - // wait for thread a to have started - db.wait_for(1); - - db.b2(key); - - 0 -} - -fn b2(db: &dyn TestDatabase, key: i32) -> i32 { - // will encounter a cycle but recover - db.b3(key); - db.b1(key); // hasn't recovered yet - 0 -} - -fn b3(db: &dyn TestDatabase, key: i32) -> i32 { - // will block on thread a, signaling stage 2 - db.a1(key) -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_none_recover.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_none_recover.rs deleted file mode 100644 index 3c73852eafb..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_none_recover.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Test a cycle where no queries recover that occurs across threads. -//! See the `../cycles.rs` for a complete listing of cycle tests, -//! both intra and cross thread. - -use crate::setup::{Knobs, ParDatabaseImpl}; -use expect_test::expect; -use salsa::ParallelDatabase; - -#[test] -fn parallel_cycle_none_recover() { - let db = ParDatabaseImpl::default(); - db.knobs().signal_on_will_block.set(3); - - let thread_a = std::thread::spawn({ - let db = db.snapshot(); - move || db.a(-1) - }); - - let thread_b = std::thread::spawn({ - let db = db.snapshot(); - move || db.b(-1) - }); - - // We expect B to panic because it detects a cycle (it is the one that calls A, ultimately). - // Right now, it panics with a string. - let err_b = thread_b.join().unwrap_err(); - if let Some(c) = err_b.downcast_ref::() { - expect![[r#" - [ - "parallel::parallel_cycle_none_recover::AQuery::a(-1)", - "parallel::parallel_cycle_none_recover::BQuery::b(-1)", - ] - "#]] - .assert_debug_eq(&c.unexpected_participants(&db)); - } else { - panic!("b failed in an unexpected way: {err_b:?}"); - } - - // We expect A to propagate a panic, which causes us to use the sentinel - // type `Canceled`. - assert!(thread_a.join().unwrap_err().downcast_ref::().is_some()); -} - -#[salsa::query_group(ParallelCycleNoneRecover)] -pub(crate) trait TestDatabase: Knobs { - fn a(&self, key: i32) -> i32; - fn b(&self, key: i32) -> i32; -} - -fn a(db: &dyn TestDatabase, key: i32) -> i32 { - // Wait to create the cycle until both threads have entered - db.signal(1); - db.wait_for(2); - - db.b(key) -} - -fn b(db: &dyn TestDatabase, key: i32) -> i32 { - // Wait to create the cycle until both threads have entered - db.wait_for(1); - db.signal(2); - - // Wait for thread A to block on this thread - db.wait_for(3); - - // Now try to execute A - db.a(key) -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_one_recovers.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_one_recovers.rs deleted file mode 100644 index 025fbf37477..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/parallel_cycle_one_recovers.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Test for cycle recover spread across two threads. -//! See `../cycles.rs` for a complete listing of cycle tests, -//! both intra and cross thread. - -use crate::setup::{Knobs, ParDatabaseImpl}; -use salsa::ParallelDatabase; - -// Recover cycle test: -// -// The pattern is as follows. -// -// Thread A Thread B -// -------- -------- -// a1 b1 -// | wait for stage 1 (blocks) -// signal stage 1 | -// wait for stage 2 (blocks) (unblocked) -// | signal stage 2 -// (unblocked) wait for stage 3 (blocks) -// a2 | -// b1 (blocks -> stage 3) | -// | (unblocked) -// | b2 -// | a1 (cycle detected) -// a2 recovery fn executes | -// a1 completes normally | -// b2 completes, recovers -// b1 completes, recovers - -#[test] -fn parallel_cycle_one_recovers() { - let db = ParDatabaseImpl::default(); - db.knobs().signal_on_will_block.set(3); - - let thread_a = std::thread::spawn({ - let db = db.snapshot(); - move || db.a1(1) - }); - - let thread_b = std::thread::spawn({ - let db = db.snapshot(); - move || db.b1(1) - }); - - // We expect that the recovery function yields - // `1 * 20 + 2`, which is returned (and forwarded) - // to b1, and from there to a2 and a1. - assert_eq!(thread_a.join().unwrap(), 22); - assert_eq!(thread_b.join().unwrap(), 22); -} - -#[salsa::query_group(ParallelCycleOneRecovers)] -pub(crate) trait TestDatabase: Knobs { - fn a1(&self, key: i32) -> i32; - - #[salsa::cycle(recover)] - fn a2(&self, key: i32) -> i32; - - fn b1(&self, key: i32) -> i32; - - fn b2(&self, key: i32) -> i32; -} - -fn recover(_db: &dyn TestDatabase, _cycle: &salsa::Cycle, key: &i32) -> i32 { - tracing::debug!("recover"); - key * 20 + 2 -} - -fn a1(db: &dyn TestDatabase, key: i32) -> i32 { - // Wait to create the cycle until both threads have entered - db.signal(1); - db.wait_for(2); - - db.a2(key) -} - -fn a2(db: &dyn TestDatabase, key: i32) -> i32 { - db.b1(key) -} - -fn b1(db: &dyn TestDatabase, key: i32) -> i32 { - // Wait to create the cycle until both threads have entered - db.wait_for(1); - db.signal(2); - - // Wait for thread A to block on this thread - db.wait_for(3); - - db.b2(key) -} - -fn b2(db: &dyn TestDatabase, key: i32) -> i32 { - db.a1(key) -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/race.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/race.rs deleted file mode 100644 index c53d4b464ea..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/race.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::panic::AssertUnwindSafe; - -use crate::setup::{ParDatabase, ParDatabaseImpl}; -use salsa::{Cancelled, ParallelDatabase}; - -/// Test where a read and a set are racing with one another. -/// Should be atomic. -#[test] -fn in_par_get_set_race() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 100); - db.set_input('b', 10); - db.set_input('c', 1); - - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - move || Cancelled::catch(AssertUnwindSafe(|| db.sum("abc"))) - }); - - let thread2 = std::thread::spawn(move || { - db.set_input('a', 1000); - db.sum("a") - }); - - // If the 1st thread runs first, you get 111, otherwise you get - // 1011; if they run concurrently and the 1st thread observes the - // cancellation, it'll unwind. - let result1 = thread1.join().unwrap(); - if let Ok(value1) = result1 { - assert!(value1 == 111 || value1 == 1011, "illegal result {value1}"); - } - - // thread2 can not observe a cancellation because it performs a - // database write before running any other queries. - assert_eq!(thread2.join().unwrap(), 1000); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/setup.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/setup.rs deleted file mode 100644 index 0a35902b435..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/setup.rs +++ /dev/null @@ -1,197 +0,0 @@ -use crate::signal::Signal; -use salsa::Database; -use salsa::ParallelDatabase; -use salsa::Snapshot; -use std::sync::Arc; -use std::{ - cell::Cell, - panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, -}; - -#[salsa::query_group(Par)] -pub(crate) trait ParDatabase: Knobs { - #[salsa::input] - fn input(&self, key: char) -> usize; - - fn sum(&self, key: &'static str) -> usize; - - /// Invokes `sum` - fn sum2(&self, key: &'static str) -> usize; - - /// Invokes `sum` but doesn't really care about the result. - fn sum2_drop_sum(&self, key: &'static str) -> usize; - - /// Invokes `sum2` - fn sum3(&self, key: &'static str) -> usize; - - /// Invokes `sum2_drop_sum` - fn sum3_drop_sum(&self, key: &'static str) -> usize; -} - -/// Various "knobs" and utilities used by tests to force -/// a certain behavior. -pub(crate) trait Knobs { - fn knobs(&self) -> &KnobsStruct; - - fn signal(&self, stage: usize); - - fn wait_for(&self, stage: usize); -} - -pub(crate) trait WithValue { - fn with_value(&self, value: T, closure: impl FnOnce() -> R) -> R; -} - -impl WithValue for Cell { - fn with_value(&self, value: T, closure: impl FnOnce() -> R) -> R { - let old_value = self.replace(value); - - let result = catch_unwind(AssertUnwindSafe(closure)); - - self.set(old_value); - - match result { - Ok(r) => r, - Err(payload) => resume_unwind(payload), - } - } -} - -#[derive(Default, Clone, Copy, PartialEq, Eq)] -pub(crate) enum CancellationFlag { - #[default] - Down, - Panic, -} - -/// Various "knobs" that can be used to customize how the queries -/// behave on one specific thread. Note that this state is -/// intentionally thread-local (apart from `signal`). -#[derive(Clone, Default)] -pub(crate) struct KnobsStruct { - /// A kind of flexible barrier used to coordinate execution across - /// threads to ensure we reach various weird states. - pub(crate) signal: Arc, - - /// When this database is about to block, send a signal. - pub(crate) signal_on_will_block: Cell, - - /// Invocations of `sum` will signal this stage on entry. - pub(crate) sum_signal_on_entry: Cell, - - /// Invocations of `sum` will wait for this stage on entry. - pub(crate) sum_wait_for_on_entry: Cell, - - /// If true, invocations of `sum` will panic before they exit. - pub(crate) sum_should_panic: Cell, - - /// If true, invocations of `sum` will wait for cancellation before - /// they exit. - pub(crate) sum_wait_for_cancellation: Cell, - - /// Invocations of `sum` will wait for this stage prior to exiting. - pub(crate) sum_wait_for_on_exit: Cell, - - /// Invocations of `sum` will signal this stage prior to exiting. - pub(crate) sum_signal_on_exit: Cell, - - /// Invocations of `sum3_drop_sum` will panic unconditionally - pub(crate) sum3_drop_sum_should_panic: Cell, -} - -fn sum(db: &dyn ParDatabase, key: &'static str) -> usize { - let mut sum = 0; - - db.signal(db.knobs().sum_signal_on_entry.get()); - - db.wait_for(db.knobs().sum_wait_for_on_entry.get()); - - if db.knobs().sum_should_panic.get() { - panic!("query set to panic before exit") - } - - for ch in key.chars() { - sum += db.input(ch); - } - - match db.knobs().sum_wait_for_cancellation.get() { - CancellationFlag::Down => (), - CancellationFlag::Panic => { - tracing::debug!("waiting for cancellation"); - loop { - db.unwind_if_cancelled(); - std::thread::yield_now(); - } - } - } - - db.wait_for(db.knobs().sum_wait_for_on_exit.get()); - - db.signal(db.knobs().sum_signal_on_exit.get()); - - sum -} - -fn sum2(db: &dyn ParDatabase, key: &'static str) -> usize { - db.sum(key) -} - -fn sum2_drop_sum(db: &dyn ParDatabase, key: &'static str) -> usize { - let _ = db.sum(key); - 22 -} - -fn sum3(db: &dyn ParDatabase, key: &'static str) -> usize { - db.sum2(key) -} - -fn sum3_drop_sum(db: &dyn ParDatabase, key: &'static str) -> usize { - if db.knobs().sum3_drop_sum_should_panic.get() { - panic!("sum3_drop_sum executed") - } - db.sum2_drop_sum(key) -} - -#[salsa::database( - Par, - crate::parallel_cycle_all_recover::ParallelCycleAllRecover, - crate::parallel_cycle_none_recover::ParallelCycleNoneRecover, - crate::parallel_cycle_mid_recover::ParallelCycleMidRecovers, - crate::parallel_cycle_one_recovers::ParallelCycleOneRecovers -)] -#[derive(Default)] -pub(crate) struct ParDatabaseImpl { - storage: salsa::Storage, - knobs: KnobsStruct, -} - -impl Database for ParDatabaseImpl { - fn salsa_event(&self, event: salsa::Event) { - if let salsa::EventKind::WillBlockOn { .. } = event.kind { - self.signal(self.knobs().signal_on_will_block.get()); - } - } -} - -impl ParallelDatabase for ParDatabaseImpl { - fn snapshot(&self) -> Snapshot { - Snapshot::new(ParDatabaseImpl { - storage: self.storage.snapshot(), - knobs: self.knobs.clone(), - }) - } -} - -impl Knobs for ParDatabaseImpl { - fn knobs(&self) -> &KnobsStruct { - &self.knobs - } - - fn signal(&self, stage: usize) { - self.knobs.signal.signal(stage); - } - - fn wait_for(&self, stage: usize) { - self.knobs.signal.wait_for(stage); - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/signal.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/signal.rs deleted file mode 100644 index 0af7b66e482..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/signal.rs +++ /dev/null @@ -1,40 +0,0 @@ -use parking_lot::{Condvar, Mutex}; - -#[derive(Default)] -pub(crate) struct Signal { - value: Mutex, - cond_var: Condvar, -} - -impl Signal { - pub(crate) fn signal(&self, stage: usize) { - tracing::debug!("signal({})", stage); - - // This check avoids acquiring the lock for things that will - // clearly be a no-op. Not *necessary* but helps to ensure we - // are more likely to encounter weird race conditions; - // otherwise calls to `sum` will tend to be unnecessarily - // synchronous. - if stage > 0 { - let mut v = self.value.lock(); - if stage > *v { - *v = stage; - self.cond_var.notify_all(); - } - } - } - - /// Waits until the given condition is true; the fn is invoked - /// with the current stage. - pub(crate) fn wait_for(&self, stage: usize) { - tracing::debug!("wait_for({})", stage); - - // As above, avoid lock if clearly a no-op. - if stage > 0 { - let mut v = self.value.lock(); - while *v < stage { - self.cond_var.wait(&mut v); - } - } - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/stress.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/stress.rs deleted file mode 100644 index 2fa317b2b90..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/stress.rs +++ /dev/null @@ -1,168 +0,0 @@ -use rand::seq::SliceRandom; -use rand::Rng; - -use salsa::ParallelDatabase; -use salsa::Snapshot; -use salsa::{Cancelled, Database}; - -// Number of operations a reader performs -const N_MUTATOR_OPS: usize = 100; -const N_READER_OPS: usize = 100; - -#[salsa::query_group(Stress)] -trait StressDatabase: salsa::Database { - #[salsa::input] - fn a(&self, key: usize) -> usize; - - fn b(&self, key: usize) -> usize; - - fn c(&self, key: usize) -> usize; -} - -fn b(db: &dyn StressDatabase, key: usize) -> usize { - db.unwind_if_cancelled(); - db.a(key) -} - -fn c(db: &dyn StressDatabase, key: usize) -> usize { - db.b(key) -} - -#[salsa::database(Stress)] -#[derive(Default)] -struct StressDatabaseImpl { - storage: salsa::Storage, -} - -impl salsa::Database for StressDatabaseImpl {} - -impl salsa::ParallelDatabase for StressDatabaseImpl { - fn snapshot(&self) -> Snapshot { - Snapshot::new(StressDatabaseImpl { storage: self.storage.snapshot() }) - } -} - -#[derive(Clone, Copy, Debug)] -enum Query { - A, - B, - C, -} - -enum MutatorOp { - WriteOp(WriteOp), - LaunchReader { ops: Vec, check_cancellation: bool }, -} - -#[derive(Debug)] -enum WriteOp { - SetA(usize, usize), -} - -#[derive(Debug)] -enum ReadOp { - Get(Query, usize), -} - -impl rand::distributions::Distribution for rand::distributions::Standard { - fn sample(&self, rng: &mut R) -> Query { - *[Query::A, Query::B, Query::C].choose(rng).unwrap() - } -} - -impl rand::distributions::Distribution for rand::distributions::Standard { - fn sample(&self, rng: &mut R) -> MutatorOp { - if rng.gen_bool(0.5) { - MutatorOp::WriteOp(rng.gen()) - } else { - MutatorOp::LaunchReader { - ops: (0..N_READER_OPS).map(|_| rng.gen()).collect(), - check_cancellation: rng.gen(), - } - } - } -} - -impl rand::distributions::Distribution for rand::distributions::Standard { - fn sample(&self, rng: &mut R) -> WriteOp { - let key = rng.gen::() % 10; - let value = rng.gen::() % 10; - WriteOp::SetA(key, value) - } -} - -impl rand::distributions::Distribution for rand::distributions::Standard { - fn sample(&self, rng: &mut R) -> ReadOp { - let query = rng.gen::(); - let key = rng.gen::() % 10; - ReadOp::Get(query, key) - } -} - -fn db_reader_thread(db: &StressDatabaseImpl, ops: Vec, check_cancellation: bool) { - for op in ops { - if check_cancellation { - db.unwind_if_cancelled(); - } - op.execute(db); - } -} - -impl WriteOp { - fn execute(self, db: &mut StressDatabaseImpl) { - match self { - WriteOp::SetA(key, value) => { - db.set_a(key, value); - } - } - } -} - -impl ReadOp { - fn execute(self, db: &StressDatabaseImpl) { - match self { - ReadOp::Get(query, key) => match query { - Query::A => { - db.a(key); - } - Query::B => { - let _ = db.b(key); - } - Query::C => { - let _ = db.c(key); - } - }, - } - } -} - -#[test] -fn stress_test() { - let mut db = StressDatabaseImpl::default(); - for i in 0..10 { - db.set_a(i, i); - } - - let mut rng = rand::thread_rng(); - - // generate the ops that the mutator thread will perform - let write_ops: Vec = (0..N_MUTATOR_OPS).map(|_| rng.gen()).collect(); - - // execute the "main thread", which sometimes snapshots off other threads - let mut all_threads = vec![]; - for op in write_ops { - match op { - MutatorOp::WriteOp(w) => w.execute(&mut db), - MutatorOp::LaunchReader { ops, check_cancellation } => { - all_threads.push(std::thread::spawn({ - let db = db.snapshot(); - move || Cancelled::catch(|| db_reader_thread(&db, ops, check_cancellation)) - })) - } - } - } - - for thread in all_threads { - thread.join().unwrap().ok(); - } -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/parallel/true_parallel.rs b/src/tools/rust-analyzer/crates/salsa/tests/parallel/true_parallel.rs deleted file mode 100644 index d0e58efd1ac..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/parallel/true_parallel.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::setup::{Knobs, ParDatabase, ParDatabaseImpl, WithValue}; -use salsa::ParallelDatabase; -use std::panic::{self, AssertUnwindSafe}; - -/// Test where two threads are executing sum. We show that they can -/// both be executing sum in parallel by having thread1 wait for -/// thread2 to send a signal before it leaves (similarly, thread2 -/// waits for thread1 to send a signal before it enters). -#[test] -fn true_parallel_different_keys() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 100); - db.set_input('b', 10); - db.set_input('c', 1); - - // Thread 1 will signal stage 1 when it enters and wait for stage 2. - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - move || { - let v = db - .knobs() - .sum_signal_on_entry - .with_value(1, || db.knobs().sum_wait_for_on_exit.with_value(2, || db.sum("a"))); - v - } - }); - - // Thread 2 will wait_for stage 1 when it enters and signal stage 2 - // when it leaves. - let thread2 = std::thread::spawn({ - let db = db.snapshot(); - move || { - let v = db - .knobs() - .sum_wait_for_on_entry - .with_value(1, || db.knobs().sum_signal_on_exit.with_value(2, || db.sum("b"))); - v - } - }); - - assert_eq!(thread1.join().unwrap(), 100); - assert_eq!(thread2.join().unwrap(), 10); -} - -/// Add a test that tries to trigger a conflict, where we fetch -/// `sum("abc")` from two threads simultaneously, and of them -/// therefore has to block. -#[test] -fn true_parallel_same_keys() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 100); - db.set_input('b', 10); - db.set_input('c', 1); - - // Thread 1 will wait_for a barrier in the start of `sum` - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - move || { - let v = db - .knobs() - .sum_signal_on_entry - .with_value(1, || db.knobs().sum_wait_for_on_entry.with_value(2, || db.sum("abc"))); - v - } - }); - - // Thread 2 will wait until Thread 1 has entered sum and then -- - // once it has set itself to block -- signal Thread 1 to - // continue. This way, we test out the mechanism of one thread - // blocking on another. - let thread2 = std::thread::spawn({ - let db = db.snapshot(); - move || { - db.knobs().signal.wait_for(1); - db.knobs().signal_on_will_block.set(2); - db.sum("abc") - } - }); - - assert_eq!(thread1.join().unwrap(), 111); - assert_eq!(thread2.join().unwrap(), 111); -} - -/// Add a test that tries to trigger a conflict, where we fetch `sum("a")` -/// from two threads simultaneously. After `thread2` begins blocking, -/// we force `thread1` to panic and should see that propagate to `thread2`. -#[test] -fn true_parallel_propagate_panic() { - let mut db = ParDatabaseImpl::default(); - - db.set_input('a', 1); - - // `thread1` will wait_for a barrier in the start of `sum`. Once it can - // continue, it will panic. - let thread1 = std::thread::spawn({ - let db = db.snapshot(); - move || { - let v = db.knobs().sum_signal_on_entry.with_value(1, || { - db.knobs() - .sum_wait_for_on_entry - .with_value(2, || db.knobs().sum_should_panic.with_value(true, || db.sum("a"))) - }); - v - } - }); - - // `thread2` will wait until `thread1` has entered sum and then -- once it - // has set itself to block -- signal `thread1` to continue. - let thread2 = std::thread::spawn({ - let db = db.snapshot(); - move || { - db.knobs().signal.wait_for(1); - db.knobs().signal_on_will_block.set(2); - db.sum("a") - } - }); - - let result1 = panic::catch_unwind(AssertUnwindSafe(|| thread1.join().unwrap())); - let result2 = panic::catch_unwind(AssertUnwindSafe(|| thread2.join().unwrap())); - - assert!(result1.is_err()); - assert!(result2.is_err()); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/implementation.rs b/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/implementation.rs deleted file mode 100644 index 2843660f154..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/implementation.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::queries; -use std::cell::Cell; - -#[salsa::database(queries::GroupStruct)] -#[derive(Default)] -pub(crate) struct DatabaseImpl { - storage: salsa::Storage, - counter: Cell, -} - -impl queries::Counter for DatabaseImpl { - fn increment(&self) -> usize { - let v = self.counter.get(); - self.counter.set(v + 1); - v - } -} - -impl salsa::Database for DatabaseImpl {} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/main.rs b/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/main.rs deleted file mode 100644 index e92c61740e0..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/main.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod implementation; -mod queries; -mod tests; - -fn main() {} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/queries.rs b/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/queries.rs deleted file mode 100644 index 0847fadefb0..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/queries.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub(crate) trait Counter: salsa::Database { - fn increment(&self) -> usize; -} - -#[salsa::query_group(GroupStruct)] -pub(crate) trait Database: Counter { - fn memoized(&self) -> usize; - fn volatile(&self) -> usize; -} - -/// Because this query is memoized, we only increment the counter -/// the first time it is invoked. -fn memoized(db: &dyn Database) -> usize { - db.volatile() -} - -/// Because this query is volatile, each time it is invoked, -/// we will increment the counter. -fn volatile(db: &dyn Database) -> usize { - db.salsa_runtime().report_untracked_read(); - db.increment() -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/tests.rs b/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/tests.rs deleted file mode 100644 index 8e2f9b03cb9..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/storage_varieties/tests.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![cfg(test)] - -use crate::implementation::DatabaseImpl; -use crate::queries::Database; -use salsa::Database as _Database; -use salsa::Durability; - -#[test] -fn memoized_twice() { - let db = DatabaseImpl::default(); - let v1 = db.memoized(); - let v2 = db.memoized(); - assert_eq!(v1, v2); -} - -#[test] -fn volatile_twice() { - let mut db = DatabaseImpl::default(); - let v1 = db.volatile(); - let v2 = db.volatile(); // volatiles are cached, so 2nd read returns the same - assert_eq!(v1, v2); - - db.synthetic_write(Durability::LOW); // clears volatile caches - - let v3 = db.volatile(); // will re-increment the counter - let v4 = db.volatile(); // second call will be cached - assert_eq!(v1 + 1, v3); - assert_eq!(v3, v4); -} - -#[test] -fn intermingled() { - let mut db = DatabaseImpl::default(); - let v1 = db.volatile(); - let v2 = db.memoized(); - let v3 = db.volatile(); // cached - let v4 = db.memoized(); // cached - - assert_eq!(v1, v2); - assert_eq!(v1, v3); - assert_eq!(v2, v4); - - db.synthetic_write(Durability::LOW); // clears volatile caches - - let v5 = db.memoized(); // re-executes volatile, caches new result - let v6 = db.memoized(); // re-use cached result - assert_eq!(v4 + 1, v5); - assert_eq!(v5, v6); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/transparent.rs b/src/tools/rust-analyzer/crates/salsa/tests/transparent.rs deleted file mode 100644 index 2e6dd4267b2..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/transparent.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Test that transparent (uncached) queries work - -#[salsa::query_group(QueryGroupStorage)] -trait QueryGroup { - #[salsa::input] - fn input(&self, x: u32) -> u32; - #[salsa::transparent] - fn wrap(&self, x: u32) -> u32; - fn get(&self, x: u32) -> u32; -} - -fn wrap(db: &dyn QueryGroup, x: u32) -> u32 { - db.input(x) -} - -fn get(db: &dyn QueryGroup, x: u32) -> u32 { - db.wrap(x) -} - -#[salsa::database(QueryGroupStorage)] -#[derive(Default)] -struct Database { - storage: salsa::Storage, -} - -impl salsa::Database for Database {} - -#[test] -fn transparent_queries_work() { - let mut db = Database::default(); - - db.set_input(1, 10); - assert_eq!(db.get(1), 10); - assert_eq!(db.get(1), 10); - - db.set_input(1, 92); - assert_eq!(db.get(1), 92); - assert_eq!(db.get(1), 92); -} diff --git a/src/tools/rust-analyzer/crates/salsa/tests/variadic.rs b/src/tools/rust-analyzer/crates/salsa/tests/variadic.rs deleted file mode 100644 index cb857844eb7..00000000000 --- a/src/tools/rust-analyzer/crates/salsa/tests/variadic.rs +++ /dev/null @@ -1,51 +0,0 @@ -#[salsa::query_group(HelloWorld)] -trait HelloWorldDatabase: salsa::Database { - #[salsa::input] - fn input(&self, a: u32, b: u32) -> u32; - - fn none(&self) -> u32; - - fn one(&self, k: u32) -> u32; - - fn two(&self, a: u32, b: u32) -> u32; - - fn trailing(&self, a: u32, b: u32) -> u32; -} - -fn none(_db: &dyn HelloWorldDatabase) -> u32 { - 22 -} - -fn one(_db: &dyn HelloWorldDatabase, k: u32) -> u32 { - k * 2 -} - -fn two(_db: &dyn HelloWorldDatabase, a: u32, b: u32) -> u32 { - a * b -} - -fn trailing(_db: &dyn HelloWorldDatabase, a: u32, b: u32) -> u32 { - a - b -} - -#[salsa::database(HelloWorld)] -#[derive(Default)] -struct DatabaseStruct { - storage: salsa::Storage, -} - -impl salsa::Database for DatabaseStruct {} - -#[test] -fn execute() { - let mut db = DatabaseStruct::default(); - - // test what happens with inputs: - db.set_input(1, 2, 3); - assert_eq!(db.input(1, 2), 3); - - assert_eq!(db.none(), 22); - assert_eq!(db.one(11), 22); - assert_eq!(db.two(11, 2), 22); - assert_eq!(db.trailing(24, 2), 22); -} diff --git a/src/tools/rust-analyzer/crates/span/Cargo.toml b/src/tools/rust-analyzer/crates/span/Cargo.toml index 3381dac0b42..569da8082a8 100644 --- a/src/tools/rust-analyzer/crates/span/Cargo.toml +++ b/src/tools/rust-analyzer/crates/span/Cargo.toml @@ -12,7 +12,7 @@ authors.workspace = true [dependencies] la-arena.workspace = true -salsa.workspace = true +ra-salsa.workspace = true rustc-hash.workspace = true hashbrown.workspace = true text-size.workspace = true diff --git a/src/tools/rust-analyzer/crates/span/src/hygiene.rs b/src/tools/rust-analyzer/crates/span/src/hygiene.rs index 3863b3e809c..67d7bb9a0de 100644 --- a/src/tools/rust-analyzer/crates/span/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/span/src/hygiene.rs @@ -21,7 +21,7 @@ //! `ExpnData::call_site` in rustc, [`MacroCallLoc::call_site`] in rust-analyzer. use std::fmt; -use salsa::{InternId, InternValue}; +use ra_salsa::{InternId, InternValue}; use crate::MacroCallId; @@ -39,11 +39,11 @@ impl fmt::Debug for SyntaxContextId { } } -impl salsa::InternKey for SyntaxContextId { - fn from_intern_id(v: salsa::InternId) -> Self { +impl ra_salsa::InternKey for SyntaxContextId { + fn from_intern_id(v: ra_salsa::InternId) -> Self { SyntaxContextId(v) } - fn as_intern_id(&self) -> salsa::InternId { + fn as_intern_id(&self) -> ra_salsa::InternId { self.0 } } diff --git a/src/tools/rust-analyzer/crates/span/src/lib.rs b/src/tools/rust-analyzer/crates/span/src/lib.rs index 61e4c98128a..bd270bfe2b1 100644 --- a/src/tools/rust-analyzer/crates/span/src/lib.rs +++ b/src/tools/rust-analyzer/crates/span/src/lib.rs @@ -1,7 +1,7 @@ //! File and span related types. use std::fmt::{self, Write}; -use salsa::InternId; +use ra_salsa::InternId; mod ast_id; mod hygiene; @@ -261,13 +261,13 @@ pub struct MacroFileId { /// `MacroCallId` identifies a particular macro invocation, like /// `println!("Hello, {}", world)`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct MacroCallId(salsa::InternId); +pub struct MacroCallId(ra_salsa::InternId); -impl salsa::InternKey for MacroCallId { - fn from_intern_id(v: salsa::InternId) -> Self { +impl ra_salsa::InternKey for MacroCallId { + fn from_intern_id(v: ra_salsa::InternId) -> Self { MacroCallId(v) } - fn as_intern_id(&self) -> salsa::InternId { + fn as_intern_id(&self) -> ra_salsa::InternId { self.0 } } diff --git a/src/tools/rust-analyzer/xtask/src/tidy.rs b/src/tools/rust-analyzer/xtask/src/tidy.rs index ea51d33ed9c..0268e2473c0 100644 --- a/src/tools/rust-analyzer/xtask/src/tidy.rs +++ b/src/tools/rust-analyzer/xtask/src/tidy.rs @@ -223,7 +223,7 @@ struct TidyDocs { impl TidyDocs { fn visit(&mut self, path: &Path, text: &str) { // Tests and diagnostic fixes don't need module level comments. - if is_exclude_dir(path, &["tests", "test_data", "fixes", "grammar", "salsa"]) { + if is_exclude_dir(path, &["tests", "test_data", "fixes", "grammar", "ra-salsa"]) { return; } -- cgit 1.4.1-3-g733a5 From 2ee4925068ca162e8d62bfe5fe02430dc894cdb3 Mon Sep 17 00:00:00 2001 From: roife Date: Sun, 13 Oct 2024 05:42:07 +0800 Subject: feat: handle self-param outside of methods when renaming --- src/tools/rust-analyzer/crates/ide/src/rename.rs | 53 ++++++++++++++++++------ 1 file changed, 41 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index e46cb5a781f..f17c1fa5c62 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs @@ -421,19 +421,28 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt None } - let impl_def = self_param.syntax().ancestors().find_map(ast::Impl::cast)?; - let type_name = target_type_name(&impl_def)?; - - let mut replacement_text = String::from(new_name); - replacement_text.push_str(": "); - match (self_param.amp_token(), self_param.mut_token()) { - (Some(_), None) => replacement_text.push('&'), - (Some(_), Some(_)) => replacement_text.push_str("&mut "), - (_, _) => (), - }; - replacement_text.push_str(type_name.as_str()); + match self_param.syntax().ancestors().find_map(ast::Impl::cast) { + Some(impl_def) => { + let type_name = target_type_name(&impl_def)?; + + let mut replacement_text = String::from(new_name); + replacement_text.push_str(": "); + match (self_param.amp_token(), self_param.mut_token()) { + (Some(_), None) => replacement_text.push('&'), + (Some(_), Some(_)) => replacement_text.push_str("&mut "), + (_, _) => (), + }; + replacement_text.push_str(type_name.as_str()); - Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) + Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) + } + None => { + cov_mark::hit!(rename_self_outside_of_methods); + let mut replacement_text = String::from(new_name); + replacement_text.push_str(": _"); + Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) + } + } } #[cfg(test)] @@ -1977,6 +1986,26 @@ impl Foo { ); } + #[test] + fn test_self_outside_of_methods() { + cov_mark::check!(rename_self_outside_of_methods); + check( + "foo", + r#" +fn f($0self) -> i32 { + use self as _; + self.i +} +"#, + r#" +fn f(foo: _) -> i32 { + use self as _; + foo.i +} +"#, + ); + } + #[test] fn test_self_in_path_to_parameter() { check( -- cgit 1.4.1-3-g733a5 From 3c8e14666836446c6250b1cb93bf6ce2150ab543 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Fri, 11 Oct 2024 02:25:15 +0900 Subject: Do not consider match/let/ref of place that evaluates to ! to diverge, disallow coercions from them too --- src/tools/rust-analyzer/crates/hir-ty/src/infer.rs | 3 +- .../rust-analyzer/crates/hir-ty/src/infer/cast.rs | 15 +- .../crates/hir-ty/src/infer/closure.rs | 3 +- .../crates/hir-ty/src/infer/coerce.rs | 49 ++- .../rust-analyzer/crates/hir-ty/src/infer/expr.rs | 404 +++++++++++++++++---- .../crates/hir-ty/src/infer/mutability.rs | 13 +- .../rust-analyzer/crates/hir-ty/src/infer/pat.rs | 6 +- .../crates/hir-ty/src/tests/never_type.rs | 246 +++++++++++++ 8 files changed, 637 insertions(+), 102 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index 8bc3c50725d..88334b492d5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -57,7 +57,7 @@ use crate::{ db::HirDatabase, fold_tys, generics::Generics, - infer::{coerce::CoerceMany, unify::InferenceTable}, + infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable}, lower::ImplTraitLoweringMode, mir::MirSpan, to_assoc_type_id, @@ -1154,6 +1154,7 @@ impl<'a> InferenceContext<'a> { _ = self.infer_expr_coerce( self.body.body_expr, &Expectation::has_type(self.return_ty.clone()), + ExprIsRead::Yes, ) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs index 5d5bff08644..21d0be6ed5f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs @@ -5,8 +5,9 @@ use hir_def::{hir::ExprId, AdtId}; use stdx::never; use crate::{ - infer::unify::InferenceTable, Adjustment, Binders, DynTy, InferenceDiagnostic, Interner, - PlaceholderIndex, QuantifiedWhereClauses, Ty, TyExt, TyKind, TypeFlags, WhereClause, + infer::{coerce::CoerceNever, unify::InferenceTable}, + Adjustment, Binders, DynTy, InferenceDiagnostic, Interner, PlaceholderIndex, + QuantifiedWhereClauses, Ty, TyExt, TyKind, TypeFlags, WhereClause, }; #[derive(Debug)] @@ -128,7 +129,7 @@ impl CastCheck { return Ok(()); } - if let Ok((adj, _)) = table.coerce(&self.expr_ty, &self.cast_ty) { + if let Ok((adj, _)) = table.coerce(&self.expr_ty, &self.cast_ty, CoerceNever::Yes) { apply_adjustments(self.source_expr, adj); set_coercion_cast(self.source_expr); return Ok(()); @@ -154,7 +155,8 @@ impl CastCheck { let sig = self.expr_ty.callable_sig(table.db).expect("FnDef had no sig"); let sig = table.normalize_associated_types_in(sig); let fn_ptr = TyKind::Function(sig.to_fn_ptr()).intern(Interner); - if let Ok((adj, _)) = table.coerce(&self.expr_ty, &fn_ptr) { + if let Ok((adj, _)) = table.coerce(&self.expr_ty, &fn_ptr, CoerceNever::Yes) + { apply_adjustments(self.source_expr, adj); } else { return Err(CastError::IllegalCast); @@ -241,7 +243,8 @@ impl CastCheck { if let TyKind::Array(ety, _) = t_expr.kind(Interner) { // Coerce to a raw pointer so that we generate RawPtr in MIR. let array_ptr_type = TyKind::Raw(m_expr, t_expr.clone()).intern(Interner); - if let Ok((adj, _)) = table.coerce(&self.expr_ty, &array_ptr_type) { + if let Ok((adj, _)) = table.coerce(&self.expr_ty, &array_ptr_type, CoerceNever::Yes) + { apply_adjustments(self.source_expr, adj); } else { never!( @@ -253,7 +256,7 @@ impl CastCheck { // This is a less strict condition than rustc's `demand_eqtype`, // but false negative is better than false positive - if table.coerce(ety, t_cast).is_ok() { + if table.coerce(ety, t_cast, CoerceNever::Yes).is_ok() { return Ok(()); } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs index 5cad08b9395..e9825cf0998 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs @@ -29,6 +29,7 @@ use crate::{ db::{HirDatabase, InternedClosure}, error_lifetime, from_chalk_trait_id, from_placeholder_idx, generics::Generics, + infer::coerce::CoerceNever, make_binders, mir::{BorrowKind, MirSpan, MutBorrowKind, ProjectionElem}, to_chalk_trait_id, @@ -65,7 +66,7 @@ impl InferenceContext<'_> { } // Deduction from where-clauses in scope, as well as fn-pointer coercion are handled here. - let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty); + let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty, CoerceNever::Yes); // Coroutines are not Fn* so return early. if matches!(closure_ty.kind(Interner), TyKind::Coroutine(..)) { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs index 7e758c0b517..366c3cb0f17 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs @@ -139,8 +139,8 @@ impl CoerceMany { }; if let Some(sig) = sig { let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(Interner); - let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty); - let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty); + let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty, CoerceNever::Yes); + let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty, CoerceNever::Yes); if let (Ok(result1), Ok(result2)) = (result1, result2) { ctx.table.register_infer_ok(InferOk { value: (), goals: result1.goals }); for &e in &self.expressions { @@ -159,9 +159,9 @@ impl CoerceMany { // type is a type variable and the new one is `!`, trying it the other // way around first would mean we make the type variable `!`, instead of // just marking it as possibly diverging. - if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty()) { + if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty(), CoerceNever::Yes) { self.final_ty = Some(res); - } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) { + } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty, CoerceNever::Yes) { self.final_ty = Some(res); } else { match cause { @@ -197,7 +197,7 @@ pub(crate) fn coerce( let vars = table.fresh_subst(tys.binders.as_slice(Interner)); let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner); let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner); - let (adjustments, ty) = table.coerce(&ty1_with_vars, &ty2_with_vars)?; + let (adjustments, ty) = table.coerce(&ty1_with_vars, &ty2_with_vars, CoerceNever::Yes)?; // default any type vars that weren't unified back to their original bound vars // (kind of hacky) let find_var = |iv| { @@ -219,6 +219,12 @@ pub(crate) fn coerce( Ok((adjustments, table.resolve_with_fallback(ty, &fallback))) } +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum CoerceNever { + Yes, + No, +} + impl InferenceContext<'_> { /// Unify two types, but may coerce the first one to the second one /// using "implicit coercion rules" if needed. @@ -227,10 +233,16 @@ impl InferenceContext<'_> { expr: Option, from_ty: &Ty, to_ty: &Ty, + // [Comment from rustc](https://github.com/rust-lang/rust/blob/4cc494bbfe9911d24f3ee521f98d5c6bb7e3ffe8/compiler/rustc_hir_typeck/src/coercion.rs#L85-L89) + // Whether we allow `NeverToAny` coercions. This is unsound if we're + // coercing a place expression without it counting as a read in the MIR. + // This is a side-effect of HIR not really having a great distinction + // between places and values. + coerce_never: CoerceNever, ) -> Result { let from_ty = self.resolve_ty_shallow(from_ty); let to_ty = self.resolve_ty_shallow(to_ty); - let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty)?; + let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty, coerce_never)?; if let Some(expr) = expr { self.write_expr_adj(expr, adjustments); } @@ -245,10 +257,11 @@ impl InferenceTable<'_> { &mut self, from_ty: &Ty, to_ty: &Ty, + coerce_never: CoerceNever, ) -> Result<(Vec, Ty), TypeError> { let from_ty = self.resolve_ty_shallow(from_ty); let to_ty = self.resolve_ty_shallow(to_ty); - match self.coerce_inner(from_ty, &to_ty) { + match self.coerce_inner(from_ty, &to_ty, coerce_never) { Ok(InferOk { value: (adjustments, ty), goals }) => { self.register_infer_ok(InferOk { value: (), goals }); Ok((adjustments, ty)) @@ -260,19 +273,23 @@ impl InferenceTable<'_> { } } - fn coerce_inner(&mut self, from_ty: Ty, to_ty: &Ty) -> CoerceResult { + fn coerce_inner(&mut self, from_ty: Ty, to_ty: &Ty, coerce_never: CoerceNever) -> CoerceResult { if from_ty.is_never() { - // Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound - // type variable, we want `?T` to fallback to `!` if not - // otherwise constrained. An example where this arises: - // - // let _: Option = Some({ return; }); - // - // here, we would coerce from `!` to `?T`. if let TyKind::InferenceVar(tv, TyVariableKind::General) = to_ty.kind(Interner) { self.set_diverging(*tv, true); } - return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]); + if coerce_never == CoerceNever::Yes { + // Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound + // type variable, we want `?T` to fallback to `!` if not + // otherwise constrained. An example where this arises: + // + // let _: Option = Some({ return; }); + // + // here, we would coerce from `!` to `?T`. + return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]); + } else { + return self.unify_and(&from_ty, to_ty, identity); + } } // If we are coercing into a TAIT, coerce into its proxy inference var, instead. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index a04e7b17ae6..657e4d77966 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -10,10 +10,11 @@ use either::Either; use hir_def::{ hir::{ ArithOp, Array, AsmOperand, AsmOptions, BinaryOp, ClosureKind, Expr, ExprId, LabelId, - Literal, Statement, UnaryOp, + Literal, Pat, PatId, Statement, UnaryOp, }, lang_item::{LangItem, LangItemTarget}, path::{GenericArg, GenericArgs, Path}, + resolver::ValueNs, BlockId, FieldId, GenericDefId, GenericParamId, ItemContainerId, Lookup, TupleFieldId, TupleId, }; use hir_expand::name::Name; @@ -28,7 +29,7 @@ use crate::{ error_lifetime, generics::{generics, Generics}, infer::{ - coerce::{CoerceMany, CoercionCause}, + coerce::{CoerceMany, CoerceNever, CoercionCause}, find_continuable, pat::contains_explicit_ref_binding, BreakableKind, @@ -52,9 +53,20 @@ use super::{ Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, }; +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum ExprIsRead { + Yes, + No, +} + impl InferenceContext<'_> { - pub(crate) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(tgt_expr, expected); + pub(crate) fn infer_expr( + &mut self, + tgt_expr: ExprId, + expected: &Expectation, + is_read: ExprIsRead, + ) -> Ty { + let ty = self.infer_expr_inner(tgt_expr, expected, is_read); if let Some(expected_ty) = expected.only_has_type(&mut self.table) { let could_unify = self.unify(&ty, &expected_ty); if !could_unify { @@ -67,16 +79,26 @@ impl InferenceContext<'_> { ty } - pub(crate) fn infer_expr_no_expect(&mut self, tgt_expr: ExprId) -> Ty { - self.infer_expr_inner(tgt_expr, &Expectation::None) + pub(crate) fn infer_expr_no_expect(&mut self, tgt_expr: ExprId, is_read: ExprIsRead) -> Ty { + self.infer_expr_inner(tgt_expr, &Expectation::None, is_read) } /// Infer type of expression with possibly implicit coerce to the expected type. /// Return the type after possible coercion. - pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(expr, expected); + pub(super) fn infer_expr_coerce( + &mut self, + expr: ExprId, + expected: &Expectation, + is_read: ExprIsRead, + ) -> Ty { + let ty = self.infer_expr_inner(expr, expected, is_read); if let Some(target) = expected.only_has_type(&mut self.table) { - match self.coerce(Some(expr), &ty, &target) { + let coerce_never = if self.expr_guaranteed_to_constitute_read_for_never(expr, is_read) { + CoerceNever::Yes + } else { + CoerceNever::No + }; + match self.coerce(Some(expr), &ty, &target, coerce_never) { Ok(res) => res, Err(_) => { self.result.type_mismatches.insert( @@ -91,8 +113,137 @@ impl InferenceContext<'_> { } } - fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(expr, expected); + /// Whether this expression constitutes a read of value of the type that + /// it evaluates to. + /// + /// This is used to determine if we should consider the block to diverge + /// if the expression evaluates to `!`, and if we should insert a `NeverToAny` + /// coercion for values of type `!`. + /// + /// This function generally returns `false` if the expression is a place + /// expression and the *parent* expression is the scrutinee of a match or + /// the pointee of an `&` addr-of expression, since both of those parent + /// expressions take a *place* and not a value. + pub(super) fn expr_guaranteed_to_constitute_read_for_never( + &mut self, + expr: ExprId, + is_read: ExprIsRead, + ) -> bool { + // rustc does the place expr check first, but since we are feeding + // readness of the `expr` as a given value, we just can short-circuit + // the place expr check if it's true(see codes and comments below) + if is_read == ExprIsRead::Yes { + return true; + } + + // We only care about place exprs. Anything else returns an immediate + // which would constitute a read. We don't care about distinguishing + // "syntactic" place exprs since if the base of a field projection is + // not a place then it would've been UB to read from it anyways since + // that constitutes a read. + if !self.is_syntactic_place_expr(expr) { + return true; + } + + // rustc queries parent hir node of `expr` here and determine whether + // the current `expr` is read of value per its parent. + // But since we don't have hir node, we cannot follow such "bottom-up" + // method. + // So, we pass down such readness from the parent expression through the + // recursive `infer_expr*` calls in a "top-down" manner. + is_read == ExprIsRead::Yes + } + + /// Whether this pattern constitutes a read of value of the scrutinee that + /// it is matching against. This is used to determine whether we should + /// perform `NeverToAny` coercions. + fn pat_guaranteed_to_constitute_read_for_never(&self, pat: PatId) -> bool { + match &self.body[pat] { + // Does not constitute a read. + Pat::Wild => false, + + // This is unnecessarily restrictive when the pattern that doesn't + // constitute a read is unreachable. + // + // For example `match *never_ptr { value => {}, _ => {} }` or + // `match *never_ptr { _ if false => {}, value => {} }`. + // + // It is however fine to be restrictive here; only returning `true` + // can lead to unsoundness. + Pat::Or(subpats) => { + subpats.iter().all(|pat| self.pat_guaranteed_to_constitute_read_for_never(*pat)) + } + + // All of these constitute a read, or match on something that isn't `!`, + // which would require a `NeverToAny` coercion. + Pat::Bind { .. } + | Pat::TupleStruct { .. } + | Pat::Path(_) + | Pat::Tuple { .. } + | Pat::Box { .. } + | Pat::Ref { .. } + | Pat::Lit(_) + | Pat::Range { .. } + | Pat::Slice { .. } + | Pat::ConstBlock(_) + | Pat::Record { .. } + | Pat::Missing => true, + } + } + + fn is_syntactic_place_expr(&self, expr: ExprId) -> bool { + match &self.body[expr] { + // Lang item paths cannot currently be local variables or statics. + Expr::Path(Path::LangItem(_, _)) => false, + Expr::Path(Path::Normal { type_anchor: Some(_), .. }) => false, + Expr::Path(path) => self + .resolver + .resolve_path_in_value_ns_fully(self.db.upcast(), path) + .map_or(true, |res| matches!(res, ValueNs::LocalBinding(_) | ValueNs::StaticId(_))), + Expr::Underscore => true, + Expr::UnaryOp { op: UnaryOp::Deref, .. } => true, + Expr::Field { .. } | Expr::Index { .. } => true, + Expr::Call { .. } + | Expr::MethodCall { .. } + | Expr::Tuple { .. } + | Expr::If { .. } + | Expr::Match { .. } + | Expr::Closure { .. } + | Expr::Block { .. } + | Expr::Array(..) + | Expr::Break { .. } + | Expr::Continue { .. } + | Expr::Return { .. } + | Expr::Become { .. } + | Expr::Let { .. } + | Expr::Loop { .. } + | Expr::InlineAsm(..) + | Expr::OffsetOf(..) + | Expr::Literal(..) + | Expr::Const(..) + | Expr::UnaryOp { .. } + | Expr::BinaryOp { .. } + | Expr::Yield { .. } + | Expr::Cast { .. } + | Expr::Async { .. } + | Expr::Unsafe { .. } + | Expr::Await { .. } + | Expr::Ref { .. } + | Expr::Range { .. } + | Expr::Box { .. } + | Expr::RecordLit { .. } + | Expr::Yeet { .. } + | Expr::Missing => false, + } + } + + fn infer_expr_coerce_never( + &mut self, + expr: ExprId, + expected: &Expectation, + is_read: ExprIsRead, + ) -> Ty { + let ty = self.infer_expr_inner(expr, expected, is_read); // While we don't allow *arbitrary* coercions here, we *do* allow // coercions from `!` to `expected`. if ty.is_never() { @@ -105,7 +256,7 @@ impl InferenceContext<'_> { } if let Some(target) = expected.only_has_type(&mut self.table) { - self.coerce(Some(expr), &ty, &target) + self.coerce(Some(expr), &ty, &target, CoerceNever::Yes) .expect("never-to-any coercion should always succeed") } else { ty @@ -124,7 +275,12 @@ impl InferenceContext<'_> { } } - fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { + fn infer_expr_inner( + &mut self, + tgt_expr: ExprId, + expected: &Expectation, + is_read: ExprIsRead, + ) -> Ty { self.db.unwind_if_cancelled(); let ty = match &self.body[tgt_expr] { @@ -134,17 +290,18 @@ impl InferenceContext<'_> { self.infer_expr_coerce_never( condition, &Expectation::HasType(self.result.standard_types.bool_.clone()), + ExprIsRead::Yes, ); let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); - let then_ty = self.infer_expr_inner(then_branch, expected); + let then_ty = self.infer_expr_inner(then_branch, expected, ExprIsRead::Yes); let then_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let mut coerce = CoerceMany::new(expected.coercion_target_type(&mut self.table)); coerce.coerce(self, Some(then_branch), &then_ty, CoercionCause::Expr(then_branch)); match else_branch { Some(else_branch) => { - let else_ty = self.infer_expr_inner(else_branch, expected); + let else_ty = self.infer_expr_inner(else_branch, expected, ExprIsRead::Yes); let else_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); coerce.coerce( self, @@ -163,7 +320,12 @@ impl InferenceContext<'_> { coerce.complete(self) } &Expr::Let { pat, expr } => { - let input_ty = self.infer_expr(expr, &Expectation::none()); + let child_is_read = if self.pat_guaranteed_to_constitute_read_for_never(pat) { + ExprIsRead::Yes + } else { + ExprIsRead::No + }; + let input_ty = self.infer_expr(expr, &Expectation::none(), child_is_read); self.infer_top_pat(pat, &input_ty); self.result.standard_types.bool_.clone() } @@ -176,7 +338,7 @@ impl InferenceContext<'_> { Expr::Const(id) => { self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { let loc = this.db.lookup_intern_anonymous_const(*id); - this.infer_expr(loc.root, expected) + this.infer_expr(loc.root, expected, ExprIsRead::Yes) }) .1 } @@ -189,7 +351,11 @@ impl InferenceContext<'_> { let ty = self.table.new_type_var(); let (breaks, ()) = self.with_breakable_ctx(BreakableKind::Loop, Some(ty), label, |this| { - this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); + this.infer_expr( + body, + &Expectation::HasType(TyBuilder::unit()), + ExprIsRead::Yes, + ); }); match breaks { @@ -312,7 +478,7 @@ impl InferenceContext<'_> { ty } Expr::Call { callee, args, .. } => { - let callee_ty = self.infer_expr(*callee, &Expectation::none()); + let callee_ty = self.infer_expr(*callee, &Expectation::none(), ExprIsRead::Yes); let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false); let (res, derefed_callee) = loop { let Some((callee_deref_ty, _)) = derefs.next() else { @@ -393,7 +559,12 @@ impl InferenceContext<'_> { expected, ), Expr::Match { expr, arms } => { - let input_ty = self.infer_expr(*expr, &Expectation::none()); + let scrutinee_is_read = arms + .iter() + .all(|arm| self.pat_guaranteed_to_constitute_read_for_never(arm.pat)); + let scrutinee_is_read = + if scrutinee_is_read { ExprIsRead::Yes } else { ExprIsRead::No }; + let input_ty = self.infer_expr(*expr, &Expectation::none(), scrutinee_is_read); if arms.is_empty() { self.diverges = Diverges::Always; @@ -423,11 +594,12 @@ impl InferenceContext<'_> { self.infer_expr_coerce_never( guard_expr, &Expectation::HasType(self.result.standard_types.bool_.clone()), + ExprIsRead::Yes, ); } self.diverges = Diverges::Maybe; - let arm_ty = self.infer_expr_inner(arm.expr, &expected); + let arm_ty = self.infer_expr_inner(arm.expr, &expected, ExprIsRead::Yes); all_arms_diverge &= self.diverges; coerce.coerce(self, Some(arm.expr), &arm_ty, CoercionCause::Expr(arm.expr)); } @@ -480,7 +652,11 @@ impl InferenceContext<'_> { }, None => self.err_ty(), }; - self.infer_expr_inner(expr, &Expectation::HasType(opt_coerce_to)) + self.infer_expr_inner( + expr, + &Expectation::HasType(opt_coerce_to), + ExprIsRead::Yes, + ) } else { TyBuilder::unit() }; @@ -517,10 +693,14 @@ impl InferenceContext<'_> { Expr::Yield { expr } => { if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() { if let Some(expr) = expr { - self.infer_expr_coerce(*expr, &Expectation::has_type(yield_ty)); + self.infer_expr_coerce( + *expr, + &Expectation::has_type(yield_ty), + ExprIsRead::Yes, + ); } else { let unit = self.result.standard_types.unit.clone(); - let _ = self.coerce(Some(tgt_expr), &unit, &yield_ty); + let _ = self.coerce(Some(tgt_expr), &unit, &yield_ty, CoerceNever::Yes); } resume_ty } else { @@ -530,7 +710,7 @@ impl InferenceContext<'_> { } Expr::Yeet { expr } => { if let &Some(expr) = expr { - self.infer_expr_no_expect(expr); + self.infer_expr_no_expect(expr, ExprIsRead::Yes); } self.result.standard_types.never.clone() } @@ -589,28 +769,37 @@ impl InferenceContext<'_> { // Field type might have some unknown types // FIXME: we may want to emit a single type variable for all instance of type fields? let field_ty = self.insert_type_vars(field_ty); - self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); + self.infer_expr_coerce( + field.expr, + &Expectation::has_type(field_ty), + ExprIsRead::Yes, + ); } } None => { for field in fields.iter() { - self.infer_expr_coerce(field.expr, &Expectation::None); + // Field projections don't constitute reads. + self.infer_expr_coerce(field.expr, &Expectation::None, ExprIsRead::No); } } } if let Some(expr) = spread { - self.infer_expr(*expr, &Expectation::has_type(ty.clone())); + self.infer_expr(*expr, &Expectation::has_type(ty.clone()), ExprIsRead::Yes); } ty } Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name, expected), Expr::Await { expr } => { - let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); + let inner_ty = self.infer_expr_inner(*expr, &Expectation::none(), ExprIsRead::Yes); self.resolve_associated_type(inner_ty, self.resolve_future_future_output()) } Expr::Cast { expr, type_ref } => { let cast_ty = self.make_ty(type_ref); - let expr_ty = self.infer_expr(*expr, &Expectation::Castable(cast_ty.clone())); + let expr_ty = self.infer_expr( + *expr, + &Expectation::Castable(cast_ty.clone()), + ExprIsRead::Yes, + ); self.deferred_cast_checks.push(CastCheck::new( tgt_expr, *expr, @@ -638,7 +827,7 @@ impl InferenceContext<'_> { } else { Expectation::none() }; - let inner_ty = self.infer_expr_inner(*expr, &expectation); + let inner_ty = self.infer_expr_inner(*expr, &expectation, ExprIsRead::Yes); match rawness { Rawness::RawPtr => TyKind::Raw(mutability, inner_ty), Rawness::Ref => { @@ -650,7 +839,7 @@ impl InferenceContext<'_> { } &Expr::Box { expr } => self.infer_expr_box(expr, expected), Expr::UnaryOp { expr, op } => { - let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); + let inner_ty = self.infer_expr_inner(*expr, &Expectation::none(), ExprIsRead::Yes); let inner_ty = self.resolve_ty_shallow(&inner_ty); // FIXME: Note down method resolution her match op { @@ -720,19 +909,32 @@ impl InferenceContext<'_> { // cannot happen in destructuring assignments because of how // they are desugared. if is_ordinary { - let lhs_ty = self.infer_expr(lhs, &Expectation::none()); - self.infer_expr_coerce(*rhs, &Expectation::has_type(lhs_ty)); + // LHS of assignment doesn't constitute reads. + let lhs_ty = self.infer_expr(lhs, &Expectation::none(), ExprIsRead::No); + self.infer_expr_coerce( + *rhs, + &Expectation::has_type(lhs_ty), + ExprIsRead::No, + ); } else { - let rhs_ty = self.infer_expr(*rhs, &Expectation::none()); + let rhs_ty = self.infer_expr(*rhs, &Expectation::none(), ExprIsRead::Yes); self.infer_assignee_expr(lhs, &rhs_ty); } self.result.standard_types.unit.clone() } Some(BinaryOp::LogicOp(_)) => { let bool_ty = self.result.standard_types.bool_.clone(); - self.infer_expr_coerce(*lhs, &Expectation::HasType(bool_ty.clone())); + self.infer_expr_coerce( + *lhs, + &Expectation::HasType(bool_ty.clone()), + ExprIsRead::Yes, + ); let lhs_diverges = self.diverges; - self.infer_expr_coerce(*rhs, &Expectation::HasType(bool_ty.clone())); + self.infer_expr_coerce( + *rhs, + &Expectation::HasType(bool_ty.clone()), + ExprIsRead::Yes, + ); // Depending on the LHS' value, the RHS can never execute. self.diverges = lhs_diverges; bool_ty @@ -741,11 +943,12 @@ impl InferenceContext<'_> { _ => self.err_ty(), }, Expr::Range { lhs, rhs, range_type } => { - let lhs_ty = lhs.map(|e| self.infer_expr_inner(e, &Expectation::none())); + let lhs_ty = + lhs.map(|e| self.infer_expr_inner(e, &Expectation::none(), ExprIsRead::Yes)); let rhs_expect = lhs_ty .as_ref() .map_or_else(Expectation::none, |ty| Expectation::has_type(ty.clone())); - let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect)); + let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect, ExprIsRead::Yes)); match (range_type, lhs_ty, rhs_ty) { (RangeOp::Exclusive, None, None) => match self.resolve_range_full() { Some(adt) => TyBuilder::adt(self.db, adt).build(), @@ -779,8 +982,8 @@ impl InferenceContext<'_> { } } Expr::Index { base, index, is_assignee_expr } => { - let base_ty = self.infer_expr_inner(*base, &Expectation::none()); - let index_ty = self.infer_expr(*index, &Expectation::none()); + let base_ty = self.infer_expr_inner(*base, &Expectation::none(), ExprIsRead::Yes); + let index_ty = self.infer_expr(*index, &Expectation::none(), ExprIsRead::Yes); if let Some(index_trait) = self.resolve_lang_trait(LangItem::Index) { let canonicalized = self.canonicalize(base_ty.clone()); @@ -851,7 +1054,11 @@ impl InferenceContext<'_> { }; for (expr, ty) in exprs.iter().zip(tys.iter_mut()) { - *ty = self.infer_expr_coerce(*expr, &Expectation::has_type(ty.clone())); + *ty = self.infer_expr_coerce( + *expr, + &Expectation::has_type(ty.clone()), + ExprIsRead::Yes, + ); } TyKind::Tuple(tys.len(), Substitution::from_iter(Interner, tys)).intern(Interner) @@ -958,7 +1165,7 @@ impl InferenceContext<'_> { Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), Expr::InlineAsm(asm) => { let mut check_expr_asm_operand = |expr, is_input: bool| { - let ty = self.infer_expr_no_expect(expr); + let ty = self.infer_expr_no_expect(expr, ExprIsRead::Yes); // If this is an input value, we require its type to be fully resolved // at this point. This allows us to provide helpful coercions which help @@ -975,11 +1182,11 @@ impl InferenceContext<'_> { CallableSig::from_def(self.db, *def, parameters).to_fn_ptr(), ) .intern(Interner); - _ = self.coerce(Some(expr), &ty, &fnptr_ty); + _ = self.coerce(Some(expr), &ty, &fnptr_ty, CoerceNever::Yes); } TyKind::Ref(mutbl, _, base_ty) => { let ptr_ty = TyKind::Raw(*mutbl, base_ty.clone()).intern(Interner); - _ = self.coerce(Some(expr), &ty, &ptr_ty); + _ = self.coerce(Some(expr), &ty, &ptr_ty, CoerceNever::Yes); } _ => {} } @@ -1016,7 +1223,9 @@ impl InferenceContext<'_> { // use a new type variable if we got unknown here let ty = self.insert_type_vars_shallow(ty); self.write_expr_ty(tgt_expr, ty.clone()); - if self.resolve_ty_shallow(&ty).is_never() { + if self.resolve_ty_shallow(&ty).is_never() + && self.expr_guaranteed_to_constitute_read_for_never(tgt_expr, is_read) + { // Any expression that produces a value of type `!` must have diverged self.diverges = Diverges::Always; } @@ -1041,7 +1250,7 @@ impl InferenceContext<'_> { let (_, inner_ty) = self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { let ty = this.infer_block(tgt_expr, *id, statements, *tail, None, expected); if let Some(target) = expected.only_has_type(&mut this.table) { - match this.coerce(Some(tgt_expr), &ty, &target) { + match this.coerce(Some(tgt_expr), &ty, &target, CoerceNever::Yes) { Ok(res) => res, Err(_) => { this.result.type_mismatches.insert( @@ -1153,7 +1362,7 @@ impl InferenceContext<'_> { Array::ElementList { elements, .. } => { let mut coerce = CoerceMany::new(elem_ty); for &expr in elements.iter() { - let cur_elem_ty = self.infer_expr_inner(expr, &expected); + let cur_elem_ty = self.infer_expr_inner(expr, &expected, ExprIsRead::Yes); coerce.coerce(self, Some(expr), &cur_elem_ty, CoercionCause::Expr(expr)); } ( @@ -1162,13 +1371,17 @@ impl InferenceContext<'_> { ) } &Array::Repeat { initializer, repeat } => { - self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty.clone())); + self.infer_expr_coerce( + initializer, + &Expectation::has_type(elem_ty.clone()), + ExprIsRead::Yes, + ); let usize = TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner); match self.body[repeat] { Expr::Underscore => { self.write_expr_ty(repeat, usize); } - _ => _ = self.infer_expr(repeat, &Expectation::HasType(usize)), + _ => _ = self.infer_expr(repeat, &Expectation::HasType(usize), ExprIsRead::Yes), } ( @@ -1193,7 +1406,8 @@ impl InferenceContext<'_> { .as_mut() .expect("infer_return called outside function body") .expected_ty(); - let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty)); + let return_expr_ty = + self.infer_expr_inner(expr, &Expectation::HasType(ret_ty), ExprIsRead::Yes); let mut coerce_many = self.return_coercion.take().unwrap(); coerce_many.coerce(self, Some(expr), &return_expr_ty, CoercionCause::Expr(expr)); self.return_coercion = Some(coerce_many); @@ -1213,7 +1427,7 @@ impl InferenceContext<'_> { None => { // FIXME: diagnose return outside of function if let Some(expr) = expr { - self.infer_expr_no_expect(expr); + self.infer_expr_no_expect(expr, ExprIsRead::Yes); } } } @@ -1225,8 +1439,11 @@ impl InferenceContext<'_> { Some(return_coercion) => { let ret_ty = return_coercion.expected_ty(); - let call_expr_ty = - self.infer_expr_inner(expr, &Expectation::HasType(ret_ty.clone())); + let call_expr_ty = self.infer_expr_inner( + expr, + &Expectation::HasType(ret_ty.clone()), + ExprIsRead::Yes, + ); // NB: this should *not* coerce. // tail calls don't support any coercions except lifetimes ones (like `&'static u8 -> &'a u8`). @@ -1234,7 +1451,7 @@ impl InferenceContext<'_> { } None => { // FIXME: diagnose `become` outside of functions - self.infer_expr_no_expect(expr); + self.infer_expr_no_expect(expr, ExprIsRead::Yes); } } @@ -1255,7 +1472,7 @@ impl InferenceContext<'_> { }) .unwrap_or_else(Expectation::none); - let inner_ty = self.infer_expr_inner(inner_expr, &inner_exp); + let inner_ty = self.infer_expr_inner(inner_expr, &inner_exp, ExprIsRead::Yes); TyBuilder::adt(self.db, box_id) .push(inner_ty) .fill_with_defaults(self.db, || self.table.new_type_var()) @@ -1333,12 +1550,13 @@ impl InferenceContext<'_> { Expr::Underscore => rhs_ty.clone(), _ => { // `lhs` is a place expression, a unit struct, or an enum variant. - let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none()); + // LHS of assignment doesn't constitute reads. + let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none(), ExprIsRead::No); // This is the only branch where this function may coerce any type. // We are returning early to avoid the unifiability check below. let lhs_ty = self.insert_type_vars_shallow(lhs_ty); - let ty = match self.coerce(None, &rhs_ty, &lhs_ty) { + let ty = match self.coerce(None, &rhs_ty, &lhs_ty, CoerceNever::Yes) { Ok(ty) => ty, Err(_) => { self.result.type_mismatches.insert( @@ -1373,7 +1591,12 @@ impl InferenceContext<'_> { tgt_expr: ExprId, ) -> Ty { let lhs_expectation = Expectation::none(); - let lhs_ty = self.infer_expr(lhs, &lhs_expectation); + let is_read = if matches!(op, BinaryOp::Assignment { .. }) { + ExprIsRead::Yes + } else { + ExprIsRead::No + }; + let lhs_ty = self.infer_expr(lhs, &lhs_expectation, is_read); let rhs_ty = self.table.new_type_var(); let trait_func = lang_items_for_bin_op(op).and_then(|(name, lang_item)| { @@ -1396,7 +1619,7 @@ impl InferenceContext<'_> { self.err_ty() }; - self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty)); + self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty), ExprIsRead::Yes); return ret_ty; } @@ -1415,7 +1638,7 @@ impl InferenceContext<'_> { let method_ty = self.db.value_ty(func.into()).unwrap().substitute(Interner, &subst); self.register_obligations_for_call(&method_ty); - self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty.clone())); + self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty.clone()), ExprIsRead::Yes); let ret_ty = match method_ty.callable_sig(self.db) { Some(sig) => { @@ -1487,12 +1710,25 @@ impl InferenceContext<'_> { .unwrap_or_else(|| this.table.new_type_var()); let ty = if let Some(expr) = initializer { + // If we have a subpattern that performs a read, we want to consider this + // to diverge for compatibility to support something like `let x: () = *never_ptr;`. + let target_is_read = + if this.pat_guaranteed_to_constitute_read_for_never(*pat) { + ExprIsRead::Yes + } else { + ExprIsRead::No + }; let ty = if contains_explicit_ref_binding(this.body, *pat) { - this.infer_expr(*expr, &Expectation::has_type(decl_ty.clone())) + this.infer_expr( + *expr, + &Expectation::has_type(decl_ty.clone()), + target_is_read, + ) } else { this.infer_expr_coerce( *expr, &Expectation::has_type(decl_ty.clone()), + target_is_read, ) }; if type_ref.is_some() { @@ -1512,17 +1748,19 @@ impl InferenceContext<'_> { this.infer_expr_coerce( *expr, &Expectation::HasType(this.result.standard_types.never.clone()), + ExprIsRead::Yes, ); this.diverges = previous_diverges; } } &Statement::Expr { expr, has_semi } => { if has_semi { - this.infer_expr(expr, &Expectation::none()); + this.infer_expr(expr, &Expectation::none(), ExprIsRead::Yes); } else { this.infer_expr_coerce( expr, &Expectation::HasType(this.result.standard_types.unit.clone()), + ExprIsRead::Yes, ); } } @@ -1532,7 +1770,7 @@ impl InferenceContext<'_> { // FIXME: This should make use of the breakable CoerceMany if let Some(expr) = tail { - this.infer_expr_coerce(expr, expected) + this.infer_expr_coerce(expr, expected, ExprIsRead::Yes) } else { // Citing rustc: if there is no explicit tail expression, // that is typically equivalent to a tail expression @@ -1545,8 +1783,20 @@ impl InferenceContext<'_> { // we don't even make an attempt at coercion this.table.new_maybe_never_var() } else if let Some(t) = expected.only_has_type(&mut this.table) { + let coerce_never = if this + .expr_guaranteed_to_constitute_read_for_never(expr, ExprIsRead::Yes) + { + CoerceNever::Yes + } else { + CoerceNever::No + }; if this - .coerce(Some(expr), &this.result.standard_types.unit.clone(), &t) + .coerce( + Some(expr), + &this.result.standard_types.unit.clone(), + &t, + coerce_never, + ) .is_err() { this.result.type_mismatches.insert( @@ -1658,7 +1908,8 @@ impl InferenceContext<'_> { name: &Name, expected: &Expectation, ) -> Ty { - let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none()); + // Field projections don't constitute reads. + let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none(), ExprIsRead::No); if name.is_missing() { // Bail out early, don't even try to look up field. Also, we don't issue an unresolved @@ -1730,7 +1981,7 @@ impl InferenceContext<'_> { generic_args: Option<&GenericArgs>, expected: &Expectation, ) -> Ty { - let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none()); + let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none(), ExprIsRead::Yes); let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); let resolved = method_resolution::lookup_method( @@ -1917,7 +2168,7 @@ impl InferenceContext<'_> { let expected_ty = self.normalize_associated_types_in(expected_ty); let expected = Expectation::rvalue_hint(self, expected_ty); // infer with the expected type we have... - let ty = self.infer_expr_inner(arg, &expected); + let ty = self.infer_expr_inner(arg, &expected, ExprIsRead::Yes); // then coerce to either the expected type or just the formal parameter type let coercion_target = if let Some(ty) = expected.only_has_type(&mut self.table) { @@ -1931,7 +2182,20 @@ impl InferenceContext<'_> { // The function signature may contain some unknown types, so we need to insert // type vars here to avoid type mismatch false positive. let coercion_target = self.insert_type_vars(coercion_target); - if self.coerce(Some(arg), &ty, &coercion_target).is_err() && !arg_count_mismatch { + + // Any expression that produces a value of type `!` must have diverged, + // unless it's a place expression that isn't being read from, in which case + // diverging would be unsound since we may never actually read the `!`. + // e.g. `let _ = *never_ptr;` with `never_ptr: *const !`. + let coerce_never = + if self.expr_guaranteed_to_constitute_read_for_never(arg, ExprIsRead::Yes) { + CoerceNever::Yes + } else { + CoerceNever::No + }; + if self.coerce(Some(arg), &ty, &coercion_target, coerce_never).is_err() + && !arg_count_mismatch + { self.result.type_mismatches.insert( arg.into(), TypeMismatch { expected: coercion_target, actual: ty.clone() }, @@ -2106,7 +2370,7 @@ impl InferenceContext<'_> { } let _ty = arg.data(Interner).ty.clone(); let expected = Expectation::none(); // FIXME use actual const ty, when that is lowered correctly - self.infer_expr(args[arg_idx as usize], &expected); + self.infer_expr(args[arg_idx as usize], &expected, ExprIsRead::Yes); // FIXME: evaluate and unify with the const } let mut indices = legacy_const_generics_indices.as_ref().clone(); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs index 8dcaa9c581e..6a0daee6ea9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs @@ -12,12 +12,11 @@ use hir_expand::name::Name; use intern::sym; use crate::{ - infer::Expectation, lower::lower_to_chalk_mutability, Adjust, Adjustment, AutoBorrow, Interner, - OverloadedDeref, TyBuilder, TyKind, + infer::{expr::ExprIsRead, Expectation, InferenceContext}, + lower::lower_to_chalk_mutability, + Adjust, Adjustment, AutoBorrow, Interner, OverloadedDeref, TyBuilder, TyKind, }; -use super::InferenceContext; - impl InferenceContext<'_> { pub(crate) fn infer_mut_body(&mut self) { self.infer_mut_expr(self.body.body_expr, Mutability::Not); @@ -164,7 +163,11 @@ impl InferenceContext<'_> { if let Some(ty) = self.result.type_of_expr.get(index) { ty.clone() } else { - self.infer_expr(index, &Expectation::none()) + self.infer_expr( + index, + &Expectation::none(), + ExprIsRead::Yes, + ) }; let trait_ref = TyBuilder::trait_ref(self.db, index_trait) .push(base_ty) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs index 50542b2acd4..fee6755408e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs @@ -12,7 +12,7 @@ use stdx::TupleExt; use crate::{ consteval::{try_const_usize, usize_const}, - infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, + infer::{expr::ExprIsRead, BindingMode, Expectation, InferenceContext, TypeMismatch}, lower::lower_to_chalk_mutability, primitive::UintTy, static_lifetime, InferenceDiagnostic, Interner, Mutability, Scalar, Substitution, Ty, @@ -361,7 +361,7 @@ impl InferenceContext<'_> { None => self.err_ty(), }, Pat::ConstBlock(expr) => { - self.infer_expr(*expr, &Expectation::has_type(expected.clone())) + self.infer_expr(*expr, &Expectation::has_type(expected.clone()), ExprIsRead::Yes) } Pat::Missing => self.err_ty(), }; @@ -497,7 +497,7 @@ impl InferenceContext<'_> { } } - self.infer_expr(expr, &Expectation::has_type(expected.clone())) + self.infer_expr(expr, &Expectation::has_type(expected.clone()), ExprIsRead::Yes) } fn is_non_ref_pat(&mut self, body: &hir_def::body::Body, pat: PatId) -> bool { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs index 5c63cd00f97..1ca4c9b2ad5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs @@ -539,3 +539,249 @@ fn test() { "#, ); } + +#[test] +fn diverging_place_match1() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn not_a_read() -> ! { + unsafe { + let x: *const ! = 0 as _; + let _: ! = *x; + } +} +"#, + expect![[r#" + 21..100 '{ ... } }': ! + 27..98 'unsafe... }': ! + 48..49 'x': *const ! + 62..63 '0': i32 + 62..68 '0 as _': *const ! + 82..83 '_': ! + 89..91 '*x': ! + 90..91 'x': *const ! + 27..98: expected !, got () + "#]], + ) +} + +#[test] +fn diverging_place_match2() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn not_a_read_implicit() -> ! { + unsafe { + let x: *const ! = 0 as _; + let _ = *x; + } +} +"#, + expect![[r#" + 30..106 '{ ... } }': ! + 36..104 'unsafe... }': ! + 57..58 'x': *const ! + 71..72 '0': i32 + 71..77 '0 as _': *const ! + 91..92 '_': ! + 95..97 '*x': ! + 96..97 'x': *const ! + 36..104: expected !, got () + "#]], + ) +} + +#[test] +fn diverging_place_match3() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn not_a_read_guide_coercion() -> ! { + unsafe { + let x: *const ! = 0 as _; + let _: () = *x; + } +} +"#, + expect![[r#" + 36..116 '{ ... } }': ! + 42..114 'unsafe... }': ! + 63..64 'x': *const ! + 77..78 '0': i32 + 77..83 '0 as _': *const ! + 97..98 '_': () + 105..107 '*x': ! + 106..107 'x': *const ! + 42..114: expected !, got () + 105..107: expected (), got ! + "#]], + ) +} + +#[test] +fn diverging_place_match4() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn empty_match() -> ! { + unsafe { + let x: *const ! = 0 as _; + match *x { _ => {} }; + } +} +"#, + expect![[r#" + 22..108 '{ ... } }': ! + 28..106 'unsafe... }': ! + 49..50 'x': *const ! + 63..64 '0': i32 + 63..69 '0 as _': *const ! + 79..99 'match ...> {} }': () + 85..87 '*x': ! + 86..87 'x': *const ! + 90..91 '_': ! + 95..97 '{}': () + 28..106: expected !, got () + "#]], + ) +} + +#[test] +fn diverging_place_match5() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn field_projection() -> ! { + unsafe { + let x: *const (!, ()) = 0 as _; + let _ = (*x).0; + } +} +"#, + expect![[r#" + 27..113 '{ ... } }': ! + 33..111 'unsafe... }': ! + 54..55 'x': *const (!, ()) + 74..75 '0': i32 + 74..80 '0 as _': *const (!, ()) + 94..95 '_': ! + 98..104 '(*x).0': ! + 99..101 '*x': (!, ()) + 100..101 'x': *const (!, ()) + 33..111: expected !, got () + "#]], + ) +} + +#[test] +fn diverging_place_match6() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn covered_arm() -> ! { + unsafe { + let x: *const ! = 0 as _; + let (_ | 1i32) = *x; + } +} +"#, + expect![[r#" + 22..107 '{ ... } }': ! + 28..105 'unsafe... }': ! + 49..50 'x': *const ! + 63..64 '0': i32 + 63..69 '0 as _': *const ! + 84..85 '_': ! + 84..92 '_ | 1i32': ! + 88..92 '1i32': i32 + 88..92 '1i32': i32 + 96..98 '*x': ! + 97..98 'x': *const ! + 28..105: expected !, got () + 88..92: expected !, got i32 + "#]], + ) +} + +#[test] +fn diverging_place_match7() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn uncovered_arm() -> ! { + unsafe { + let x: *const ! = 0 as _; + let (1i32 | _) = *x; + } +} +"#, + expect![[r#" + 24..109 '{ ... } }': ! + 30..107 'unsafe... }': ! + 51..52 'x': *const ! + 65..66 '0': i32 + 65..71 '0 as _': *const ! + 86..90 '1i32': i32 + 86..90 '1i32': i32 + 86..94 '1i32 | _': ! + 93..94 '_': ! + 98..100 '*x': ! + 99..100 'x': *const ! + 30..107: expected !, got () + 86..90: expected !, got i32 + "#]], + ) +} + +#[test] +fn diverging_place_match8() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn coerce_ref_binding() -> ! { + unsafe { + let x: *const ! = 0 as _; + let ref _x: () = *x; + } +} +"#, + expect![[r#" + 29..114 '{ ... } }': ! + 35..112 'unsafe... }': ! + 56..57 'x': *const ! + 70..71 '0': i32 + 70..76 '0 as _': *const ! + 90..96 'ref _x': &'? () + 103..105 '*x': ! + 104..105 'x': *const ! + 103..105: expected (), got ! + "#]], + ) +} + +#[test] +fn never_place_isnt_diverging() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn make_up_a_pointer() -> *const T { + unsafe { + let x: *const ! = 0 as _; + &raw const *x + } +} +"#, + expect![[r#" + 38..116 '{ ... } }': *const T + 44..114 'unsafe... }': *const T + 65..66 'x': *const ! + 79..80 '0': i32 + 79..85 '0 as _': *const ! + 95..108 '&raw const *x': *const ! + 106..108 '*x': ! + 107..108 'x': *const ! + 95..108: expected *const T, got *const ! + "#]], + ) +} -- cgit 1.4.1-3-g733a5 From 004e97485a9c425ab1896ac0b632c9fae6f85992 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Tue, 15 Oct 2024 00:51:39 +0900 Subject: minor: `ra-salsa` in `profile.dev.package` --- src/tools/rust-analyzer/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index ee310c06c08..d27d1ddde20 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -20,7 +20,7 @@ rustc-hash.opt-level = 3 smol_str.opt-level = 3 text-size.opt-level = 3 serde.opt-level = 3 -ra-salsa.opt-level = 3 +salsa.opt-level = 3 # This speeds up `cargo xtask dist`. miniz_oxide.opt-level = 3 -- cgit 1.4.1-3-g733a5 From d381df43c4035e2339f479d2117f0b22c46240a4 Mon Sep 17 00:00:00 2001 From: roife Date: Tue, 15 Oct 2024 14:22:20 +0800 Subject: fix: autofix for missing wrapped unit in return expr --- .../ide-diagnostics/src/handlers/type_mismatch.rs | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 5cce7c4aed5..90f88d6705b 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -130,6 +130,7 @@ fn add_missing_ok_or_some( if d.actual.is_unit() { if let Expr::BlockExpr(block) = &expr { if block.tail_expr().is_none() { + // Fix for forms like `fn foo() -> Result<(), String> {}` let mut builder = TextEdit::builder(); let block_indent = block.indent_level(); @@ -156,6 +157,20 @@ fn add_missing_ok_or_some( acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range)); } return Some(()); + } else if let Expr::ReturnExpr(ret_expr) = &expr { + // Fix for forms like `fn foo() -> Result<(), String> { return; }` + if ret_expr.expr().is_none() { + let mut builder = TextEdit::builder(); + builder + .insert(ret_expr.syntax().text_range().end(), format!(" {variant_name}(())")); + let source_change = SourceChange::from_text_edit( + expr_ptr.file_id.original_file(ctx.sema.db), + builder.finish(), + ); + let name = format!("Insert {variant_name}(()) as the return value"); + acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range)); + } + return Some(()); } } @@ -603,6 +618,29 @@ fn foo() -> Result<(), ()> { ); } + #[test] + fn test_wrapped_unit_as_return_expr() { + check_fix( + r#" +//- minicore: result +fn foo(b: bool) -> Result<(), String> { + if b { + return$0; + } + + Err("oh dear".to_owned()) +}"#, + r#" +fn foo(b: bool) -> Result<(), String> { + if b { + return Ok(()); + } + + Err("oh dear".to_owned()) +}"#, + ); + } + #[test] fn test_in_const_and_static() { check_fix( -- cgit 1.4.1-3-g733a5 From f5577a8174685aca342b9189e625648f25a23a20 Mon Sep 17 00:00:00 2001 From: Giang Dao Date: Mon, 7 Oct 2024 19:07:57 +0800 Subject: fix missing rustfmt and clippy for msi --- src/bootstrap/src/core/build_steps/dist.rs | 31 +++++++++++++++++++++- src/etc/installer/msi/rust.wxs | 41 +++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 90e6a10d9d6..cf8ed11c6b0 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -1633,6 +1633,8 @@ impl Step for Extended { "rust-analyzer-preview".to_string() } else if name == "clippy" { "clippy-preview".to_string() + } else if name == "rustfmt" { + "rustfmt-preview".to_string() } else if name == "miri" { "miri-preview".to_string() } else if name == "rustc-codegen-cranelift" { @@ -1652,7 +1654,7 @@ impl Step for Extended { prepare("cargo"); prepare("rust-analysis"); prepare("rust-std"); - for tool in &["clippy", "rust-analyzer", "rust-docs", "miri"] { + for tool in &["clippy", "rustfmt", "rust-analyzer", "rust-docs", "miri"] { if built_tools.contains(tool) { prepare(tool); } @@ -1770,6 +1772,24 @@ impl Step for Extended { .arg(etc.join("msi/remove-duplicates.xsl")) .run(builder); } + if built_tools.contains("rustfmt") { + command(&heat) + .current_dir(&exe) + .arg("dir") + .arg("rustfmt") + .args(heat_flags) + .arg("-cg") + .arg("RustFmtGroup") + .arg("-dr") + .arg("RustFmt") + .arg("-var") + .arg("var.RustFmtDir") + .arg("-out") + .arg(exe.join("RustFmtGroup.wxs")) + .arg("-t") + .arg(etc.join("msi/remove-duplicates.xsl")) + .run(builder); + } if built_tools.contains("miri") { command(&heat) .current_dir(&exe) @@ -1841,6 +1861,9 @@ impl Step for Extended { if built_tools.contains("clippy") { cmd.arg("-dClippyDir=clippy"); } + if built_tools.contains("rustfmt") { + cmd.arg("-dRustFmtDir=rustfmt"); + } if built_tools.contains("rust-docs") { cmd.arg("-dDocsDir=rust-docs"); } @@ -1867,6 +1890,9 @@ impl Step for Extended { if built_tools.contains("clippy") { candle("ClippyGroup.wxs".as_ref()); } + if built_tools.contains("rustfmt") { + candle("RustFmtGroup.wxs".as_ref()); + } if built_tools.contains("miri") { candle("MiriGroup.wxs".as_ref()); } @@ -1905,6 +1931,9 @@ impl Step for Extended { if built_tools.contains("clippy") { cmd.arg("ClippyGroup.wixobj"); } + if built_tools.contains("rustfmt") { + cmd.arg("RustFmtGroup.wixobj"); + } if built_tools.contains("miri") { cmd.arg("MiriGroup.wixobj"); } diff --git a/src/etc/installer/msi/rust.wxs b/src/etc/installer/msi/rust.wxs index f29e1e4d27a..2d155bf0b10 100644 --- a/src/etc/installer/msi/rust.wxs +++ b/src/etc/installer/msi/rust.wxs @@ -172,6 +172,11 @@ + + + + + @@ -279,7 +284,41 @@ - + + + + + + + + + + + + + + + -- cgit 1.4.1-3-g733a5 From 2316749ca954030afed6145342808a8c1ae29fac Mon Sep 17 00:00:00 2001 From: Giang Dao Date: Mon, 7 Oct 2024 19:10:18 +0800 Subject: fix missing rustfmt for apple darwin --- src/bootstrap/src/core/build_steps/dist.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index cf8ed11c6b0..80ba9f44448 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -1591,9 +1591,15 @@ impl Step for Extended { prepare("cargo"); prepare("rust-std"); prepare("rust-analysis"); - prepare("clippy"); - prepare("rust-analyzer"); - for tool in &["rust-docs", "miri", "rustc-codegen-cranelift"] { + + for tool in &[ + "clippy", + "rustfmt", + "rust-analyzer", + "rust-docs", + "miri", + "rustc-codegen-cranelift", + ] { if built_tools.contains(tool) { prepare(tool); } -- cgit 1.4.1-3-g733a5 From 2e6f3bd1d32455e535de1d9ee154253c333aec73 Mon Sep 17 00:00:00 2001 From: León Orell Valerian Liehr Date: Sat, 12 Oct 2024 12:47:08 +0200 Subject: rustdoc-JSON: Rename "object safe" to "dyn compatible" --- src/librustdoc/json/conversions.rs | 4 ++-- src/rustdoc-json-types/lib.rs | 9 ++++++--- tests/rustdoc-json/traits/is_dyn_compatible.rs | 19 +++++++++++++++++++ tests/rustdoc-json/traits/is_object_safe.rs | 19 ------------------- 4 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 tests/rustdoc-json/traits/is_dyn_compatible.rs delete mode 100644 tests/rustdoc-json/traits/is_object_safe.rs (limited to 'src') diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 77e7d83090b..0130f2ce517 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -672,12 +672,12 @@ impl FromClean for Trait { let tcx = renderer.tcx; let is_auto = trait_.is_auto(tcx); let is_unsafe = trait_.safety(tcx) == rustc_hir::Safety::Unsafe; - let is_object_safe = trait_.is_dyn_compatible(tcx); + let is_dyn_compatible = trait_.is_dyn_compatible(tcx); let clean::Trait { items, generics, bounds, .. } = trait_; Trait { is_auto, is_unsafe, - is_object_safe, + is_dyn_compatible, items: renderer.ids(items), generics: generics.into_json(renderer), bounds: bounds.into_json(renderer), diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index fc64bc98bb9..b0bedab495f 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; /// This integer is incremented with every breaking change to the API, /// and is returned along with the JSON blob as [`Crate::format_version`]. /// Consuming code should assert that this value matches the format version(s) that it supports. -pub const FORMAT_VERSION: u32 = 35; +pub const FORMAT_VERSION: u32 = 36; /// The root of the emitted JSON blob. /// @@ -1082,8 +1082,11 @@ pub struct Trait { pub is_auto: bool, /// Whether the trait is marked as `unsafe`. pub is_unsafe: bool, - /// Whether the trait is [object safe](https://doc.rust-lang.org/reference/items/traits.html#object-safety). - pub is_object_safe: bool, + // FIXME(dyn_compat_renaming): Update the URL once the Reference is updated and hits stable. + /// Whether the trait is [dyn compatible](https://doc.rust-lang.org/reference/items/traits.html#object-safety)[^1]. + /// + /// [^1]: Formerly known as "object safe". + pub is_dyn_compatible: bool, /// Associated [`Item`]s that can/must be implemented by the `impl` blocks. pub items: Vec, /// Information about the type parameters and `where` clauses of the trait. diff --git a/tests/rustdoc-json/traits/is_dyn_compatible.rs b/tests/rustdoc-json/traits/is_dyn_compatible.rs new file mode 100644 index 00000000000..bccf94d17d6 --- /dev/null +++ b/tests/rustdoc-json/traits/is_dyn_compatible.rs @@ -0,0 +1,19 @@ +#![no_std] + +//@ has "$.index[*][?(@.name=='FooDynIncompatible')]" +//@ is "$.index[*][?(@.name=='FooDynIncompatible')].inner.trait.is_dyn_compatible" false +pub trait FooDynIncompatible { + fn foo() -> Self; +} + +//@ has "$.index[*][?(@.name=='BarDynIncompatible')]" +//@ is "$.index[*][?(@.name=='BarDynIncompatible')].inner.trait.is_dyn_compatible" false +pub trait BarDynIncompatible { + fn foo(i: T); +} + +//@ has "$.index[*][?(@.name=='FooDynCompatible')]" +//@ is "$.index[*][?(@.name=='FooDynCompatible')].inner.trait.is_dyn_compatible" true +pub trait FooDynCompatible { + fn foo(&self); +} diff --git a/tests/rustdoc-json/traits/is_object_safe.rs b/tests/rustdoc-json/traits/is_object_safe.rs deleted file mode 100644 index 35c4e4eb847..00000000000 --- a/tests/rustdoc-json/traits/is_object_safe.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_std] - -//@ has "$.index[*][?(@.name=='FooUnsafe')]" -//@ is "$.index[*][?(@.name=='FooUnsafe')].inner.trait.is_object_safe" false -pub trait FooUnsafe { - fn foo() -> Self; -} - -//@ has "$.index[*][?(@.name=='BarUnsafe')]" -//@ is "$.index[*][?(@.name=='BarUnsafe')].inner.trait.is_object_safe" false -pub trait BarUnsafe { - fn foo(i: T); -} - -//@ has "$.index[*][?(@.name=='FooSafe')]" -//@ is "$.index[*][?(@.name=='FooSafe')].inner.trait.is_object_safe" true -pub trait FooSafe { - fn foo(&self); -} -- cgit 1.4.1-3-g733a5 From 929d5bcf77509ad36d3c45dc302297f06dad04ba Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Fri, 30 Aug 2024 14:26:09 -0700 Subject: internal: Use local time when formatting logs When debugging rust-analyzer and looking at logs, it's much easier to read when the timestamp is in the local timezone. Before: 2024-08-28T20:55:38.792321Z INFO ParseQuery: invoked at R18460 After: 2024-08-28T13:55:38.792321-07:00 INFO ParseQuery: invoked at R18460 --- src/tools/rust-analyzer/Cargo.lock | 24 ++++++++++++++++++++++ src/tools/rust-analyzer/Cargo.toml | 3 +++ .../crates/rust-analyzer/src/tracing/config.rs | 6 +++++- 3 files changed, 32 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 7891edc2447..f068be9e9d4 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1187,6 +1187,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.33.0" @@ -2093,10 +2102,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -2105,6 +2118,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2213,6 +2236,7 @@ checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "sharded-slab", "thread_local", + "time", "tracing-core", "tracing-log", ] diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index d27d1ddde20..d97d4d4d368 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -153,6 +153,9 @@ tracing-tree = "0.3.0" tracing-subscriber = { version = "0.3.18", default-features = false, features = [ "registry", "fmt", + "local-time", + "std", + "time", "tracing-log", ] } triomphe = { version = "0.1.10", default-features = false, features = ["std"] } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs index b73f6e77514..5ab2dc2b67a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs @@ -7,7 +7,7 @@ use anyhow::Context; use tracing::level_filters::LevelFilter; use tracing_subscriber::{ filter::{filter_fn, Targets}, - fmt::MakeWriter, + fmt::{time, MakeWriter}, layer::SubscriberExt, Layer, Registry, }; @@ -58,6 +58,10 @@ where let writer = self.writer; let ra_fmt_layer = tracing_subscriber::fmt::layer() + .with_timer( + time::OffsetTime::local_rfc_3339() + .expect("Could not get local offset, make sure you're on the main thread"), + ) .with_target(false) .with_ansi(false) .with_writer(writer) -- cgit 1.4.1-3-g733a5 From 08a5dc491c7f23b2636a3559daf3bb4e9417ffa1 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 17 Oct 2024 13:19:48 +0800 Subject: Bump version of scip crate --- src/tools/rust-analyzer/Cargo.lock | 12 ++++++------ src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index f068be9e9d4..d09315d9061 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1448,9 +1448,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "3.2.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +checksum = "a3a7c64d9bf75b1b8d981124c14c179074e8caa7dfe7b6a12e6222ddcd0c8f72" dependencies = [ "once_cell", "protobuf-support", @@ -1459,9 +1459,9 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.2.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +checksum = "b088fd20b938a875ea00843b6faf48579462630015c3788d397ad6a786663252" dependencies = [ "thiserror", ] @@ -1774,9 +1774,9 @@ dependencies = [ [[package]] name = "scip" -version = "0.3.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5dc1bd66649133af84ab62436ddd2856c2605182b02dec2cd197f684dfe15ef" +checksum = "8dfafd2fa14c6237fa1fc4310f739d02fa915d92977fa069426591f1de046f81" dependencies = [ "protobuf", ] diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml index eb95f42d755..2dd2f2242a0 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml @@ -25,7 +25,7 @@ crossbeam-channel.workspace = true dirs = "5.0.1" dissimilar.workspace = true itertools.workspace = true -scip = "0.3.3" +scip = "0.5.1" lsp-types = { version = "=0.95.0", features = ["proposed"] } parking_lot = "0.12.1" xflags = "0.3.0" -- cgit 1.4.1-3-g733a5 From aff69ccfb2b7da9374dd47f13e626a980ce8338a Mon Sep 17 00:00:00 2001 From: Laurențiu Nicola Date: Thu, 17 Oct 2024 10:04:47 +0300 Subject: Preparing for merge from rust-lang/rust --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index f217c6a19cb..d0f9fa7ac42 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -cf24c73141a77db730f4b7fda69dcd7e8b113b51 +dd5127615ad626741a1116d022cf784637ac05df -- cgit 1.4.1-3-g733a5 From 6e4f8fea36cd04f623c46d99adc3c370b1879883 Mon Sep 17 00:00:00 2001 From: Laurențiu Nicola Date: Thu, 17 Oct 2024 10:10:01 +0300 Subject: Bump rustc crates --- src/tools/rust-analyzer/Cargo.lock | 24 +++++++++++----------- src/tools/rust-analyzer/Cargo.toml | 10 ++++----- .../rust-analyzer/crates/parser/src/lexed_str.rs | 5 +++-- 3 files changed, 20 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index d09315d9061..4a6da47a47d 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1497,9 +1497,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_abi" -version = "0.68.0" +version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8cb51bb4534ac3e9c74f1d9bd90e607e60f94f734b1cf1a66f753ad2af6ed7" +checksum = "c6999d098000b98415939f13158dac78cb3eeeb7b0c073847f3e4b623866e27c" dependencies = [ "bitflags 2.6.0", "ra-ap-rustc_index", @@ -1508,9 +1508,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_index" -version = "0.68.0" +version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b640fba2b7ef4f875459e2e76daeb846ef341d1d376fa758962ac0eba79bce6" +checksum = "ae9fb312d942817dab10790881f555928c1f6a11a85186e8e573ad4a86c7d3be" dependencies = [ "arrayvec", "ra-ap-rustc_index_macros", @@ -1519,9 +1519,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_index_macros" -version = "0.68.0" +version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faef502419ba5ac9d3079b1a835c6e5b4e605388254bbe55eb5683936f541be9" +checksum = "766e3990eb1066a06deefc561b5a01b32ca5c9211feea31cbf4ed50611519872" dependencies = [ "proc-macro2", "quote", @@ -1530,9 +1530,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_lexer" -version = "0.68.0" +version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da7f9d533b8d5be6704558da741ff20b982ad4647b1e9e08632853e4fecf9d5" +checksum = "f4afa98eb7889c137d5a3f1cd189089e16da04d1e4837d358a67aa3dab10ffbe" dependencies = [ "unicode-properties", "unicode-xid", @@ -1540,9 +1540,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_parse_format" -version = "0.68.0" +version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94389cf81c651b1bda9ac45d3de6a2d851bb6fd4cb893875daa44e419c94205f" +checksum = "d9234c96ffb0565286790407fb7eb7f55ebf69267de4db382fdec0a17f14b0e2" dependencies = [ "ra-ap-rustc_index", "ra-ap-rustc_lexer", @@ -1550,9 +1550,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_pattern_analysis" -version = "0.68.0" +version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3679d8dd0114ed6000918309f843782738e51c99d8e4baec0d0f706e4d948819" +checksum = "273d5f72926a58c7eea27aebc898d1d5b32d23d2342f692a94a2cf8746aa4a2f" dependencies = [ "ra-ap-rustc_index", "rustc-hash", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index d97d4d4d368..8c099f324b4 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -85,11 +85,11 @@ tt = { path = "./crates/tt", version = "0.0.0" } vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" } -ra-ap-rustc_lexer = { version = "0.68.0", default-features = false } -ra-ap-rustc_parse_format = { version = "0.68.0", default-features = false } -ra-ap-rustc_index = { version = "0.68.0", default-features = false } -ra-ap-rustc_abi = { version = "0.68.0", default-features = false } -ra-ap-rustc_pattern_analysis = { version = "0.68.0", default-features = false } +ra-ap-rustc_lexer = { version = "0.71.0", default-features = false } +ra-ap-rustc_parse_format = { version = "0.71.0", default-features = false } +ra-ap-rustc_index = { version = "0.71.0", default-features = false } +ra-ap-rustc_abi = { version = "0.71.0", default-features = false } +ra-ap-rustc_pattern_analysis = { version = "0.71.0", default-features = false } # local crates that aren't published to crates.io. These should not have versions. test-fixture = { path = "./crates/test-fixture" } diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index 7ea23b4f752..5322463a713 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -188,10 +188,11 @@ impl<'a> Converter<'a> { rustc_lexer::TokenKind::RawIdent => IDENT, - rustc_lexer::TokenKind::GuardedStrPrefix => { + rustc_lexer::TokenKind::GuardedStrPrefix if self.edition.at_least_2024() => { err = "Invalid string literal (reserved syntax)"; ERROR - }, + } + rustc_lexer::TokenKind::GuardedStrPrefix => POUND, rustc_lexer::TokenKind::Literal { kind, .. } => { self.extend_literal(token_text.len(), kind); -- cgit 1.4.1-3-g733a5 From c4bce0b8b13f2ae39bfd01e55bf98a556b2d703f Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 10 Jul 2024 13:00:36 -0400 Subject: Add more tests --- src/tools/miri/tests/pass/dyn-upcast.rs | 51 ++++++++++++++++++++++++++ src/tools/miri/tests/pass/dyn-upcast.stdout | 4 ++ tests/ui/traits/dyn-drop-principal.rs | 8 +++- tests/ui/traits/dyn-star-drop-principal.rs | 12 ++++++ tests/ui/traits/dyn-star-drop-principal.stderr | 11 ++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/tools/miri/tests/pass/dyn-upcast.stdout create mode 100644 tests/ui/traits/dyn-star-drop-principal.rs create mode 100644 tests/ui/traits/dyn-star-drop-principal.stderr (limited to 'src') diff --git a/src/tools/miri/tests/pass/dyn-upcast.rs b/src/tools/miri/tests/pass/dyn-upcast.rs index ff995f38196..306e9ab9c67 100644 --- a/src/tools/miri/tests/pass/dyn-upcast.rs +++ b/src/tools/miri/tests/pass/dyn-upcast.rs @@ -9,6 +9,7 @@ fn main() { struct_(); replace_vptr(); vtable_nop_cast(); + drop_principal(); } fn vtable_nop_cast() { @@ -430,3 +431,53 @@ fn replace_vptr() { let s = S(42); invoke_outer(&s); } + +fn drop_principal() { + use std::{alloc::Layout, any::Any}; + + const fn yeet_principal(x: Box) -> Box { + x + } + + trait Bar: Send + Sync {} + + impl Bar for T {} + + const fn yeet_principal_2(x: Box) -> Box { + x + } + + struct CallMe(Option); + + impl CallMe { + fn new(f: F) -> Self { + CallMe(Some(f)) + } + } + + impl Drop for CallMe { + fn drop(&mut self) { + (self.0.take().unwrap())(); + } + } + + fn goodbye() { + println!("goodbye"); + } + + let x = Box::new(CallMe::new(goodbye)) as Box; + let x_layout = Layout::for_value(&*x); + let y = yeet_principal(x); + let y_layout = Layout::for_value(&*y); + assert_eq!(x_layout, y_layout); + println!("before"); + drop(y); + + let x = Box::new(CallMe::new(goodbye)) as Box; + let x_layout = Layout::for_value(&*x); + let y = yeet_principal_2(x); + let y_layout = Layout::for_value(&*y); + assert_eq!(x_layout, y_layout); + println!("before"); + drop(y); +} diff --git a/src/tools/miri/tests/pass/dyn-upcast.stdout b/src/tools/miri/tests/pass/dyn-upcast.stdout new file mode 100644 index 00000000000..edd99a114a1 --- /dev/null +++ b/src/tools/miri/tests/pass/dyn-upcast.stdout @@ -0,0 +1,4 @@ +before +goodbye +before +goodbye diff --git a/tests/ui/traits/dyn-drop-principal.rs b/tests/ui/traits/dyn-drop-principal.rs index eeff0550fcb..c233127e43d 100644 --- a/tests/ui/traits/dyn-drop-principal.rs +++ b/tests/ui/traits/dyn-drop-principal.rs @@ -1,7 +1,7 @@ //@ run-pass //@ check-run-results -use std::any::Any; +use std::{alloc::Layout, any::Any}; const fn yeet_principal(x: Box) -> Box { x @@ -35,12 +35,18 @@ fn goodbye() { fn main() { let x = Box::new(CallMe::new(goodbye)) as Box; + let x_layout = Layout::for_value(&*x); let y = yeet_principal(x); + let y_layout = Layout::for_value(&*y); + assert_eq!(x_layout, y_layout); println!("before"); drop(y); let x = Box::new(CallMe::new(goodbye)) as Box; + let x_layout = Layout::for_value(&*x); let y = yeet_principal_2(x); + let y_layout = Layout::for_value(&*y); + assert_eq!(x_layout, y_layout); println!("before"); drop(y); } diff --git a/tests/ui/traits/dyn-star-drop-principal.rs b/tests/ui/traits/dyn-star-drop-principal.rs new file mode 100644 index 00000000000..1ad99070339 --- /dev/null +++ b/tests/ui/traits/dyn-star-drop-principal.rs @@ -0,0 +1,12 @@ +#![feature(dyn_star)] +#![allow(incomplete_features)] + +trait Trait {} +impl Trait for usize {} + +fn main() { + // We allow &dyn Trait + Send -> &dyn Send (i.e. dropping principal), + // but we don't (currently?) allow the same for dyn* + let x: dyn* Trait + Send = 1usize; + x as dyn* Send; //~ error: `dyn* Trait + Send` needs to have the same ABI as a pointer +} diff --git a/tests/ui/traits/dyn-star-drop-principal.stderr b/tests/ui/traits/dyn-star-drop-principal.stderr new file mode 100644 index 00000000000..721ae7e191e --- /dev/null +++ b/tests/ui/traits/dyn-star-drop-principal.stderr @@ -0,0 +1,11 @@ +error[E0277]: `dyn* Trait + Send` needs to have the same ABI as a pointer + --> $DIR/dyn-star-drop-principal.rs:11:5 + | +LL | x as dyn* Send; + | ^ `dyn* Trait + Send` needs to be a pointer-like type + | + = help: the trait `PointerLike` is not implemented for `dyn* Trait + Send` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. -- cgit 1.4.1-3-g733a5 From 21a020881da6e7bbf3851971e1ec39f844616436 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 18 Oct 2024 11:32:49 +1100 Subject: Consolidate test collection state in one place --- src/tools/compiletest/src/lib.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 2e66c084dd7..7abdfadbfa8 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -464,9 +464,7 @@ pub fn run_tests(config: Arc) { // structure for each test (or each revision of a multi-revision test). let mut tests = Vec::new(); for c in configs { - let mut found_paths = HashSet::new(); - make_tests(c, &mut tests, &mut found_paths); - check_overlapping_tests(&found_paths); + tests.extend(collect_and_make_tests(c)); } tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice())); @@ -550,27 +548,26 @@ pub fn test_opts(config: &Config) -> test::TestOpts { /// This always inspects _all_ test files in the suite (e.g. all 17k+ ui tests), /// regardless of whether any filters/tests were specified on the command-line, /// because filtering is handled later by libtest. -pub fn make_tests( - config: Arc, - tests: &mut Vec, - found_paths: &mut HashSet, -) { +pub fn collect_and_make_tests(config: Arc) -> Vec { debug!("making tests from {:?}", config.src_base.display()); let inputs = common_inputs_stamp(&config); let modified_tests = modified_tests(&config, &config.src_base).unwrap_or_else(|err| { panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err) }); - let cache = HeadersCache::load(&config); + + let mut tests = vec![]; + let mut found_paths = HashSet::new(); let mut poisoned = false; + collect_tests_from_dir( config.clone(), &cache, &config.src_base, &PathBuf::new(), &inputs, - tests, - found_paths, + &mut tests, + &mut found_paths, &modified_tests, &mut poisoned, ) @@ -582,6 +579,10 @@ pub fn make_tests( eprintln!(); panic!("there are errors in tests"); } + + check_overlapping_tests(&found_paths); + + tests } /// Returns a stamp constructed from input files common to all test cases. -- cgit 1.4.1-3-g733a5 From 932e9f01b1f60370b3ffcb370cdbf3b62ac2e718 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 18 Oct 2024 11:32:49 +1100 Subject: Store test collection context/state in two structs --- src/tools/compiletest/src/lib.rs | 159 ++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 86 deletions(-) (limited to 'src') diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 7abdfadbfa8..acf4860cc63 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -543,6 +543,21 @@ pub fn test_opts(config: &Config) -> test::TestOpts { } } +/// Read-only context data used during test collection. +struct TestCollectorCx { + config: Arc, + cache: HeadersCache, + inputs: Stamp, + modified_tests: Vec, +} + +/// Mutable state used during test collection. +struct TestCollector { + tests: Vec, + found_paths: HashSet, + poisoned: bool, +} + /// Creates libtest structures for every test/revision in the test suite directory. /// /// This always inspects _all_ test files in the suite (e.g. all 17k+ ui tests), @@ -556,24 +571,16 @@ pub fn collect_and_make_tests(config: Arc) -> Vec { }); let cache = HeadersCache::load(&config); - let mut tests = vec![]; - let mut found_paths = HashSet::new(); - let mut poisoned = false; - - collect_tests_from_dir( - config.clone(), - &cache, - &config.src_base, - &PathBuf::new(), - &inputs, - &mut tests, - &mut found_paths, - &modified_tests, - &mut poisoned, - ) - .unwrap_or_else(|reason| { - panic!("Could not read tests from {}: {reason}", config.src_base.display()) - }); + let cx = TestCollectorCx { config, cache, inputs, modified_tests }; + let mut collector = + TestCollector { tests: vec![], found_paths: HashSet::new(), poisoned: false }; + + collect_tests_from_dir(&cx, &mut collector, &cx.config.src_base, &PathBuf::new()) + .unwrap_or_else(|reason| { + panic!("Could not read tests from {}: {reason}", cx.config.src_base.display()) + }); + + let TestCollector { tests, found_paths, poisoned } = collector; if poisoned { eprintln!(); @@ -663,15 +670,10 @@ fn modified_tests(config: &Config, dir: &Path) -> Result, String> { /// Recursively scans a directory to find test files and create test structures /// that will be handed over to libtest. fn collect_tests_from_dir( - config: Arc, - cache: &HeadersCache, + cx: &TestCollectorCx, + collector: &mut TestCollector, dir: &Path, relative_dir_path: &Path, - inputs: &Stamp, - tests: &mut Vec, - found_paths: &mut HashSet, - modified_tests: &Vec, - poisoned: &mut bool, ) -> io::Result<()> { // Ignore directories that contain a file named `compiletest-ignore-dir`. if dir.join("compiletest-ignore-dir").exists() { @@ -680,7 +682,7 @@ fn collect_tests_from_dir( // For run-make tests, a "test file" is actually a directory that contains // an `rmake.rs` or `Makefile`" - if config.mode == Mode::RunMake { + if cx.config.mode == Mode::RunMake { if dir.join("Makefile").exists() && dir.join("rmake.rs").exists() { return Err(io::Error::other( "run-make tests cannot have both `Makefile` and `rmake.rs`", @@ -692,7 +694,7 @@ fn collect_tests_from_dir( file: dir.to_path_buf(), relative_dir: relative_dir_path.parent().unwrap().to_path_buf(), }; - tests.extend(make_test(config, cache, &paths, inputs, poisoned)); + make_test(cx, collector, &paths); // This directory is a test, so don't try to find other tests inside it. return Ok(()); } @@ -704,7 +706,7 @@ fn collect_tests_from_dir( // sequential loop because otherwise, if we do it in the // tests themselves, they race for the privilege of // creating the directories and sometimes fail randomly. - let build_dir = output_relative_path(&config, relative_dir_path); + let build_dir = output_relative_path(&cx.config, relative_dir_path); fs::create_dir_all(&build_dir).unwrap(); // Add each `.rs` file as a test, and recurse further on any @@ -716,33 +718,25 @@ fn collect_tests_from_dir( let file_path = file.path(); let file_name = file.file_name(); - if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) { + if is_test(&file_name) + && (!cx.config.only_modified || cx.modified_tests.contains(&file_path)) + { // We found a test file, so create the corresponding libtest structures. debug!("found test file: {:?}", file_path.display()); // Record the stem of the test file, to check for overlaps later. let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap()); - found_paths.insert(rel_test_path); + collector.found_paths.insert(rel_test_path); let paths = TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() }; - tests.extend(make_test(config.clone(), cache, &paths, inputs, poisoned)) + make_test(cx, collector, &paths); } else if file_path.is_dir() { // Recurse to find more tests in a subdirectory. let relative_file_path = relative_dir_path.join(file.file_name()); if &file_name != "auxiliary" { debug!("found directory: {:?}", file_path.display()); - collect_tests_from_dir( - config.clone(), - cache, - &file_path, - &relative_file_path, - inputs, - tests, - found_paths, - modified_tests, - poisoned, - )?; + collect_tests_from_dir(cx, collector, &file_path, &relative_file_path)?; } } else { debug!("found other file/directory: {:?}", file_path.display()); @@ -766,17 +760,11 @@ pub fn is_test(file_name: &OsString) -> bool { /// For a single test file, creates one or more test structures (one per revision) /// that can be handed over to libtest to run, possibly in parallel. -fn make_test( - config: Arc, - cache: &HeadersCache, - testpaths: &TestPaths, - inputs: &Stamp, - poisoned: &mut bool, -) -> Vec { +fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) { // For run-make tests, each "test file" is actually a _directory_ containing // an `rmake.rs` or `Makefile`. But for the purposes of directive parsing, // we want to look at that recipe file, not the directory itself. - let test_path = if config.mode == Mode::RunMake { + let test_path = if cx.config.mode == Mode::RunMake { if testpaths.file.join("rmake.rs").exists() && testpaths.file.join("Makefile").exists() { panic!("run-make tests cannot have both `rmake.rs` and `Makefile`"); } @@ -793,7 +781,7 @@ fn make_test( }; // Scan the test file to discover its revisions, if any. - let early_props = EarlyProps::from_file(&config, &test_path); + let early_props = EarlyProps::from_file(&cx.config, &test_path); // Normally we create one libtest structure per revision, with two exceptions: // - If a test doesn't use revisions, create a dummy revision (None) so that @@ -801,44 +789,44 @@ fn make_test( // - Incremental tests inherently can't run their revisions in parallel, so // we treat them like non-revisioned tests here. Incremental revisions are // handled internally by `runtest::run` instead. - let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental { + let revisions = if early_props.revisions.is_empty() || cx.config.mode == Mode::Incremental { vec![None] } else { early_props.revisions.iter().map(|r| Some(r.as_str())).collect() }; - // For each revision (or the sole dummy revision), create and return a + // For each revision (or the sole dummy revision), create and append a // `test::TestDescAndFn` that can be handed over to libtest. - revisions - .into_iter() - .map(|revision| { - // Create a test name and description to hand over to libtest. - let src_file = - std::fs::File::open(&test_path).expect("open test file to parse ignores"); - let test_name = crate::make_test_name(&config, testpaths, revision); - // Create a libtest description for the test/revision. - // This is where `ignore-*`/`only-*`/`needs-*` directives are handled, - // because they need to set the libtest ignored flag. - let mut desc = make_test_description( - &config, cache, test_name, &test_path, src_file, revision, poisoned, - ); + collector.tests.extend(revisions.into_iter().map(|revision| { + // Create a test name and description to hand over to libtest. + let src_file = fs::File::open(&test_path).expect("open test file to parse ignores"); + let test_name = make_test_name(&cx.config, testpaths, revision); + // Create a libtest description for the test/revision. + // This is where `ignore-*`/`only-*`/`needs-*` directives are handled, + // because they need to set the libtest ignored flag. + let mut desc = make_test_description( + &cx.config, + &cx.cache, + test_name, + &test_path, + src_file, + revision, + &mut collector.poisoned, + ); - // If a test's inputs haven't changed since the last time it ran, - // mark it as ignored so that libtest will skip it. - if !config.force_rerun - && is_up_to_date(&config, testpaths, &early_props, revision, inputs) - { - desc.ignore = true; - // Keep this in sync with the "up-to-date" message detected by bootstrap. - desc.ignore_message = Some("up-to-date"); - } + // If a test's inputs haven't changed since the last time it ran, + // mark it as ignored so that libtest will skip it. + if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) { + desc.ignore = true; + // Keep this in sync with the "up-to-date" message detected by bootstrap. + desc.ignore_message = Some("up-to-date"); + } - // Create the callback that will run this test/revision when libtest calls it. - let testfn = make_test_closure(config.clone(), testpaths, revision); + // Create the callback that will run this test/revision when libtest calls it. + let testfn = make_test_closure(Arc::clone(&cx.config), testpaths, revision); - test::TestDescAndFn { desc, testfn } - }) - .collect() + test::TestDescAndFn { desc, testfn } + })); } /// The path of the `stamp` file that gets created or updated whenever a @@ -892,13 +880,12 @@ fn files_related_to_test( /// (This is not very reliable in some circumstances, so the `--force-rerun` /// flag can be used to ignore up-to-date checking and always re-run tests.) fn is_up_to_date( - config: &Config, + cx: &TestCollectorCx, testpaths: &TestPaths, props: &EarlyProps, revision: Option<&str>, - inputs: &Stamp, // Last-modified timestamp of the compiler, compiletest etc ) -> bool { - let stamp_name = stamp(config, testpaths, revision); + let stamp_name = stamp(&cx.config, testpaths, revision); // Check the config hash inside the stamp file. let contents = match fs::read_to_string(&stamp_name) { Ok(f) => f, @@ -906,7 +893,7 @@ fn is_up_to_date( // The test hasn't succeeded yet, so it is not up-to-date. Err(_) => return false, }; - let expected_hash = runtest::compute_stamp_hash(config); + let expected_hash = runtest::compute_stamp_hash(&cx.config); if contents != expected_hash { // Some part of compiletest configuration has changed since the test // last succeeded, so it is not up-to-date. @@ -915,8 +902,8 @@ fn is_up_to_date( // Check the timestamp of the stamp file against the last modified time // of all files known to be relevant to the test. - let mut inputs = inputs.clone(); - for path in files_related_to_test(config, testpaths, props, revision) { + let mut inputs = cx.inputs.clone(); + for path in files_related_to_test(&cx.config, testpaths, props, revision) { inputs.add_path(&path); } -- cgit 1.4.1-3-g733a5 From c4c62a5591bdaa061ef6bab7fc18b29ff02a00c4 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 18 Oct 2024 13:30:31 +1100 Subject: Rename `inputs` to `common_inputs_stamp` The new name makes it clearer that this is a timestamp, and is collected from input files considered common to all tests. --- src/tools/compiletest/src/lib.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index acf4860cc63..5c148e37143 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -547,7 +547,7 @@ pub fn test_opts(config: &Config) -> test::TestOpts { struct TestCollectorCx { config: Arc, cache: HeadersCache, - inputs: Stamp, + common_inputs_stamp: Stamp, modified_tests: Vec, } @@ -565,13 +565,13 @@ struct TestCollector { /// because filtering is handled later by libtest. pub fn collect_and_make_tests(config: Arc) -> Vec { debug!("making tests from {:?}", config.src_base.display()); - let inputs = common_inputs_stamp(&config); + let common_inputs_stamp = common_inputs_stamp(&config); let modified_tests = modified_tests(&config, &config.src_base).unwrap_or_else(|err| { panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err) }); let cache = HeadersCache::load(&config); - let cx = TestCollectorCx { config, cache, inputs, modified_tests }; + let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests }; let mut collector = TestCollector { tests: vec![], found_paths: HashSet::new(), poisoned: false }; @@ -592,7 +592,13 @@ pub fn collect_and_make_tests(config: Arc) -> Vec { tests } -/// Returns a stamp constructed from input files common to all test cases. +/// Returns the most recent last-modified timestamp from among the input files +/// that are considered relevant to all tests (e.g. the compiler, std, and +/// compiletest itself). +/// +/// (Some of these inputs aren't actually relevant to _all_ tests, but they are +/// common to some subset of tests, and are hopefully unlikely to be modified +/// while working on other tests.) fn common_inputs_stamp(config: &Config) -> Stamp { let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root"); @@ -902,14 +908,14 @@ fn is_up_to_date( // Check the timestamp of the stamp file against the last modified time // of all files known to be relevant to the test. - let mut inputs = cx.inputs.clone(); + let mut inputs_stamp = cx.common_inputs_stamp.clone(); for path in files_related_to_test(&cx.config, testpaths, props, revision) { - inputs.add_path(&path); + inputs_stamp.add_path(&path); } // If no relevant files have been modified since the stamp file was last // written, the test is up-to-date. - inputs < Stamp::from_path(&stamp_name) + inputs_stamp < Stamp::from_path(&stamp_name) } /// The maximum of a set of file-modified timestamps. -- cgit 1.4.1-3-g733a5 From 3f8d87beda3f615f1df19504f908fbe9e5824b83 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 18 Oct 2024 13:32:29 +1100 Subject: Rename `found_paths` to `found_path_stems` --- src/tools/compiletest/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 5c148e37143..9962487ef81 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -554,7 +554,7 @@ struct TestCollectorCx { /// Mutable state used during test collection. struct TestCollector { tests: Vec, - found_paths: HashSet, + found_path_stems: HashSet, poisoned: bool, } @@ -573,21 +573,21 @@ pub fn collect_and_make_tests(config: Arc) -> Vec { let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests }; let mut collector = - TestCollector { tests: vec![], found_paths: HashSet::new(), poisoned: false }; + TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }; collect_tests_from_dir(&cx, &mut collector, &cx.config.src_base, &PathBuf::new()) .unwrap_or_else(|reason| { panic!("Could not read tests from {}: {reason}", cx.config.src_base.display()) }); - let TestCollector { tests, found_paths, poisoned } = collector; + let TestCollector { tests, found_path_stems, poisoned } = collector; if poisoned { eprintln!(); panic!("there are errors in tests"); } - check_overlapping_tests(&found_paths); + check_for_overlapping_test_paths(&found_path_stems); tests } @@ -732,7 +732,7 @@ fn collect_tests_from_dir( // Record the stem of the test file, to check for overlaps later. let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap()); - collector.found_paths.insert(rel_test_path); + collector.found_path_stems.insert(rel_test_path); let paths = TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() }; @@ -1023,11 +1023,11 @@ fn make_test_closure( /// To avoid problems, we forbid test names from overlapping in this way. /// /// See for more context. -fn check_overlapping_tests(found_paths: &HashSet) { +fn check_for_overlapping_test_paths(found_path_stems: &HashSet) { let mut collisions = Vec::new(); - for path in found_paths { + for path in found_path_stems { for ancestor in path.ancestors().skip(1) { - if found_paths.contains(ancestor) { + if found_path_stems.contains(ancestor) { collisions.push((path, ancestor)); } } -- cgit 1.4.1-3-g733a5 From 554097678a990255d580078e9d7f6b1d81d78752 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 18 Oct 2024 16:11:15 +1100 Subject: Rename `stamp` to `stamp_file_path` --- src/tools/compiletest/src/lib.rs | 8 ++++---- src/tools/compiletest/src/runtest.rs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 9962487ef81..d045c6fe053 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -837,7 +837,7 @@ fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &Te /// The path of the `stamp` file that gets created or updated whenever a /// particular test completes successfully. -fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf { +fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf { output_base_dir(config, testpaths, revision).join("stamp") } @@ -891,9 +891,9 @@ fn is_up_to_date( props: &EarlyProps, revision: Option<&str>, ) -> bool { - let stamp_name = stamp(&cx.config, testpaths, revision); + let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision); // Check the config hash inside the stamp file. - let contents = match fs::read_to_string(&stamp_name) { + let contents = match fs::read_to_string(&stamp_file_path) { Ok(f) => f, Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"), // The test hasn't succeeded yet, so it is not up-to-date. @@ -915,7 +915,7 @@ fn is_up_to_date( // If no relevant files have been modified since the stamp file was last // written, the test is up-to-date. - inputs_stamp < Stamp::from_path(&stamp_name) + inputs_stamp < Stamp::from_path(&stamp_file_path) } /// The maximum of a set of file-modified timestamps. diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 69a47fcd0fb..f0452008304 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -29,7 +29,7 @@ use crate::errors::{self, Error, ErrorKind}; use crate::header::TestProps; use crate::read2::{Truncated, read2_abbreviated}; use crate::util::{PathBufExt, add_dylib_path, logv, static_regex}; -use crate::{ColorConfig, json}; +use crate::{ColorConfig, json, stamp_file_path}; mod debugger; @@ -2595,8 +2595,8 @@ impl<'test> TestCx<'test> { } fn create_stamp(&self) { - let stamp = crate::stamp(&self.config, self.testpaths, self.revision); - fs::write(&stamp, compute_stamp_hash(&self.config)).unwrap(); + let stamp_file_path = stamp_file_path(&self.config, self.testpaths, self.revision); + fs::write(&stamp_file_path, compute_stamp_hash(&self.config)).unwrap(); } fn init_incremental_test(&self) { -- cgit 1.4.1-3-g733a5 From 4277160f610b7a6e754bd7c51e8ec26f02d6dd6a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 18 Oct 2024 10:30:22 +0200 Subject: checktools.sh: add link to issue for more context about disabled Miri tests --- src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh | 2 ++ src/ci/github-actions/jobs.yml | 2 ++ 2 files changed, 4 insertions(+) (limited to 'src') diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh b/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh index 303a2f26c0f..8324d1ec586 100755 --- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh +++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh @@ -59,6 +59,8 @@ case $HOST_TARGET in # "error: cannot produce cdylib for ... as the target ... does not support these crate types". # Only run "pass" tests, which is quite a bit faster. #FIXME: Re-enable this once CI issues are fixed + # See + # For now, these tests are moved to `x86_64-msvc-ext2` in `src/ci/github-actions/jobs.yml`. #python3 "$X_PY" test --stage 2 src/tools/miri --target aarch64-apple-darwin --test-args pass #python3 "$X_PY" test --stage 2 src/tools/miri --target i686-pc-windows-gnu --test-args pass ;; diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 8f49f623afa..21a073c937f 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -381,6 +381,8 @@ auto: <<: *job-windows-8c # Temporary builder to workaround CI issues + # See + #FIXME: Remove this, and re-enable the same tests in `checktools.sh`, once CI issues are fixed. - image: x86_64-msvc-ext2 env: SCRIPT: > -- cgit 1.4.1-3-g733a5 From cf918d56012663bcaace87b9876943fd5b683dd7 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 18 Oct 2024 14:54:06 +0200 Subject: Hotfix TRAIT_METHODS static->const --- src/tools/clippy/clippy_lints/src/methods/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index 722290fb68e..2a391870d70 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -5182,7 +5182,8 @@ impl ShouldImplTraitCase { } #[rustfmt::skip] -static TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ +#[expect(clippy::large_const_arrays, reason = "`Span` is not sync, so this can't be static")] +const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), -- cgit 1.4.1-3-g733a5 From e78d78868a4c9a4e29a02f70ede75ca8cfd39bef Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:34:04 +0200 Subject: Allow `#[deny(..)]` inside `#[forbid(..)]` as a no-op with a warning Forbid cannot be overriden. When someome tries to do this anyways, it results in a hard error. That makes sense. Except it doesn't, because macros. Macros may reasonably use `#[deny]` in their expansion to assert that their expanded code follows the lint. This is doesn't work when the output gets expanded into a `forbid()` context. This is pretty silly, since both the macros and the code agree on the lint! Therefore, we allow `#[deny(..)]`ing a lint that's already forbidden, keeping the level at forbid. --- compiler/rustc_lint/src/levels.rs | 5 +++- compiler/rustc_lint_defs/src/builtin.rs | 2 +- src/tools/tidy/src/issues.txt | 1 - tests/ui/lint/auxiliary/deny-macro.rs | 7 +++++ tests/ui/lint/deny-inside-forbid-ignored.rs | 16 ++++++++++ tests/ui/lint/deny-inside-forbid-ignored.stderr | 35 ++++++++++++++++++++++ tests/ui/lint/forbid-macro-with-deny.rs | 16 ++++++++++ ...sue-70819-dont-override-forbid-in-same-scope.rs | 4 +-- ...70819-dont-override-forbid-in-same-scope.stderr | 5 ++-- tests/ui/lint/issue-80988.rs | 10 ------- tests/ui/lint/issue-80988.stderr | 15 ---------- 11 files changed, 84 insertions(+), 32 deletions(-) create mode 100644 tests/ui/lint/auxiliary/deny-macro.rs create mode 100644 tests/ui/lint/deny-inside-forbid-ignored.rs create mode 100644 tests/ui/lint/deny-inside-forbid-ignored.stderr create mode 100644 tests/ui/lint/forbid-macro-with-deny.rs delete mode 100644 tests/ui/lint/issue-80988.rs delete mode 100644 tests/ui/lint/issue-80988.stderr (limited to 'src') diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 89a67fc0d89..95a8e7625ff 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -493,7 +493,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { // // This means that this only errors if we're truly lowering the lint // level from forbid. - if self.lint_added_lints && level != Level::Forbid && old_level == Level::Forbid { + if self.lint_added_lints && level == Level::Deny && old_level == Level::Forbid { + // Having a deny inside a forbid is fine and is ignored, so we skip this check. + return; + } else if self.lint_added_lints && level != Level::Forbid && old_level == Level::Forbid { // Backwards compatibility check: // // We used to not consider `forbid(lint_group)` diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 45a5ce0ca20..c11571182fe 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -156,7 +156,7 @@ declare_lint! { /// /// ```rust /// #![forbid(warnings)] - /// #![deny(bad_style)] + /// #![warn(bad_style)] /// /// fn main() {} /// ``` diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index 22126674c15..97c42752c12 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -2758,7 +2758,6 @@ ui/lint/issue-63364.rs ui/lint/issue-70819-dont-override-forbid-in-same-scope.rs ui/lint/issue-79546-fuel-ice.rs ui/lint/issue-79744.rs -ui/lint/issue-80988.rs ui/lint/issue-81218.rs ui/lint/issue-83477.rs ui/lint/issue-87274-paren-parent.rs diff --git a/tests/ui/lint/auxiliary/deny-macro.rs b/tests/ui/lint/auxiliary/deny-macro.rs new file mode 100644 index 00000000000..6106cd0ef17 --- /dev/null +++ b/tests/ui/lint/auxiliary/deny-macro.rs @@ -0,0 +1,7 @@ +#[macro_export] +macro_rules! emit_deny { + () => { + #[deny(unsafe_code)] + let _so_safe = 0; + }; +} diff --git a/tests/ui/lint/deny-inside-forbid-ignored.rs b/tests/ui/lint/deny-inside-forbid-ignored.rs new file mode 100644 index 00000000000..da5a14ce253 --- /dev/null +++ b/tests/ui/lint/deny-inside-forbid-ignored.rs @@ -0,0 +1,16 @@ +/// Ensure that using deny inside forbid is treated as a no-op, +/// and does not override the level to deny. + +#[forbid(unsafe_code)] // NO UNSAFE CODE IN HERE!! +fn main() { + #[deny(unsafe_code)] // m-m-maybe we can have unsafe code in here? + { + #[allow(unsafe_code)] // let's have some unsafe code in here + //~^ ERROR allow(unsafe_code) incompatible with previous forbid + //~| ERROR allow(unsafe_code) incompatible with previous forbid + { + unsafe { /* ≽^•⩊•^≼ */ } + //~^ ERROR usage of an `unsafe` block + } + } +} diff --git a/tests/ui/lint/deny-inside-forbid-ignored.stderr b/tests/ui/lint/deny-inside-forbid-ignored.stderr new file mode 100644 index 00000000000..b7fc0ccf5a2 --- /dev/null +++ b/tests/ui/lint/deny-inside-forbid-ignored.stderr @@ -0,0 +1,35 @@ +error[E0453]: allow(unsafe_code) incompatible with previous forbid + --> $DIR/deny-inside-forbid-ignored.rs:8:17 + | +LL | #[forbid(unsafe_code)] // NO UNSAFE CODE IN HERE!! + | ----------- `forbid` level set here +... +LL | #[allow(unsafe_code)] // let's have some unsafe code in here + | ^^^^^^^^^^^ overruled by previous forbid + +error[E0453]: allow(unsafe_code) incompatible with previous forbid + --> $DIR/deny-inside-forbid-ignored.rs:8:17 + | +LL | #[forbid(unsafe_code)] // NO UNSAFE CODE IN HERE!! + | ----------- `forbid` level set here +... +LL | #[allow(unsafe_code)] // let's have some unsafe code in here + | ^^^^^^^^^^^ overruled by previous forbid + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: usage of an `unsafe` block + --> $DIR/deny-inside-forbid-ignored.rs:12:13 + | +LL | unsafe { /* ≽^•⩊•^≼ */ } + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/deny-inside-forbid-ignored.rs:4:10 + | +LL | #[forbid(unsafe_code)] // NO UNSAFE CODE IN HERE!! + | ^^^^^^^^^^^ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0453`. diff --git a/tests/ui/lint/forbid-macro-with-deny.rs b/tests/ui/lint/forbid-macro-with-deny.rs new file mode 100644 index 00000000000..e6985ab19d1 --- /dev/null +++ b/tests/ui/lint/forbid-macro-with-deny.rs @@ -0,0 +1,16 @@ +//@ aux-build:deny-macro.rs +//@ check-pass + +// Ensure that when a macro (or normal code) does #[deny] inside a #[forbid] +// context, no error is emitted, as both parties agree on the treatment of the lint. + +#![forbid(unsafe_code)] + +extern crate deny_macro; + +fn main() { + deny_macro::emit_deny! {} + + #[deny(unsafe_code)] + let _ = 0; +} diff --git a/tests/ui/lint/issue-70819-dont-override-forbid-in-same-scope.rs b/tests/ui/lint/issue-70819-dont-override-forbid-in-same-scope.rs index 37d96129317..45b78d75b27 100644 --- a/tests/ui/lint/issue-70819-dont-override-forbid-in-same-scope.rs +++ b/tests/ui/lint/issue-70819-dont-override-forbid-in-same-scope.rs @@ -19,9 +19,9 @@ fn forbid_first(num: i32) -> i32 { #![forbid(unused)] #![deny(unused)] - //~^ ERROR: deny(unused) incompatible with previous forbid - //~| WARNING being phased out #![warn(unused)] + //~^ ERROR: warn(unused) incompatible with previous forbid + //~| WARNING being phased out #![allow(unused)] num * num diff --git a/tests/ui/lint/issue-70819-dont-override-forbid-in-same-scope.stderr b/tests/ui/lint/issue-70819-dont-override-forbid-in-same-scope.stderr index f78bf899b84..407eaf1c60a 100644 --- a/tests/ui/lint/issue-70819-dont-override-forbid-in-same-scope.stderr +++ b/tests/ui/lint/issue-70819-dont-override-forbid-in-same-scope.stderr @@ -1,9 +1,10 @@ -error: deny(unused) incompatible with previous forbid - --> $DIR/issue-70819-dont-override-forbid-in-same-scope.rs:21:13 +error: warn(unused) incompatible with previous forbid + --> $DIR/issue-70819-dont-override-forbid-in-same-scope.rs:22:13 | LL | #![forbid(unused)] | ------ `forbid` level set here LL | #![deny(unused)] +LL | #![warn(unused)] | ^^^^^^ overruled by previous forbid | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! diff --git a/tests/ui/lint/issue-80988.rs b/tests/ui/lint/issue-80988.rs deleted file mode 100644 index 80decd8e736..00000000000 --- a/tests/ui/lint/issue-80988.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Regression test for #80988 -// -//@ check-pass - -#![forbid(warnings)] - -#[deny(warnings)] -//~^ WARNING incompatible with previous forbid -//~| WARNING being phased out -fn main() {} diff --git a/tests/ui/lint/issue-80988.stderr b/tests/ui/lint/issue-80988.stderr deleted file mode 100644 index afc93fcfeef..00000000000 --- a/tests/ui/lint/issue-80988.stderr +++ /dev/null @@ -1,15 +0,0 @@ -warning: deny(warnings) incompatible with previous forbid - --> $DIR/issue-80988.rs:7:8 - | -LL | #![forbid(warnings)] - | -------- `forbid` level set here -LL | -LL | #[deny(warnings)] - | ^^^^^^^^ overruled by previous forbid - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #81670 - = note: `#[warn(forbidden_lint_groups)]` on by default - -warning: 1 warning emitted - -- cgit 1.4.1-3-g733a5 From e541058b0e5e1b6e831c27f6a031734a1b9665ff Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Fri, 18 Oct 2024 18:00:28 -0400 Subject: Update cargo --- src/tools/cargo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/cargo b/src/tools/cargo index 8c30ce53688..cf53cc54bb5 160000 --- a/src/tools/cargo +++ b/src/tools/cargo @@ -1 +1 @@ -Subproject commit 8c30ce53688e25f7e9d860b33cc914fb2957ca9a +Subproject commit cf53cc54bb593b5ec3dc2be4b1702f50c36d24d5 -- cgit 1.4.1-3-g733a5 From 3cf8a61a7ad803b3c785d1052dc4dc9fecb7a92f Mon Sep 17 00:00:00 2001 From: Alona Enraght-Moony Date: Fri, 18 Oct 2024 20:28:32 +0000 Subject: rustdoc: Switch from FxHash to sha256 for static file hashing. --- Cargo.lock | 1 + src/librustdoc/Cargo.toml | 1 + src/librustdoc/html/static/css/rustdoc.css | 18 +++++++++--------- src/librustdoc/html/static_files.rs | 11 +++++------ 4 files changed, 16 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/Cargo.lock b/Cargo.lock index 5f81a5a8496..4aa079612c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4608,6 +4608,7 @@ dependencies = [ "rustdoc-json-types", "serde", "serde_json", + "sha2", "smallvec", "tempfile", "threadpool", diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 34332de80b3..42df0b28381 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -24,6 +24,7 @@ tracing = "0.1" tracing-tree = "0.3.0" threadpool = "1.8.1" unicode-segmentation = "1.9" +sha2 = "0.10.8" [dependencies.tracing-subscriber] version = "0.3.3" diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index df9776ff5f8..2c17fd54006 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -44,7 +44,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ font-style: normal; font-weight: 400; src: local('Fira Sans'), - url("FiraSans-Regular-018c141bf0843ffd.woff2") format("woff2"); + url("FiraSans-Regular-0fe48ade.woff2") format("woff2"); font-display: swap; } @font-face { @@ -52,7 +52,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ font-style: normal; font-weight: 500; src: local('Fira Sans Medium'), - url("FiraSans-Medium-8f9a781e4970d388.woff2") format("woff2"); + url("FiraSans-Medium-e1aa3f0a.woff2") format("woff2"); font-display: swap; } @@ -62,7 +62,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ font-style: normal; font-weight: 400; src: local('Source Serif 4'), - url("SourceSerif4-Regular-46f98efaafac5295.ttf.woff2") format("woff2"); + url("SourceSerif4-Regular-6b053e98.ttf.woff2") format("woff2"); font-display: swap; } @font-face { @@ -70,7 +70,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ font-style: italic; font-weight: 400; src: local('Source Serif 4 Italic'), - url("SourceSerif4-It-acdfaf1a8af734b1.ttf.woff2") format("woff2"); + url("SourceSerif4-It-ca3b17ed.ttf.woff2") format("woff2"); font-display: swap; } @font-face { @@ -78,7 +78,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ font-style: normal; font-weight: 700; src: local('Source Serif 4 Bold'), - url("SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2") format("woff2"); + url("SourceSerif4-Bold-6d4fd4c0.ttf.woff2") format("woff2"); font-display: swap; } @@ -89,28 +89,28 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ font-weight: 400; /* Avoid using locally installed font because bad versions are in circulation: * see https://github.com/rust-lang/rust/issues/24355 */ - src: url("SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2") format("woff2"); + src: url("SourceCodePro-Regular-8badfe75.ttf.woff2") format("woff2"); font-display: swap; } @font-face { font-family: 'Source Code Pro'; font-style: italic; font-weight: 400; - src: url("SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2") format("woff2"); + src: url("SourceCodePro-It-fc8b9304.ttf.woff2") format("woff2"); font-display: swap; } @font-face { font-family: 'Source Code Pro'; font-style: normal; font-weight: 600; - src: url("SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2") format("woff2"); + src: url("SourceCodePro-Semibold-aa29a496.ttf.woff2") format("woff2"); font-display: swap; } /* Avoid using legacy CJK serif fonts in Windows like Batang. */ @font-face { font-family: 'NanumBarunGothic'; - src: url("NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2") format("woff2"); + src: url("NanumBarunGothic-13b3dcba.ttf.woff2") format("woff2"); font-display: swap; unicode-range: U+AC00-D7AF, U+1100-11FF, U+3130-318F, U+A960-A97F, U+D7B0-D7FF; } diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 6157598ba38..9e0803f5d3f 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -3,12 +3,9 @@ //! All the static files are included here for centralized access in case anything other than the //! HTML rendering code (say, the theme checker) needs to access one of these files. -use std::hash::Hasher; use std::path::{Path, PathBuf}; use std::{fmt, str}; -use rustc_data_structures::fx::FxHasher; - pub(crate) struct StaticFile { pub(crate) filename: PathBuf, pub(crate) bytes: &'static [u8], @@ -64,9 +61,11 @@ pub(crate) fn static_filename(filename: &str, contents: &[u8]) -> PathBuf { } fn static_suffix(bytes: &[u8]) -> String { - let mut hasher = FxHasher::default(); - hasher.write(bytes); - format!("-{:016x}", hasher.finish()) + use sha2::Digest; + let bytes = sha2::Sha256::digest(bytes); + let mut digest = format!("-{bytes:x}"); + digest.truncate(9); + digest } macro_rules! static_files { -- cgit 1.4.1-3-g733a5 From 65458aed68fe6786068bab00e5a46d7ecdd2a072 Mon Sep 17 00:00:00 2001 From: "许杰友 Jieyou Xu (Joe)" <39484203+jieyouxu@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:58:45 +0800 Subject: bootstrap: allow setting `--jobs` in config.toml --- config.example.toml | 5 +++ src/bootstrap/src/core/config/config.rs | 5 ++- src/bootstrap/src/core/config/flags.rs | 3 +- src/bootstrap/src/core/config/tests.rs | 58 +++++++++++++++++++++++++++++++ src/bootstrap/src/utils/change_tracker.rs | 5 +++ 5 files changed, 73 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/config.example.toml b/config.example.toml index 4b591b949b3..168ac353cff 100644 --- a/config.example.toml +++ b/config.example.toml @@ -414,6 +414,11 @@ # Specify the location of the Android NDK. Used when targeting Android. #android-ndk = "/path/to/android-ndk-r26d" +# Number of parallel jobs to be used for building and testing. If set to `0` or +# omitted, it will be automatically determined. This is the `-j`/`--jobs` flag +# passed to cargo invocations. +#jobs = 0 + # ============================================================================= # General install configuration options # ============================================================================= diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index c2ab439891e..aeb81b14638 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -891,6 +891,7 @@ define_config! { metrics: Option = "metrics", android_ndk: Option = "android-ndk", optimized_compiler_builtins: Option = "optimized-compiler-builtins", + jobs: Option = "jobs", } } @@ -1289,7 +1290,6 @@ impl Config { config.rustc_error_format = flags.rustc_error_format; config.json_output = flags.json_output; config.on_fail = flags.on_fail; - config.jobs = Some(threads_from_config(flags.jobs as u32)); config.cmd = flags.cmd; config.incremental = flags.incremental; config.dry_run = if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled }; @@ -1511,8 +1511,11 @@ impl Config { metrics: _, android_ndk, optimized_compiler_builtins, + jobs, } = toml.build.unwrap_or_default(); + config.jobs = Some(threads_from_config(flags.jobs.unwrap_or(jobs.unwrap_or(0)))); + if let Some(file_build) = build { config.build = TargetSelection::from_user(&file_build); }; diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index 3aefe517a5b..bfeb811508c 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -110,11 +110,10 @@ pub struct Flags { short, long, value_hint = clap::ValueHint::Other, - default_value_t = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get), value_name = "JOBS" )] /// number of jobs to run in parallel - pub jobs: usize, + pub jobs: Option, // This overrides the deny-warnings configuration option, // which passes -Dwarnings to the compiler invocations. #[arg(global = true, long)] diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs index 2611b6cf51b..1f02757682c 100644 --- a/src/bootstrap/src/core/config/tests.rs +++ b/src/bootstrap/src/core/config/tests.rs @@ -352,3 +352,61 @@ fn parse_rust_std_features_empty() { fn parse_rust_std_features_invalid() { parse("rust.std-features = \"backtrace\""); } + +#[test] +fn parse_jobs() { + assert_eq!(parse("build.jobs = 1").jobs, Some(1)); +} + +#[test] +fn jobs_precedence() { + // `--jobs` should take precedence over using `--set build.jobs`. + + let config = Config::parse_inner( + Flags::parse(&[ + "check".to_owned(), + "--config=/does/not/exist".to_owned(), + "--jobs=67890".to_owned(), + "--set=build.jobs=12345".to_owned(), + ]), + |&_| toml::from_str(""), + ); + assert_eq!(config.jobs, Some(67890)); + + // `--set build.jobs` should take precedence over `config.toml`. + let config = Config::parse_inner( + Flags::parse(&[ + "check".to_owned(), + "--config=/does/not/exist".to_owned(), + "--set=build.jobs=12345".to_owned(), + ]), + |&_| { + toml::from_str( + r#" + [build] + jobs = 67890 + "#, + ) + }, + ); + assert_eq!(config.jobs, Some(12345)); + + // `--jobs` > `--set build.jobs` > `config.toml` + let config = Config::parse_inner( + Flags::parse(&[ + "check".to_owned(), + "--jobs=123".to_owned(), + "--config=/does/not/exist".to_owned(), + "--set=build.jobs=456".to_owned(), + ]), + |&_| { + toml::from_str( + r#" + [build] + jobs = 789 + "#, + ) + }, + ); + assert_eq!(config.jobs, Some(123)); +} diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index b37786496cb..9169bc90a45 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -275,4 +275,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "New option `./x setup editor` added, replacing `./x setup vscode` and adding support for vim, emacs and helix.", }, + ChangeInfo { + change_id: 131838, + severity: ChangeSeverity::Info, + summary: "Allow setting `--jobs` in config.toml with `build.jobs`.", + }, ]; -- cgit 1.4.1-3-g733a5 From eea74be5c1e8ea8c0ae0c4959b85104de75acef7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Oct 2024 08:49:13 +0200 Subject: interpret errors: add map_err_kind, rename InterpError -> InterpErrorKind --- compiler/rustc_const_eval/src/const_eval/error.rs | 7 +-- .../src/const_eval/eval_queries.rs | 4 +- compiler/rustc_const_eval/src/errors.rs | 24 ++++----- compiler/rustc_const_eval/src/interpret/call.rs | 2 +- .../rustc_const_eval/src/interpret/eval_context.rs | 15 ++++-- .../rustc_const_eval/src/interpret/intrinsics.rs | 3 +- .../rustc_const_eval/src/interpret/validity.rs | 32 ++++++------ .../rustc_middle/src/mir/interpret/allocation.rs | 20 +++---- compiler/rustc_middle/src/mir/interpret/error.rs | 61 +++++++++++++--------- compiler/rustc_middle/src/mir/interpret/mod.rs | 2 +- .../rustc_mir_transform/src/known_panics_lint.rs | 2 +- .../borrow_tracker/stacked_borrows/diagnostics.rs | 10 ++-- .../src/borrow_tracker/tree_borrows/diagnostics.rs | 2 +- .../miri/src/borrow_tracker/tree_borrows/tree.rs | 4 +- src/tools/miri/src/diagnostics.rs | 2 +- src/tools/miri/src/intrinsics/simd.rs | 10 ++-- src/tools/miri/src/shims/foreign_items.rs | 3 +- 17 files changed, 108 insertions(+), 95 deletions(-) (limited to 'src') diff --git a/compiler/rustc_const_eval/src/const_eval/error.rs b/compiler/rustc_const_eval/src/const_eval/error.rs index fd05664e2f2..6686413bf02 100644 --- a/compiler/rustc_const_eval/src/const_eval/error.rs +++ b/compiler/rustc_const_eval/src/const_eval/error.rs @@ -11,7 +11,8 @@ use rustc_span::{Span, Symbol}; use super::CompileTimeMachine; use crate::errors::{self, FrameNote, ReportErrorExt}; use crate::interpret::{ - ErrorHandled, Frame, InterpError, InterpErrorInfo, MachineStopType, err_inval, err_machine_stop, + ErrorHandled, Frame, InterpErrorInfo, InterpErrorKind, MachineStopType, err_inval, + err_machine_stop, }; /// The CTFE machine has some custom error kinds. @@ -57,7 +58,7 @@ impl MachineStopType for ConstEvalErrKind { } } -/// The errors become [`InterpError::MachineStop`] when being raised. +/// The errors become [`InterpErrorKind::MachineStop`] when being raised. impl<'tcx> Into> for ConstEvalErrKind { fn into(self) -> InterpErrorInfo<'tcx> { err_machine_stop!(self).into() @@ -124,7 +125,7 @@ pub fn get_span_and_frames<'tcx>( /// `get_span_and_frames`. pub(super) fn report<'tcx, C, F, E>( tcx: TyCtxt<'tcx>, - error: InterpError<'tcx>, + error: InterpErrorKind<'tcx>, span: Span, get_span_and_frames: C, mk: F, diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 672353e629d..7319c251bbd 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -18,7 +18,7 @@ use tracing::{debug, instrument, trace}; use super::{CanAccessMutGlobal, CompileTimeInterpCx, CompileTimeMachine}; use crate::const_eval::CheckAlignment; use crate::interpret::{ - CtfeValidationMode, GlobalId, Immediate, InternKind, InternResult, InterpCx, InterpError, + CtfeValidationMode, GlobalId, Immediate, InternKind, InternResult, InterpCx, InterpErrorKind, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, StackPopCleanup, create_static_alloc, eval_nullary_intrinsic, intern_const_alloc_recursive, interp_ok, throw_exhaust, }; @@ -463,7 +463,7 @@ fn report_validation_error<'tcx>( error: InterpErrorInfo<'tcx>, alloc_id: AllocId, ) -> ErrorHandled { - if !matches!(error.kind(), InterpError::UndefinedBehavior(_)) { + if !matches!(error.kind(), InterpErrorKind::UndefinedBehavior(_)) { // Some other error happened during validation, e.g. an unsupported operation. return report_eval_error(ecx, cid, error); } diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index c943236affc..211668cf055 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -9,7 +9,7 @@ use rustc_errors::{ use rustc_hir::ConstContext; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::mir::interpret::{ - CheckInAllocMsg, CtfeProvenance, ExpectedKind, InterpError, InvalidMetaKind, + CheckInAllocMsg, CtfeProvenance, ExpectedKind, InterpErrorKind, InvalidMetaKind, InvalidProgramInfo, Misalignment, Pointer, PointerKind, ResourceExhaustionInfo, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo, }; @@ -835,23 +835,23 @@ impl ReportErrorExt for UnsupportedOpInfo { } } -impl<'tcx> ReportErrorExt for InterpError<'tcx> { +impl<'tcx> ReportErrorExt for InterpErrorKind<'tcx> { fn diagnostic_message(&self) -> DiagMessage { match self { - InterpError::UndefinedBehavior(ub) => ub.diagnostic_message(), - InterpError::Unsupported(e) => e.diagnostic_message(), - InterpError::InvalidProgram(e) => e.diagnostic_message(), - InterpError::ResourceExhaustion(e) => e.diagnostic_message(), - InterpError::MachineStop(e) => e.diagnostic_message(), + InterpErrorKind::UndefinedBehavior(ub) => ub.diagnostic_message(), + InterpErrorKind::Unsupported(e) => e.diagnostic_message(), + InterpErrorKind::InvalidProgram(e) => e.diagnostic_message(), + InterpErrorKind::ResourceExhaustion(e) => e.diagnostic_message(), + InterpErrorKind::MachineStop(e) => e.diagnostic_message(), } } fn add_args(self, diag: &mut Diag<'_, G>) { match self { - InterpError::UndefinedBehavior(ub) => ub.add_args(diag), - InterpError::Unsupported(e) => e.add_args(diag), - InterpError::InvalidProgram(e) => e.add_args(diag), - InterpError::ResourceExhaustion(e) => e.add_args(diag), - InterpError::MachineStop(e) => e.add_args(&mut |name, value| { + InterpErrorKind::UndefinedBehavior(ub) => ub.add_args(diag), + InterpErrorKind::Unsupported(e) => e.add_args(diag), + InterpErrorKind::InvalidProgram(e) => e.add_args(diag), + InterpErrorKind::ResourceExhaustion(e) => e.add_args(diag), + InterpErrorKind::MachineStop(e) => e.add_args(&mut |name, value| { diag.arg(name, value); }), } diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 4945563f4a4..85d99900c6c 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -471,7 +471,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Don't forget to mark "initially live" locals as live. self.storage_live_for_always_live_locals()?; }; - res.inspect_err(|_| { + res.inspect_err_kind(|_| { // Don't show the incomplete stack frame in the error stacktrace. self.stack_mut().pop(); }) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 02dd7821ef6..a1c773a4b80 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -19,7 +19,7 @@ use rustc_trait_selection::traits::ObligationCtxt; use tracing::{debug, instrument, trace}; use super::{ - Frame, FrameInfo, GlobalId, InterpError, InterpErrorInfo, InterpResult, MPlaceTy, Machine, + Frame, FrameInfo, GlobalId, InterpErrorInfo, InterpErrorKind, InterpResult, MPlaceTy, Machine, MemPlaceMeta, Memory, OpTy, Place, PlaceTy, PointerArithmetic, Projectable, Provenance, err_inval, interp_ok, throw_inval, throw_ub, throw_ub_custom, }; @@ -73,7 +73,7 @@ where } impl<'tcx, M: Machine<'tcx>> LayoutOfHelpers<'tcx> for InterpCx<'tcx, M> { - type LayoutOfResult = Result, InterpError<'tcx>>; + type LayoutOfResult = Result, InterpErrorKind<'tcx>>; #[inline] fn layout_tcx_at_span(&self) -> Span { @@ -82,20 +82,25 @@ impl<'tcx, M: Machine<'tcx>> LayoutOfHelpers<'tcx> for InterpCx<'tcx, M> { } #[inline] - fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> InterpError<'tcx> { + fn handle_layout_err( + &self, + err: LayoutError<'tcx>, + _: Span, + _: Ty<'tcx>, + ) -> InterpErrorKind<'tcx> { err_inval!(Layout(err)) } } impl<'tcx, M: Machine<'tcx>> FnAbiOfHelpers<'tcx> for InterpCx<'tcx, M> { - type FnAbiOfResult = Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, InterpError<'tcx>>; + type FnAbiOfResult = Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, InterpErrorKind<'tcx>>; fn handle_fn_abi_err( &self, err: FnAbiError<'tcx>, _span: Span, _fn_abi_request: FnAbiRequest<'tcx>, - ) -> InterpError<'tcx> { + ) -> InterpErrorKind<'tcx> { match err { FnAbiError::Layout(err) => err_inval!(Layout(err)), FnAbiError::AdjustForForeignAbi(err) => { diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 540898ec645..4e603f57c56 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -324,13 +324,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { dist.checked_neg().unwrap(), // i64::MIN is impossible as no allocation can be that large CheckInAllocMsg::OffsetFromTest, ) - .map_err(|_| { + .map_err_kind(|_| { // Make the error more specific. err_ub_custom!( fluent::const_eval_offset_from_different_allocations, name = intrinsic_name, ) - .into() })?; // Perform division by size to compute return value. diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 13641ef2bd3..b6120ce82fe 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -17,8 +17,8 @@ use rustc_hir as hir; use rustc_middle::bug; use rustc_middle::mir::interpret::ValidationErrorKind::{self, *}; use rustc_middle::mir::interpret::{ - ExpectedKind, InterpError, InterpErrorInfo, InvalidMetaKind, Misalignment, PointerKind, - Provenance, UnsupportedOpInfo, ValidationErrorInfo, alloc_range, interp_ok, + ExpectedKind, InterpErrorKind, InvalidMetaKind, Misalignment, PointerKind, Provenance, + UnsupportedOpInfo, ValidationErrorInfo, alloc_range, interp_ok, }; use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, Ty}; @@ -37,8 +37,8 @@ use super::{ // for the validation errors #[rustfmt::skip] -use super::InterpError::UndefinedBehavior as Ub; -use super::InterpError::Unsupported as Unsup; +use super::InterpErrorKind::UndefinedBehavior as Ub; +use super::InterpErrorKind::Unsupported as Unsup; use super::UndefinedBehaviorInfo::*; use super::UnsupportedOpInfo::*; @@ -97,20 +97,19 @@ macro_rules! try_validation { ($e:expr, $where:expr, $( $( $p:pat_param )|+ => $kind: expr ),+ $(,)? ) => {{ - $e.map_err(|e| { + $e.map_err_kind(|e| { // We catch the error and turn it into a validation failure. We are okay with // allocation here as this can only slow down builds that fail anyway. - let (kind, backtrace) = e.into_parts(); - match kind { + match e { $( $($p)|+ => { err_validation_failure!( $where, $kind - ).into() + ) } ),+, - _ => InterpErrorInfo::from_parts(kind, backtrace), + e => e, } })? }}; @@ -1230,11 +1229,10 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, // No need for an alignment check here, this is not an actual memory access. let alloc = self.ecx.get_ptr_alloc(mplace.ptr(), size)?.expect("we already excluded size 0"); - alloc.get_bytes_strip_provenance().map_err(|err| { + alloc.get_bytes_strip_provenance().map_err_kind(|kind| { // Some error happened, try to provide a more detailed description. // For some errors we might be able to provide extra information. // (This custom logic does not fit the `try_validation!` macro.) - let (kind, backtrace) = err.into_parts(); match kind { Ub(InvalidUninitBytes(Some((_alloc_id, access)))) | Unsup(ReadPointerAsInt(Some((_alloc_id, access)))) => { // Some byte was uninitialized, determine which @@ -1247,14 +1245,14 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, self.path.push(PathElem::ArrayElem(i)); if matches!(kind, Ub(InvalidUninitBytes(_))) { - err_validation_failure!(self.path, Uninit { expected }).into() + err_validation_failure!(self.path, Uninit { expected }) } else { - err_validation_failure!(self.path, PointerAsInt { expected }).into() + err_validation_failure!(self.path, PointerAsInt { expected }) } } // Propagate upwards (that will also check for unexpected errors). - _ => return InterpErrorInfo::from_parts(kind, backtrace), + err => err, } })?; @@ -1368,12 +1366,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { v.reset_padding(val)?; interp_ok(()) }) - .map_err(|err| { + .map_err_info(|err| { if !matches!( err.kind(), err_ub!(ValidationError { .. }) - | InterpError::InvalidProgram(_) - | InterpError::Unsupported(UnsupportedOpInfo::ExternTypeField) + | InterpErrorKind::InvalidProgram(_) + | InterpErrorKind::Unsupported(UnsupportedOpInfo::ExternTypeField) ) { bug!( "Unexpected error during validation: {}", diff --git a/compiler/rustc_middle/src/mir/interpret/allocation.rs b/compiler/rustc_middle/src/mir/interpret/allocation.rs index 04d035e27ba..ac3baf74ca7 100644 --- a/compiler/rustc_middle/src/mir/interpret/allocation.rs +++ b/compiler/rustc_middle/src/mir/interpret/allocation.rs @@ -18,9 +18,9 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use rustc_target::abi::{Align, HasDataLayout, Size}; use super::{ - AllocId, BadBytesAccess, CtfeProvenance, InterpError, InterpResult, Pointer, PointerArithmetic, - Provenance, ResourceExhaustionInfo, Scalar, ScalarSizeMismatch, UndefinedBehaviorInfo, - UnsupportedOpInfo, interp_ok, read_target_uint, write_target_uint, + AllocId, BadBytesAccess, CtfeProvenance, InterpErrorKind, InterpResult, Pointer, + PointerArithmetic, Provenance, ResourceExhaustionInfo, Scalar, ScalarSizeMismatch, + UndefinedBehaviorInfo, UnsupportedOpInfo, interp_ok, read_target_uint, write_target_uint, }; use crate::ty; @@ -199,22 +199,22 @@ impl From for AllocError { } impl AllocError { - pub fn to_interp_error<'tcx>(self, alloc_id: AllocId) -> InterpError<'tcx> { + pub fn to_interp_error<'tcx>(self, alloc_id: AllocId) -> InterpErrorKind<'tcx> { use AllocError::*; match self { ScalarSizeMismatch(s) => { - InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ScalarSizeMismatch(s)) + InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::ScalarSizeMismatch(s)) } - ReadPointerAsInt(info) => InterpError::Unsupported( + ReadPointerAsInt(info) => InterpErrorKind::Unsupported( UnsupportedOpInfo::ReadPointerAsInt(info.map(|b| (alloc_id, b))), ), - OverwritePartialPointer(offset) => InterpError::Unsupported( + OverwritePartialPointer(offset) => InterpErrorKind::Unsupported( UnsupportedOpInfo::OverwritePartialPointer(Pointer::new(alloc_id, offset)), ), - ReadPartialPointer(offset) => InterpError::Unsupported( + ReadPartialPointer(offset) => InterpErrorKind::Unsupported( UnsupportedOpInfo::ReadPartialPointer(Pointer::new(alloc_id, offset)), ), - InvalidUninitBytes(info) => InterpError::UndefinedBehavior( + InvalidUninitBytes(info) => InterpErrorKind::UndefinedBehavior( UndefinedBehaviorInfo::InvalidUninitBytes(info.map(|b| (alloc_id, b))), ), } @@ -318,7 +318,7 @@ impl Allocation { pub fn try_uninit<'tcx>(size: Size, align: Align) -> InterpResult<'tcx, Self> { Self::uninit_inner(size, align, || { ty::tls::with(|tcx| tcx.dcx().delayed_bug("exhausted memory during interpretation")); - InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted) + InterpErrorKind::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted) }) .into() } diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index fcb87e19435..b520f21ce20 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -113,7 +113,7 @@ pub struct InterpErrorInfo<'tcx>(Box>); #[derive(Debug)] struct InterpErrorInfoInner<'tcx> { - kind: InterpError<'tcx>, + kind: InterpErrorKind<'tcx>, backtrace: InterpErrorBacktrace, } @@ -154,21 +154,21 @@ impl InterpErrorBacktrace { } impl<'tcx> InterpErrorInfo<'tcx> { - pub fn into_parts(self) -> (InterpError<'tcx>, InterpErrorBacktrace) { + pub fn into_parts(self) -> (InterpErrorKind<'tcx>, InterpErrorBacktrace) { let InterpErrorInfo(box InterpErrorInfoInner { kind, backtrace }) = self; (kind, backtrace) } - pub fn into_kind(self) -> InterpError<'tcx> { + pub fn into_kind(self) -> InterpErrorKind<'tcx> { self.0.kind } - pub fn from_parts(kind: InterpError<'tcx>, backtrace: InterpErrorBacktrace) -> Self { + pub fn from_parts(kind: InterpErrorKind<'tcx>, backtrace: InterpErrorBacktrace) -> Self { Self(Box::new(InterpErrorInfoInner { kind, backtrace })) } #[inline] - pub fn kind(&self) -> &InterpError<'tcx> { + pub fn kind(&self) -> &InterpErrorKind<'tcx> { &self.0.kind } } @@ -179,13 +179,13 @@ fn print_backtrace(backtrace: &Backtrace) { impl From for InterpErrorInfo<'_> { fn from(err: ErrorGuaranteed) -> Self { - InterpError::InvalidProgram(InvalidProgramInfo::AlreadyReported(err.into())).into() + InterpErrorKind::InvalidProgram(InvalidProgramInfo::AlreadyReported(err.into())).into() } } impl From for InterpErrorInfo<'_> { fn from(err: ErrorHandled) -> Self { - InterpError::InvalidProgram(match err { + InterpErrorKind::InvalidProgram(match err { ErrorHandled::Reported(r, _span) => InvalidProgramInfo::AlreadyReported(r), ErrorHandled::TooGeneric(_span) => InvalidProgramInfo::TooGeneric, }) @@ -193,8 +193,8 @@ impl From for InterpErrorInfo<'_> { } } -impl<'tcx> From> for InterpErrorInfo<'tcx> { - fn from(kind: InterpError<'tcx>) -> Self { +impl<'tcx> From> for InterpErrorInfo<'tcx> { + fn from(kind: InterpErrorKind<'tcx>) -> Self { InterpErrorInfo(Box::new(InterpErrorInfoInner { kind, backtrace: InterpErrorBacktrace::new(), @@ -590,7 +590,7 @@ impl dyn MachineStopType { } #[derive(Debug)] -pub enum InterpError<'tcx> { +pub enum InterpErrorKind<'tcx> { /// The program caused undefined behavior. UndefinedBehavior(UndefinedBehaviorInfo<'tcx>), /// The program did something the interpreter does not support (some of these *might* be UB @@ -606,25 +606,25 @@ pub enum InterpError<'tcx> { MachineStop(Box), } -impl InterpError<'_> { +impl InterpErrorKind<'_> { /// Some errors do string formatting even if the error is never printed. /// To avoid performance issues, there are places where we want to be sure to never raise these formatting errors, /// so this method lets us detect them and `bug!` on unexpected errors. pub fn formatted_string(&self) -> bool { matches!( self, - InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_)) - | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationError { .. }) - | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_)) + InterpErrorKind::Unsupported(UnsupportedOpInfo::Unsupported(_)) + | InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::ValidationError { .. }) + | InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_)) ) } } -// Macros for constructing / throwing `InterpError` +// Macros for constructing / throwing `InterpErrorKind` #[macro_export] macro_rules! err_unsup { ($($tt:tt)*) => { - $crate::mir::interpret::InterpError::Unsupported( + $crate::mir::interpret::InterpErrorKind::Unsupported( $crate::mir::interpret::UnsupportedOpInfo::$($tt)* ) }; @@ -638,7 +638,7 @@ macro_rules! err_unsup_format { #[macro_export] macro_rules! err_inval { ($($tt:tt)*) => { - $crate::mir::interpret::InterpError::InvalidProgram( + $crate::mir::interpret::InterpErrorKind::InvalidProgram( $crate::mir::interpret::InvalidProgramInfo::$($tt)* ) }; @@ -647,7 +647,7 @@ macro_rules! err_inval { #[macro_export] macro_rules! err_ub { ($($tt:tt)*) => { - $crate::mir::interpret::InterpError::UndefinedBehavior( + $crate::mir::interpret::InterpErrorKind::UndefinedBehavior( $crate::mir::interpret::UndefinedBehaviorInfo::$($tt)* ) }; @@ -680,7 +680,7 @@ macro_rules! err_ub_custom { #[macro_export] macro_rules! err_exhaust { ($($tt:tt)*) => { - $crate::mir::interpret::InterpError::ResourceExhaustion( + $crate::mir::interpret::InterpErrorKind::ResourceExhaustion( $crate::mir::interpret::ResourceExhaustionInfo::$($tt)* ) }; @@ -689,7 +689,7 @@ macro_rules! err_exhaust { #[macro_export] macro_rules! err_machine_stop { ($($tt:tt)*) => { - $crate::mir::interpret::InterpError::MachineStop(Box::new($($tt)*)) + $crate::mir::interpret::InterpErrorKind::MachineStop(Box::new($($tt)*)) }; } @@ -792,9 +792,9 @@ impl<'tcx, T> ops::FromResidual for InterpResult_<'tcx, T> { } // Allow `yeet`ing `InterpError` in functions returning `InterpResult_`. -impl<'tcx, T> ops::FromResidual>> for InterpResult_<'tcx, T> { +impl<'tcx, T> ops::FromResidual>> for InterpResult_<'tcx, T> { #[inline] - fn from_residual(ops::Yeet(e): ops::Yeet>) -> Self { + fn from_residual(ops::Yeet(e): ops::Yeet>) -> Self { Self::new(Err(e.into())) } } @@ -856,7 +856,7 @@ impl<'tcx, T> InterpResult_<'tcx, T> { } #[inline] - pub fn map_err( + pub fn map_err_info( self, f: impl FnOnce(InterpErrorInfo<'tcx>) -> InterpErrorInfo<'tcx>, ) -> InterpResult<'tcx, T> { @@ -864,8 +864,19 @@ impl<'tcx, T> InterpResult_<'tcx, T> { } #[inline] - pub fn inspect_err(self, f: impl FnOnce(&InterpErrorInfo<'tcx>)) -> InterpResult<'tcx, T> { - InterpResult_::new(self.disarm().inspect_err(f)) + pub fn map_err_kind( + self, + f: impl FnOnce(InterpErrorKind<'tcx>) -> InterpErrorKind<'tcx>, + ) -> InterpResult<'tcx, T> { + InterpResult_::new(self.disarm().map_err(|mut e| { + e.0.kind = f(e.0.kind); + e + })) + } + + #[inline] + pub fn inspect_err_kind(self, f: impl FnOnce(&InterpErrorKind<'tcx>)) -> InterpResult<'tcx, T> { + InterpResult_::new(self.disarm().inspect_err(|e| f(&e.0.kind))) } #[inline] diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs index 115bcdbc589..790ff3e2fe0 100644 --- a/compiler/rustc_middle/src/mir/interpret/mod.rs +++ b/compiler/rustc_middle/src/mir/interpret/mod.rs @@ -36,7 +36,7 @@ pub use self::allocation::{ pub use self::error::{ BadBytesAccess, CheckAlignMsg, CheckInAllocMsg, ErrorHandled, EvalStaticInitializerRawResult, EvalToAllocationRawResult, EvalToConstValueResult, EvalToValTreeResult, ExpectedKind, - InterpError, InterpErrorInfo, InterpResult, InvalidMetaKind, InvalidProgramInfo, + InterpErrorInfo, InterpErrorKind, InterpResult, InvalidMetaKind, InvalidProgramInfo, MachineStopType, Misalignment, PointerKind, ReportedErrorInfo, ResourceExhaustionInfo, ScalarSizeMismatch, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo, ValidationErrorKind, interp_ok, diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 8f490094d60..2f97c408f2a 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -232,7 +232,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { F: FnOnce(&mut Self) -> InterpResult<'tcx, T>, { f(self) - .map_err(|err| { + .map_err_info(|err| { trace!("InterpCx operation failed: {:?}", err); // Some errors shouldn't come up because creating them causes // an allocation, which we should avoid. When that happens, diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index 146f9902f6f..1684abeec6b 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -13,7 +13,7 @@ fn err_sb_ub<'tcx>( msg: String, help: Vec, history: Option, -) -> InterpError<'tcx> { +) -> InterpErrorKind<'tcx> { err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history }) } @@ -376,7 +376,7 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { /// Report a descriptive error when `new` could not be granted from `derived_from`. #[inline(never)] // This is only called on fatal code paths - pub(super) fn grant_error(&self, stack: &Stack) -> InterpError<'tcx> { + pub(super) fn grant_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> { let Operation::Retag(op) = &self.operation else { unreachable!("grant_error should only be called during a retag") }; @@ -402,7 +402,7 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { /// Report a descriptive error when `access` is not permitted based on `tag`. #[inline(never)] // This is only called on fatal code paths - pub(super) fn access_error(&self, stack: &Stack) -> InterpError<'tcx> { + pub(super) fn access_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> { // Deallocation and retagging also do an access as part of their thing, so handle that here, too. let op = match &self.operation { Operation::Access(op) => op, @@ -424,7 +424,7 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { } #[inline(never)] // This is only called on fatal code paths - pub(super) fn protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpError<'tcx> { + pub(super) fn protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpErrorKind<'tcx> { let protected = match kind { ProtectorKind::WeakProtector => "weakly protected", ProtectorKind::StrongProtector => "strongly protected", @@ -445,7 +445,7 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { } #[inline(never)] // This is only called on fatal code paths - pub fn dealloc_error(&self, stack: &Stack) -> InterpError<'tcx> { + pub fn dealloc_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> { let Operation::Dealloc(op) = &self.operation else { unreachable!("dealloc_error should only be called during a deallocation") }; diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs index cb840f19e3b..b2fd9b2bf05 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -298,7 +298,7 @@ pub(super) struct TbError<'node> { impl TbError<'_> { /// Produce a UB error. - pub fn build<'tcx>(self) -> InterpError<'tcx> { + pub fn build<'tcx>(self) -> InterpErrorKind<'tcx> { use TransitionError::*; let cause = self.access_cause; let accessed = self.accessed_info; diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs index 15cefab1a68..a551b017dfc 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs @@ -674,7 +674,7 @@ impl<'tcx> Tree { Ok(()) } }, - |args: ErrHandlerArgs<'_, TransitionError>| -> InterpError<'tcx> { + |args: ErrHandlerArgs<'_, TransitionError>| -> InterpErrorKind<'tcx> { let ErrHandlerArgs { error_kind, conflicting_info, accessed_info } = args; TbError { conflicting_info, @@ -772,7 +772,7 @@ impl<'tcx> Tree { let err_handler = |perms_range: Range, access_cause: diagnostics::AccessCause, args: ErrHandlerArgs<'_, TransitionError>| - -> InterpError<'tcx> { + -> InterpErrorKind<'tcx> { let ErrHandlerArgs { error_kind, conflicting_info, accessed_info } = args; TbError { conflicting_info, diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 475139a3b51..f055662891e 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -214,7 +214,7 @@ pub fn report_error<'tcx>( ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, e: InterpErrorInfo<'tcx>, ) -> Option<(i64, bool)> { - use InterpError::*; + use InterpErrorKind::*; use UndefinedBehaviorInfo::*; let mut msg = vec![]; diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index de293495e86..59776a484b5 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -245,17 +245,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let val = match which { Op::MirOp(mir_op) => { // This does NaN adjustments. - let val = this.binary_op(mir_op, &left, &right).map_err(|err| { - match err.kind() { - &InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ShiftOverflow { shift_amount, .. }) => { + let val = this.binary_op(mir_op, &left, &right).map_err_kind(|kind| { + match kind { + InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::ShiftOverflow { shift_amount, .. }) => { // This resets the interpreter backtrace, but it's not worth avoiding that. let shift_amount = match shift_amount { Either::Left(v) => v.to_string(), Either::Right(v) => v.to_string(), }; - err_ub_format!("overflowing shift by {shift_amount} in `simd_{intrinsic_name}` in lane {i}").into() + err_ub_format!("overflowing shift by {shift_amount} in `simd_{intrinsic_name}` in lane {i}") } - _ => err + kind => kind } })?; if matches!(mir_op, BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) { diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 7fce5b63306..f6f91e58969 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -289,11 +289,10 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "miri_get_alloc_id" => { let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?; let ptr = this.read_pointer(ptr)?; - let (alloc_id, _, _) = this.ptr_get_alloc_id(ptr, 0).map_err(|_e| { + let (alloc_id, _, _) = this.ptr_get_alloc_id(ptr, 0).map_err_kind(|_e| { err_machine_stop!(TerminationInfo::Abort(format!( "pointer passed to `miri_get_alloc_id` must not be dangling, got {ptr:?}" ))) - .into() })?; this.write_scalar(Scalar::from_u64(alloc_id.0.get()), dest)?; } -- cgit 1.4.1-3-g733a5 From d82a21f9adf840a48c5ddee3dbc474259d0bc1d3 Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sat, 19 Oct 2024 11:11:33 +0000 Subject: Warn on redundant `--cfg` directive when revisions are used --- src/tools/compiletest/src/runtest.rs | 14 +++++++++++++- tests/ui/precondition-checks/layout.rs | 2 -- tests/ui/sanitizer/cfg.rs | 12 ++++++------ .../core-std-import-order-issue-83564.no_std.fixed | 2 +- tests/ui/suggestions/core-std-import-order-issue-83564.rs | 2 +- .../core-std-import-order-issue-83564.std.fixed | 2 +- 6 files changed, 22 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index f0452008304..5b8a96a54c2 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -468,7 +468,19 @@ impl<'test> TestCx<'test> { if let Some(revision) = self.revision { let normalized_revision = normalize_revision(revision); - cmd.args(&["--cfg", &normalized_revision]); + let cfg_arg = ["--cfg", &normalized_revision]; + let arg = format!("--cfg={normalized_revision}"); + if self + .props + .compile_flags + .windows(2) + .any(|args| args == cfg_arg || args[0] == arg || args[1] == arg) + { + panic!( + "error: redundant cfg argument `{normalized_revision}` is already created by the revision" + ); + } + cmd.args(cfg_arg); } if !self.props.no_auto_check_cfg { diff --git a/tests/ui/precondition-checks/layout.rs b/tests/ui/precondition-checks/layout.rs index 4fd1bbc4a99..4ee66cc9328 100644 --- a/tests/ui/precondition-checks/layout.rs +++ b/tests/ui/precondition-checks/layout.rs @@ -2,8 +2,6 @@ //@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes //@ error-pattern: unsafe precondition(s) violated: Layout::from_size_align_unchecked requires //@ revisions: toolarge badalign -//@[toolarge] compile-flags: --cfg toolarge -//@[badalign] compile-flags: --cfg badalign fn main() { unsafe { diff --git a/tests/ui/sanitizer/cfg.rs b/tests/ui/sanitizer/cfg.rs index b1ba17d5713..7b8f285e41a 100644 --- a/tests/ui/sanitizer/cfg.rs +++ b/tests/ui/sanitizer/cfg.rs @@ -5,19 +5,19 @@ //@ revisions: address cfi kcfi leak memory thread //@compile-flags: -Ctarget-feature=-crt-static //@[address]needs-sanitizer-address -//@[address]compile-flags: -Zsanitizer=address --cfg address +//@[address]compile-flags: -Zsanitizer=address //@[cfi]needs-sanitizer-cfi -//@[cfi]compile-flags: -Zsanitizer=cfi --cfg cfi +//@[cfi]compile-flags: -Zsanitizer=cfi //@[cfi]compile-flags: -Clto -Ccodegen-units=1 //@[kcfi]needs-llvm-components: x86 -//@[kcfi]compile-flags: -Zsanitizer=kcfi --cfg kcfi --target x86_64-unknown-none +//@[kcfi]compile-flags: -Zsanitizer=kcfi --target x86_64-unknown-none //@[kcfi]compile-flags: -C panic=abort //@[leak]needs-sanitizer-leak -//@[leak]compile-flags: -Zsanitizer=leak --cfg leak +//@[leak]compile-flags: -Zsanitizer=leak //@[memory]needs-sanitizer-memory -//@[memory]compile-flags: -Zsanitizer=memory --cfg memory +//@[memory]compile-flags: -Zsanitizer=memory //@[thread]needs-sanitizer-thread -//@[thread]compile-flags: -Zsanitizer=thread --cfg thread +//@[thread]compile-flags: -Zsanitizer=thread #![feature(cfg_sanitize, no_core, lang_items)] #![crate_type="lib"] diff --git a/tests/ui/suggestions/core-std-import-order-issue-83564.no_std.fixed b/tests/ui/suggestions/core-std-import-order-issue-83564.no_std.fixed index 02d667d9844..b7b94a05121 100644 --- a/tests/ui/suggestions/core-std-import-order-issue-83564.no_std.fixed +++ b/tests/ui/suggestions/core-std-import-order-issue-83564.no_std.fixed @@ -4,7 +4,7 @@ // For some reason, Rust 2018 or higher is required to reproduce the bug. //@ run-rustfix //@ revisions: no_std std -//@ [no_std]compile-flags: --cfg=no_std -C panic=abort +//@ [no_std]compile-flags: -C panic=abort #![cfg_attr(no_std, no_std)] use core::num::NonZero; diff --git a/tests/ui/suggestions/core-std-import-order-issue-83564.rs b/tests/ui/suggestions/core-std-import-order-issue-83564.rs index 5bb5bfe176b..4cfc9a6bf74 100644 --- a/tests/ui/suggestions/core-std-import-order-issue-83564.rs +++ b/tests/ui/suggestions/core-std-import-order-issue-83564.rs @@ -4,7 +4,7 @@ // For some reason, Rust 2018 or higher is required to reproduce the bug. //@ run-rustfix //@ revisions: no_std std -//@ [no_std]compile-flags: --cfg=no_std -C panic=abort +//@ [no_std]compile-flags: -C panic=abort #![cfg_attr(no_std, no_std)] fn main() { diff --git a/tests/ui/suggestions/core-std-import-order-issue-83564.std.fixed b/tests/ui/suggestions/core-std-import-order-issue-83564.std.fixed index 3492b42c685..84c7c19d19e 100644 --- a/tests/ui/suggestions/core-std-import-order-issue-83564.std.fixed +++ b/tests/ui/suggestions/core-std-import-order-issue-83564.std.fixed @@ -4,7 +4,7 @@ // For some reason, Rust 2018 or higher is required to reproduce the bug. //@ run-rustfix //@ revisions: no_std std -//@ [no_std]compile-flags: --cfg=no_std -C panic=abort +//@ [no_std]compile-flags: -C panic=abort #![cfg_attr(no_std, no_std)] use std::num::NonZero; -- cgit 1.4.1-3-g733a5 From 28fce83cc6d9cce7caf1e4f11462ac76c93ee734 Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sat, 19 Oct 2024 13:02:37 +0000 Subject: Align boolean option descriptions in `configure.py` --- src/bootstrap/configure.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py index a555a26367d..15137fbb2b5 100755 --- a/src/bootstrap/configure.py +++ b/src/bootstrap/configure.py @@ -193,7 +193,8 @@ if '--help' in sys.argv or '-h' in sys.argv: if option.value: print('\t{:30} {}'.format('--{}=VAL'.format(option.name), option.desc)) else: - print('\t{:30} {}'.format('--enable-{} OR --disable-{}'.format(option.name, option.name), option.desc)) + print('\t--enable-{:25} OR --disable-{}'.format(option.name, option.name)) + print('\t\t' + option.desc) print('') print('This configure script is a thin configuration shim over the true') print('configuration system, `config.toml`. You can explore the comments') -- cgit 1.4.1-3-g733a5 From ebb842328af5ef19c98cc361e1a27dc8f87ff8cd Mon Sep 17 00:00:00 2001 From: Alona Enraght-Moony Date: Sat, 19 Oct 2024 14:37:11 +0000 Subject: rustdoc: Extract footnote logic into it's own module. --- src/librustdoc/html/markdown.rs | 84 ++----------------------------- src/librustdoc/html/markdown/footnotes.rs | 82 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 79 deletions(-) create mode 100644 src/librustdoc/html/markdown/footnotes.rs (limited to 'src') diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 315b7742a4c..7826a5d8394 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -37,7 +37,7 @@ use std::sync::OnceLock; use pulldown_cmark::{ BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html, }; -use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Diag, DiagMessage}; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::TyCtxt; @@ -57,6 +57,7 @@ use crate::html::length_limit::HtmlWithLimit; use crate::html::render::small_url_encode; use crate::html::toc::{Toc, TocBuilder}; +mod footnotes; #[cfg(test)] mod tests; @@ -646,81 +647,6 @@ impl<'a, I: Iterator>> Iterator for SummaryLine<'a, I> { } } -/// Moves all footnote definitions to the end and add back links to the -/// references. -struct Footnotes<'a, I> { - inner: I, - footnotes: FxIndexMap>, u16)>, -} - -impl<'a, I> Footnotes<'a, I> { - fn new(iter: I) -> Self { - Footnotes { inner: iter, footnotes: FxIndexMap::default() } - } - - fn get_entry(&mut self, key: &str) -> &mut (Vec>, u16) { - let new_id = self.footnotes.len() + 1; - let key = key.to_owned(); - self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16)) - } -} - -impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { - type Item = SpannedEvent<'a>; - - fn next(&mut self) -> Option { - loop { - match self.inner.next() { - Some((Event::FootnoteReference(ref reference), range)) => { - let entry = self.get_entry(reference); - let reference = format!( - "{0}", - (*entry).1 - ); - return Some((Event::Html(reference.into()), range)); - } - Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => { - let mut content = Vec::new(); - for (event, _) in &mut self.inner { - if let Event::End(TagEnd::FootnoteDefinition) = event { - break; - } - content.push(event); - } - let entry = self.get_entry(&def); - (*entry).0 = content; - } - Some(e) => return Some(e), - None => { - if !self.footnotes.is_empty() { - let mut v: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect(); - v.sort_by(|a, b| a.1.cmp(&b.1)); - let mut ret = String::from("

    "); - for (mut content, id) in v { - write!(ret, "
  1. ").unwrap(); - let mut is_paragraph = false; - if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { - content.pop(); - is_paragraph = true; - } - html::push_html(&mut ret, content.into_iter()); - write!(ret, " ").unwrap(); - if is_paragraph { - ret.push_str("

    "); - } - ret.push_str("
  2. "); - } - ret.push_str("
"); - return Some((Event::Html(ret.into()), 0..0)); - } else { - return None; - } - } - } - } - } -} - /// A newtype that represents a relative line number in Markdown. /// /// In other words, this represents an offset from the first line of Markdown @@ -1408,7 +1334,7 @@ impl Markdown<'_> { let mut s = String::with_capacity(md.len() * 3 / 2); let p = HeadingLinks::new(p, None, ids, heading_offset); - let p = Footnotes::new(p); + let p = footnotes::Footnotes::new(p); let p = LinkReplacer::new(p.map(|(ev, _)| ev), links); let p = TableWrapper::new(p); let p = CodeBlocks::new(p, codes, edition, playground); @@ -1443,7 +1369,7 @@ impl MarkdownWithToc<'_> { { let p = HeadingLinks::new(p, Some(&mut toc), ids, HeadingOffset::H1); - let p = Footnotes::new(p); + let p = footnotes::Footnotes::new(p); let p = TableWrapper::new(p.map(|(ev, _)| ev)); let p = CodeBlocks::new(p, codes, edition, playground); html::push_html(&mut s, p); @@ -1476,7 +1402,7 @@ impl MarkdownItemInfo<'_> { let mut s = String::with_capacity(md.len() * 3 / 2); let p = HeadingLinks::new(p, None, ids, HeadingOffset::H1); - let p = Footnotes::new(p); + let p = footnotes::Footnotes::new(p); let p = TableWrapper::new(p.map(|(ev, _)| ev)); let p = p.filter(|event| { !matches!(event, Event::Start(Tag::Paragraph) | Event::End(TagEnd::Paragraph)) diff --git a/src/librustdoc/html/markdown/footnotes.rs b/src/librustdoc/html/markdown/footnotes.rs new file mode 100644 index 00000000000..47e248b891c --- /dev/null +++ b/src/librustdoc/html/markdown/footnotes.rs @@ -0,0 +1,82 @@ +//! Markdown footnote handling. +use std::fmt::Write as _; + +use pulldown_cmark::{Event, Tag, TagEnd, html}; +use rustc_data_structures::fx::FxIndexMap; + +use super::SpannedEvent; + +/// Moves all footnote definitions to the end and add back links to the +/// references. +pub(super) struct Footnotes<'a, I> { + inner: I, + footnotes: FxIndexMap>, u16)>, +} + +impl<'a, I> Footnotes<'a, I> { + pub(super) fn new(iter: I) -> Self { + Footnotes { inner: iter, footnotes: FxIndexMap::default() } + } + + fn get_entry(&mut self, key: &str) -> &mut (Vec>, u16) { + let new_id = self.footnotes.len() + 1; + let key = key.to_owned(); + self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16)) + } +} + +impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { + type Item = SpannedEvent<'a>; + + fn next(&mut self) -> Option { + loop { + match self.inner.next() { + Some((Event::FootnoteReference(ref reference), range)) => { + let entry = self.get_entry(reference); + let reference = format!( + "{0}", + (*entry).1 + ); + return Some((Event::Html(reference.into()), range)); + } + Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => { + let mut content = Vec::new(); + for (event, _) in &mut self.inner { + if let Event::End(TagEnd::FootnoteDefinition) = event { + break; + } + content.push(event); + } + let entry = self.get_entry(&def); + (*entry).0 = content; + } + Some(e) => return Some(e), + None => { + if !self.footnotes.is_empty() { + let mut v: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect(); + v.sort_by(|a, b| a.1.cmp(&b.1)); + let mut ret = String::from("

    "); + for (mut content, id) in v { + write!(ret, "
  1. ").unwrap(); + let mut is_paragraph = false; + if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { + content.pop(); + is_paragraph = true; + } + html::push_html(&mut ret, content.into_iter()); + write!(ret, " ").unwrap(); + if is_paragraph { + ret.push_str("

    "); + } + ret.push_str("
  2. "); + } + ret.push_str("
"); + return Some((Event::Html(ret.into()), 0..0)); + } else { + return None; + } + } + } + } + } +} -- cgit 1.4.1-3-g733a5 From d1fa49b2e66c343210c413b68ed57f150b7b89d8 Mon Sep 17 00:00:00 2001 From: Jalil David Salamé Messina Date: Sat, 19 Oct 2024 17:48:06 +0200 Subject: feat(rustdoc-json-types): introduce rustc-hash feature This allows the public `rustdoc-types` crate to expose this feature easily and allows consumers of the crate to get the performance advantages from doing so. The reasoning for this was discussed on [Zulip][1] Changes: - Make `rustc-hash` optional but default to including it - Rename all occurrences of `FxHashMap` to `HashMap`. - Feature gate the import and rename the imported `FxHashMap` to `HashMap` - Introduce a type alias `FxHashMap` which resolves to the currently used `HashMap` (`rustc_hash::FxHashMap` or `std::collections::HashMap`) for use in `src/librustdoc`. [1]: https://rust-lang.zulipchat.com/#narrow/channel/266220-t-rustdoc/topic/rustc-hash.20and.20performance.20of.20rustdoc-types --- src/rustdoc-json-types/Cargo.toml | 5 ++++- src/rustdoc-json-types/lib.rs | 15 ++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/rustdoc-json-types/Cargo.toml b/src/rustdoc-json-types/Cargo.toml index d3548036d4c..7f7cd3672b7 100644 --- a/src/rustdoc-json-types/Cargo.toml +++ b/src/rustdoc-json-types/Cargo.toml @@ -6,9 +6,12 @@ edition = "2021" [lib] path = "lib.rs" +[features] +default = ["rustc-hash"] + [dependencies] serde = { version = "1.0", features = ["derive"] } -rustc-hash = "1.1.0" +rustc-hash = { version = "1.1.0", optional = true } [dev-dependencies] serde_json = "1.0" diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index b0bedab495f..c4e142342a8 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -3,11 +3,16 @@ //! These types are the public API exposed through the `--output-format json` flag. The [`Crate`] //! struct is the root of the JSON blob and all other items are contained within. +#[cfg(not(feature = "rustc-hash"))] +use std::collections::HashMap; use std::path::PathBuf; -pub use rustc_hash::FxHashMap; +#[cfg(feature = "rustc-hash")] +use rustc_hash::FxHashMap as HashMap; use serde::{Deserialize, Serialize}; +pub type FxHashMap = HashMap; // re-export for use in src/librustdoc + /// The version of JSON output that this crate represents. /// /// This integer is incremented with every breaking change to the API, @@ -30,11 +35,11 @@ pub struct Crate { pub includes_private: bool, /// A collection of all items in the local crate as well as some external traits and their /// items that are referenced locally. - pub index: FxHashMap, + pub index: HashMap, /// Maps IDs to fully qualified paths and other info helpful for generating links. - pub paths: FxHashMap, + pub paths: HashMap, /// Maps `crate_id` of items to a crate name and html_root_url if it exists. - pub external_crates: FxHashMap, + pub external_crates: HashMap, /// A single version number to be used in the future when making backwards incompatible changes /// to the JSON output. pub format_version: u32, @@ -95,7 +100,7 @@ pub struct Item { /// Some("") if there is some documentation but it is empty (EG `#[doc = ""]`). pub docs: Option, /// This mapping resolves [intra-doc links](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md) from the docstring to their IDs - pub links: FxHashMap, + pub links: HashMap, /// Stringified versions of the attributes on this item (e.g. `"#[inline]"`) pub attrs: Vec, /// Information about the item’s deprecation, if present. -- cgit 1.4.1-3-g733a5 From e1d6164607d63df552ce2adf900c2c4cbf7f5ce3 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Thu, 17 Oct 2024 12:59:40 -0700 Subject: rust-analyzer: Nest `LayoutCalculatorError` in `hir_ty::LayoutError` --- src/tools/rust-analyzer/crates/hir-ty/src/layout.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs index 4cdc0db46a1..bce6a80b5b0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs @@ -72,6 +72,8 @@ pub type Variants = hir_def::layout::Variants), EmptyUnion, HasErrorConst, HasErrorType, @@ -90,6 +92,7 @@ impl std::error::Error for LayoutError {} impl fmt::Display for LayoutError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + LayoutError::BadCalc(err) => err.fallback_fmt(f), LayoutError::EmptyUnion => write!(f, "type is an union with no fields"), LayoutError::HasErrorConst => write!(f, "type contains an unevaluatable const"), LayoutError::HasErrorType => write!(f, "type contains an error"), @@ -114,11 +117,7 @@ impl fmt::Display for LayoutError { impl From> for LayoutError { fn from(err: LayoutCalculatorError) -> Self { - match err { - LayoutCalculatorError::EmptyUnion => LayoutError::EmptyUnion, - LayoutCalculatorError::UnexpectedUnsized(_) => LayoutError::UnexpectedUnsized, - LayoutCalculatorError::SizeOverflow => LayoutError::SizeOverflow, - } + LayoutError::BadCalc(err.without_payload()) } } -- cgit 1.4.1-3-g733a5 From 53974b178699468ec82452db691d646265765fab Mon Sep 17 00:00:00 2001 From: binarycat Date: Sat, 19 Oct 2024 13:11:26 -0500 Subject: compiletest: disambiguate html-tidy from rust tidy tool --- src/tools/compiletest/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index b0f87593f95..b84dd956de0 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -19,7 +19,7 @@ fn main() { let config = Arc::new(parse_config(env::args().collect())); if !config.has_tidy && config.mode == Mode::Rustdoc { - eprintln!("warning: `tidy` is not installed; diffs will not be generated"); + eprintln!("warning: `tidy` (html-tidy.org) is not installed; diffs will not be generated"); } if !config.profiler_runtime && config.mode == Mode::CoverageRun { -- cgit 1.4.1-3-g733a5 From 9435afc0fbd3f41375b676230aca6a945741dfa7 Mon Sep 17 00:00:00 2001 From: Alona Enraght-Moony Date: Sat, 19 Oct 2024 19:24:54 +0000 Subject: rustdoc: Refractor footnote handling --- src/librustdoc/html/markdown/footnotes.rs | 103 +++++++++++++++++++----------- 1 file changed, 67 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/librustdoc/html/markdown/footnotes.rs b/src/librustdoc/html/markdown/footnotes.rs index 47e248b891c..3f0e586b8e3 100644 --- a/src/librustdoc/html/markdown/footnotes.rs +++ b/src/librustdoc/html/markdown/footnotes.rs @@ -10,7 +10,14 @@ use super::SpannedEvent; /// references. pub(super) struct Footnotes<'a, I> { inner: I, - footnotes: FxIndexMap>, u16)>, + footnotes: FxIndexMap>, +} + +/// The definition of a single footnote. +struct FootnoteDef<'a> { + content: Vec>, + /// The number that appears in the footnote reference and list. + id: u16, } impl<'a, I> Footnotes<'a, I> { @@ -18,10 +25,15 @@ impl<'a, I> Footnotes<'a, I> { Footnotes { inner: iter, footnotes: FxIndexMap::default() } } - fn get_entry(&mut self, key: &str) -> &mut (Vec>, u16) { + fn get_entry(&mut self, key: &str) -> (&mut Vec>, u16) { let new_id = self.footnotes.len() + 1; let key = key.to_owned(); - self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16)) + let FootnoteDef { content, id } = self + .footnotes + .entry(key) + .or_insert(FootnoteDef { content: Vec::new(), id: new_id as u16 }); + // Don't allow changing the ID of existing entrys, but allow changing the contents. + (content, *id) } } @@ -32,46 +44,28 @@ impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { loop { match self.inner.next() { Some((Event::FootnoteReference(ref reference), range)) => { - let entry = self.get_entry(reference); - let reference = format!( - "{0}", - (*entry).1 - ); + // When we see a reference (to a footnote we may not know) the definition of, + // reserve a number for it, and emit a link to that number. + let (_, id) = self.get_entry(reference); + let reference = + format!("{0}", id); return Some((Event::Html(reference.into()), range)); } Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => { - let mut content = Vec::new(); - for (event, _) in &mut self.inner { - if let Event::End(TagEnd::FootnoteDefinition) = event { - break; - } - content.push(event); - } - let entry = self.get_entry(&def); - (*entry).0 = content; + // When we see a footnote definition, collect the assocated content, and store + // that for rendering later. + let content = collect_footnote_def(&mut self.inner); + let (entry_content, _) = self.get_entry(&def); + *entry_content = content; } Some(e) => return Some(e), None => { if !self.footnotes.is_empty() { - let mut v: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect(); - v.sort_by(|a, b| a.1.cmp(&b.1)); - let mut ret = String::from("

    "); - for (mut content, id) in v { - write!(ret, "
  1. ").unwrap(); - let mut is_paragraph = false; - if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { - content.pop(); - is_paragraph = true; - } - html::push_html(&mut ret, content.into_iter()); - write!(ret, " ").unwrap(); - if is_paragraph { - ret.push_str("

    "); - } - ret.push_str("
  2. "); - } - ret.push_str("
"); - return Some((Event::Html(ret.into()), 0..0)); + // After all the markdown is emmited, emit an
then all the footnotes + // in a list. + let defs: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect(); + let defs_html = render_footnotes_defs(defs); + return Some((Event::Html(defs_html.into()), 0..0)); } else { return None; } @@ -80,3 +74,40 @@ impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { } } } + +fn collect_footnote_def<'a>(events: impl Iterator>) -> Vec> { + let mut content = Vec::new(); + for (event, _) in events { + if let Event::End(TagEnd::FootnoteDefinition) = event { + break; + } + content.push(event); + } + content +} + +fn render_footnotes_defs(mut footnotes: Vec>) -> String { + let mut ret = String::from("

    "); + + // Footnotes must listed in order of id, so the numbers the + // browser generated for
  1. are right. + footnotes.sort_by_key(|x| x.id); + + for FootnoteDef { mut content, id } in footnotes { + write!(ret, "
  2. ").unwrap(); + let mut is_paragraph = false; + if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { + content.pop(); + is_paragraph = true; + } + html::push_html(&mut ret, content.into_iter()); + write!(ret, " ").unwrap(); + if is_paragraph { + ret.push_str("

    "); + } + ret.push_str("
  3. "); + } + ret.push_str("
"); + + ret +} -- cgit 1.4.1-3-g733a5 From 3e67fbda5e53da5b21ceb22727437351df579247 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 20 Oct 2024 04:46:29 +0200 Subject: Remove unused `recoverable` argument in collect_intra_doc_links --- src/librustdoc/passes/collect_intra_doc_links.rs | 30 +++++++----------------- 1 file changed, 9 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index cbc6e351fac..2f0ea8d618c 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1091,7 +1091,6 @@ impl LinkCollector<'_, '_> { // resolutions are cached, for other links we want to report an error every // time so they are not cached. matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut), - false, )?; if resolved.len() > 1 { @@ -1404,9 +1403,6 @@ impl LinkCollector<'_, '_> { // If errors are cached then they are only reported on first occurrence // which we want in some cases but not in others. cache_errors: bool, - // If this call is intended to be recoverable, then pass true to silence. - // This is only recoverable when path is failed to resolved. - recoverable: bool, ) -> Option)>> { if let Some(res) = self.visited_links.get(&key) { if res.is_some() || cache_errors { @@ -1414,7 +1410,7 @@ impl LinkCollector<'_, '_> { } } - let mut candidates = self.resolve_with_disambiguator(&key, diag.clone(), recoverable); + let mut candidates = self.resolve_with_disambiguator(&key, diag.clone()); // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether. // However I'm not sure how to check that across crates. @@ -1463,14 +1459,10 @@ impl LinkCollector<'_, '_> { } /// After parsing the disambiguator, resolve the main part of the link. - // FIXME(jynelson): wow this is just so much fn resolve_with_disambiguator( &mut self, key: &ResolutionInfo, diag: DiagnosticInfo<'_>, - // If this call is intended to be recoverable, then pass true to silence. - // This is only recoverable when path is failed to resolved. - recoverable: bool, ) -> Vec<(Res, Option)> { let disambiguator = key.dis; let path_str = &key.path_str; @@ -1500,9 +1492,7 @@ impl LinkCollector<'_, '_> { } } } - if !recoverable { - resolution_failure(self, diag, path_str, disambiguator, smallvec![err]); - } + resolution_failure(self, diag, path_str, disambiguator, smallvec![err]); return vec![]; } } @@ -1539,15 +1529,13 @@ impl LinkCollector<'_, '_> { .fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc }); if len == 0 { - if !recoverable { - resolution_failure( - self, - diag, - path_str, - disambiguator, - candidates.into_iter().filter_map(|res| res.err()).collect(), - ); - } + resolution_failure( + self, + diag, + path_str, + disambiguator, + candidates.into_iter().filter_map(|res| res.err()).collect(), + ); return vec![]; } else if len == 1 { candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::>() -- cgit 1.4.1-3-g733a5 From ea2ac892bd89b12cedde4071f2f5f9f6702be5c9 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sun, 20 Oct 2024 04:55:19 +0000 Subject: Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index d0f9fa7ac42..778061e1310 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -dd5127615ad626741a1116d022cf784637ac05df +54791efd8235805dcfbdad3b8788e08f2142c50b -- cgit 1.4.1-3-g733a5 From e32a5be3b0beb84a32b952e91b3acc5f14c1d40b Mon Sep 17 00:00:00 2001 From: "许杰友 Jieyou Xu (Joe)" <39484203+jieyouxu@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:36:46 +0800 Subject: compiletest: disambiguate between `tidy` and `tidy` (html version) --- src/tools/compiletest/src/common.rs | 4 ++-- src/tools/compiletest/src/lib.rs | 6 +++--- src/tools/compiletest/src/main.rs | 2 +- src/tools/compiletest/src/runtest.rs | 4 +--- 4 files changed, 7 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 1ee00a3a4e8..ff059940f7c 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -338,8 +338,8 @@ pub struct Config { /// created in `//rustfix_missing_coverage.txt` pub rustfix_coverage: bool, - /// whether to run `tidy` when a rustdoc test fails - pub has_tidy: bool, + /// whether to run `tidy` (html-tidy) when a rustdoc test fails + pub has_html_tidy: bool, /// whether to run `enzyme` autodiff tests pub has_enzyme: bool, diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index d045c6fe053..7d6ede9bcda 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -230,14 +230,14 @@ pub fn parse_config(args: Vec) -> Config { let run_ignored = matches.opt_present("ignored"); let with_debug_assertions = matches.opt_present("with-debug-assertions"); let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode"); - let has_tidy = if mode == Mode::Rustdoc { + let has_html_tidy = if mode == Mode::Rustdoc { Command::new("tidy") .arg("--version") .stdout(Stdio::null()) .status() .map_or(false, |status| status.success()) } else { - // Avoid spawning an external command when we know tidy won't be used. + // Avoid spawning an external command when we know html-tidy won't be used. false }; let has_enzyme = matches.opt_present("has-enzyme"); @@ -336,7 +336,7 @@ pub fn parse_config(args: Vec) -> Config { .opt_str("compare-mode") .map(|s| s.parse().expect("invalid --compare-mode provided")), rustfix_coverage: matches.opt_present("rustfix-coverage"), - has_tidy, + has_html_tidy, has_enzyme, channel: matches.opt_str("channel").unwrap(), git_hash: matches.opt_present("git-hash"), diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index b84dd956de0..2f0c7d8ddc5 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -18,7 +18,7 @@ fn main() { let config = Arc::new(parse_config(env::args().collect())); - if !config.has_tidy && config.mode == Mode::Rustdoc { + if !config.has_html_tidy && config.mode == Mode::Rustdoc { eprintln!("warning: `tidy` (html-tidy.org) is not installed; diffs will not be generated"); } diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 5b8a96a54c2..36c5106ddad 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1,5 +1,3 @@ -// ignore-tidy-filelength - use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::ffi::OsString; @@ -1897,7 +1895,7 @@ impl<'test> TestCx<'test> { } fn compare_to_default_rustdoc(&mut self, out_dir: &Path) { - if !self.config.has_tidy { + if !self.config.has_html_tidy { return; } println!("info: generating a diff against nightly rustdoc"); -- cgit 1.4.1-3-g733a5 From ccd680ee01564d32cad783a94c4dcccf228922c1 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sun, 20 Oct 2024 05:04:32 +0000 Subject: fmt --- src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs | 6 +++++- src/tools/miri/tests/pass/dyn-upcast.rs | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index 1684abeec6b..5624c4c479e 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -424,7 +424,11 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { } #[inline(never)] // This is only called on fatal code paths - pub(super) fn protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpErrorKind<'tcx> { + pub(super) fn protector_error( + &self, + item: &Item, + kind: ProtectorKind, + ) -> InterpErrorKind<'tcx> { let protected = match kind { ProtectorKind::WeakProtector => "weakly protected", ProtectorKind::StrongProtector => "strongly protected", diff --git a/src/tools/miri/tests/pass/dyn-upcast.rs b/src/tools/miri/tests/pass/dyn-upcast.rs index 306e9ab9c67..61410f7c4e0 100644 --- a/src/tools/miri/tests/pass/dyn-upcast.rs +++ b/src/tools/miri/tests/pass/dyn-upcast.rs @@ -433,7 +433,8 @@ fn replace_vptr() { } fn drop_principal() { - use std::{alloc::Layout, any::Any}; + use std::alloc::Layout; + use std::any::Any; const fn yeet_principal(x: Box) -> Box { x -- cgit 1.4.1-3-g733a5