diff options
| author | Ralf Jung <post@ralfj.de> | 2024-10-14 17:04:43 +0200 |
|---|---|---|
| committer | Ralf Jung <post@ralfj.de> | 2024-10-14 17:04:43 +0200 |
| commit | 9d579f5358d6722fa20cbf243e201defbd5d84b3 (patch) | |
| tree | 456828aefcdd5ca1414414fdb876f8c528d82dff /src | |
| parent | 5e6170b97f81b83041666170ceeadefe04d00fb4 (diff) | |
| parent | 17a19e684cdf3ca088af8b4da6a6209d128913f4 (diff) | |
Merge from rustc
Diffstat (limited to 'src')
267 files changed, 3778 insertions, 2656 deletions
diff --git a/src/bootstrap/README.md b/src/bootstrap/README.md index 0ac58645d2d..f036603ee70 100644 --- a/src/bootstrap/README.md +++ b/src/bootstrap/README.md @@ -182,10 +182,8 @@ Some general areas that you may be interested in modifying are: `Config` struct. * Adding a sanity check? Take a look at `bootstrap/src/core/sanity.rs`. -If you make a major change on bootstrap configuration, please remember to: - -+ Update `CONFIG_CHANGE_HISTORY` in `src/bootstrap/src/utils/change_tracker.rs`. -* Update `change-id = {pull-request-id}` in `config.example.toml`. +If you make a major change on bootstrap configuration, please add a new entry to +`CONFIG_CHANGE_HISTORY` in `src/bootstrap/src/utils/change_tracker.rs`. A 'major change' includes diff --git a/src/bootstrap/src/bin/main.rs b/src/bootstrap/src/bin/main.rs index b9df7336cca..e9ec79e417b 100644 --- a/src/bootstrap/src/bin/main.rs +++ b/src/bootstrap/src/bin/main.rs @@ -14,6 +14,7 @@ use bootstrap::{ Build, CONFIG_CHANGE_HISTORY, Config, Flags, Subcommand, find_recent_config_change_ids, human_readable_changes, t, }; +use build_helper::ci::CiEnv; fn main() { let args = env::args().skip(1).collect::<Vec<_>>(); @@ -54,9 +55,12 @@ fn main() { }; } - // check_version warnings are not printed during setup - let changelog_suggestion = - if matches!(config.cmd, Subcommand::Setup { .. }) { None } else { check_version(&config) }; + // check_version warnings are not printed during setup, or during CI + let changelog_suggestion = if matches!(config.cmd, Subcommand::Setup { .. }) || CiEnv::is_ci() { + None + } else { + check_version(&config) + }; // NOTE: Since `./configure` generates a `config.toml`, distro maintainers will see the // changelog warning, not the `x.py setup` message. diff --git a/src/bootstrap/src/bin/rustc.rs b/src/bootstrap/src/bin/rustc.rs index d04e2fbeb78..18f5a1a58db 100644 --- a/src/bootstrap/src/bin/rustc.rs +++ b/src/bootstrap/src/bin/rustc.rs @@ -95,7 +95,6 @@ fn main() { // When statically linking `std` into `rustc_driver`, remove `-C prefer-dynamic` if env::var("RUSTC_LINK_STD_INTO_RUSTC_DRIVER").unwrap() == "1" && crate_name == Some("rustc_driver") - && stage != "0" { if let Some(pos) = args.iter().enumerate().position(|(i, a)| { a == "-C" && args.get(i + 1).map(|a| a == "prefer-dynamic").unwrap_or(false) @@ -137,6 +136,12 @@ fn main() { cmd.args(lint_flags.split_whitespace()); } + // Conditionally pass `-Zon-broken-pipe=kill` to underlying rustc. Not all binaries want + // `-Zon-broken-pipe=kill`, which includes cargo itself. + if env::var_os("FORCE_ON_BROKEN_PIPE_KILL").is_some() { + cmd.arg("-Z").arg("on-broken-pipe=kill"); + } + if target.is_some() { // The stage0 compiler has a special sysroot distinct from what we // actually downloaded, so we just always pass the `--sysroot` option, diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 7671fc7e013..f419bebdc12 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -398,7 +398,14 @@ impl Step for RustAnalyzer { } macro_rules! tool_check_step { - ($name:ident, $path:literal, $($alias:literal, )* $source_type:path $(, $default:literal )?) => { + ( + $name:ident, + $display_name:literal, + $path:literal, + $($alias:literal, )* + $source_type:path + $(, $default:literal )? + ) => { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct $name { pub target: TargetSelection, @@ -441,7 +448,7 @@ macro_rules! tool_check_step { cargo.arg("--all-targets"); } - let _guard = builder.msg_check(&concat!(stringify!($name), " artifacts").to_lowercase(), target); + let _guard = builder.msg_check(&format!("{} artifacts", $display_name), target); run_cargo( builder, cargo, @@ -468,20 +475,30 @@ macro_rules! tool_check_step { }; } -tool_check_step!(Rustdoc, "src/tools/rustdoc", "src/librustdoc", SourceType::InTree); +tool_check_step!(Rustdoc, "rustdoc", "src/tools/rustdoc", "src/librustdoc", SourceType::InTree); // Clippy, miri and Rustfmt are hybrids. They are external tools, but use a git subtree instead // of a submodule. Since the SourceType only drives the deny-warnings // behavior, treat it as in-tree so that any new warnings in clippy will be // rejected. -tool_check_step!(Clippy, "src/tools/clippy", SourceType::InTree); -tool_check_step!(Miri, "src/tools/miri", SourceType::InTree); -tool_check_step!(CargoMiri, "src/tools/miri/cargo-miri", SourceType::InTree); -tool_check_step!(Rls, "src/tools/rls", SourceType::InTree); -tool_check_step!(Rustfmt, "src/tools/rustfmt", SourceType::InTree); -tool_check_step!(MiroptTestTools, "src/tools/miropt-test-tools", SourceType::InTree); -tool_check_step!(TestFloatParse, "src/etc/test-float-parse", SourceType::InTree); - -tool_check_step!(Bootstrap, "src/bootstrap", SourceType::InTree, false); +tool_check_step!(Clippy, "clippy", "src/tools/clippy", SourceType::InTree); +tool_check_step!(Miri, "miri", "src/tools/miri", SourceType::InTree); +tool_check_step!(CargoMiri, "cargo-miri", "src/tools/miri/cargo-miri", SourceType::InTree); +tool_check_step!(Rls, "rls", "src/tools/rls", SourceType::InTree); +tool_check_step!(Rustfmt, "rustfmt", "src/tools/rustfmt", SourceType::InTree); +tool_check_step!( + MiroptTestTools, + "miropt-test-tools", + "src/tools/miropt-test-tools", + SourceType::InTree +); +tool_check_step!( + TestFloatParse, + "test-float-parse", + "src/etc/test-float-parse", + SourceType::InTree +); + +tool_check_step!(Bootstrap, "bootstrap", "src/bootstrap", SourceType::InTree, false); /// Cargo's output path for the standard library in a given stage, compiled /// by a particular compiler for the specified target. diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index eaa982d4e2b..27bbc8bd8ff 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1053,8 +1053,19 @@ pub fn rustc_cargo( cargo.rustdocflag("-Zcrate-attr=warn(rust_2018_idioms)"); - // If the rustc output is piped to e.g. `head -n1` we want the process to be - // killed, rather than having an error bubble up and cause a panic. + // If the rustc output is piped to e.g. `head -n1` we want the process to be killed, rather than + // having an error bubble up and cause a panic. + // + // FIXME(jieyouxu): this flag is load-bearing for rustc to not ICE on broken pipes, because + // rustc internally sometimes uses std `println!` -- but std `println!` by default will panic on + // broken pipes, and uncaught panics will manifest as an ICE. The compiler *should* handle this + // properly, but this flag is set in the meantime to paper over the I/O errors. + // + // See <https://github.com/rust-lang/rust/issues/131059> for details. + // + // Also see the discussion for properly handling I/O errors related to broken pipes, i.e. safe + // variants of `println!` in + // <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Internal.20lint.20for.20raw.20.60print!.60.20and.20.60println!.60.3F>. cargo.rustflag("-Zon-broken-pipe=kill"); if builder.config.llvm_enzyme { @@ -1923,8 +1934,24 @@ impl Step for Assemble { let src_libdir = builder.sysroot_libdir(build_compiler, host); for f in builder.read_dir(&src_libdir) { let filename = f.file_name().into_string().unwrap(); - if (is_dylib(&filename) || is_debug_info(&filename)) && !proc_macros.contains(&filename) + + let is_proc_macro = proc_macros.contains(&filename); + let is_dylib_or_debug = is_dylib(&filename) || is_debug_info(&filename); + + // If we link statically to stdlib, do not copy the libstd dynamic library file + // FIXME: Also do this for Windows once incremental post-optimization stage0 tests + // work without std.dll (see https://github.com/rust-lang/rust/pull/131188). + let can_be_rustc_dynamic_dep = if builder + .link_std_into_rustc_driver(target_compiler.host) + && !target_compiler.host.is_windows() { + let is_std = filename.starts_with("std-") || filename.starts_with("libstd-"); + !is_std + } else { + true + }; + + if is_dylib_or_debug && can_be_rustc_dynamic_dep && !is_proc_macro { builder.copy_link(&f.path(), &rustc_libdir.join(&filename)); } } diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 3d504c3771f..ca2b8742647 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -82,7 +82,7 @@ book!( EditionGuide, "src/doc/edition-guide", "edition-guide", &[], submodule; EmbeddedBook, "src/doc/embedded-book", "embedded-book", &[], submodule; Nomicon, "src/doc/nomicon", "nomicon", &[], submodule; - RustByExample, "src/doc/rust-by-example", "rust-by-example", &["ja"], submodule; + RustByExample, "src/doc/rust-by-example", "rust-by-example", &["ja", "zh"], submodule; RustdocBook, "src/doc/rustdoc", "rustdoc", &[]; StyleGuide, "src/doc/style-guide", "style-guide", &[]; ); @@ -718,6 +718,10 @@ fn doc_std( .arg("--target-dir") .arg(&*target_dir.to_string_lossy()) .arg("-Zskip-rustdoc-fingerprint") + .arg("-Zrustdoc-map") + .rustdocflag("--extern-html-root-url") + .rustdocflag("std_detect=https://docs.rs/std_detect/latest/") + .rustdocflag("--extern-html-root-takes-precedence") .rustdocflag("--resource-suffix") .rustdocflag(&builder.version); for arg in extra_args { diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs index 952d8d73328..5ca4321d855 100644 --- a/src/bootstrap/src/core/build_steps/format.rs +++ b/src/bootstrap/src/core/build_steps/format.rs @@ -93,6 +93,7 @@ fn get_modified_rs_files(build: &Builder<'_>) -> Result<Option<Vec<String>>, Str if !verify_rustfmt_version(build) { return Ok(None); } + get_git_modified_files(&build.config.git_config(), Some(&build.config.src), &["rs"]) } diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 30213584157..a2d40f6fbd8 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -20,10 +20,9 @@ use build_helper::git::get_closest_merge_commit; use crate::core::builder::{Builder, RunConfig, ShouldRun, Step}; use crate::core::config::{Config, TargetSelection}; -use crate::utils::channel; use crate::utils::exec::command; use crate::utils::helpers::{ - self, HashStamp, exe, get_clang_cl_resource_dir, output, t, unhashed_basename, up_to_date, + self, HashStamp, exe, get_clang_cl_resource_dir, t, unhashed_basename, up_to_date, }; use crate::{CLang, GitRepo, Kind, generate_smart_stamp_hash}; @@ -166,7 +165,7 @@ pub(crate) fn detect_llvm_sha(config: &Config, is_git: bool) -> String { config.src.join("src/version"), ]) .unwrap() - } else if let Some(info) = channel::read_commit_info_file(&config.src) { + } else if let Some(info) = crate::utils::channel::read_commit_info_file(&config.src) { info.sha.trim().to_owned() } else { "".to_owned() @@ -242,15 +241,29 @@ pub(crate) fn is_ci_llvm_available(config: &Config, asserts: bool) -> bool { /// Returns true if we're running in CI with modified LLVM (and thus can't download it) pub(crate) fn is_ci_llvm_modified(config: &Config) -> bool { - CiEnv::is_rust_lang_managed_ci_job() && config.rust_info.is_managed_git_subrepository() && { - // We assume we have access to git, so it's okay to unconditionally pass - // `true` here. - let llvm_sha = detect_llvm_sha(config, true); - let head_sha = - output(helpers::git(Some(&config.src)).arg("rev-parse").arg("HEAD").as_command_mut()); - let head_sha = head_sha.trim(); - llvm_sha == head_sha + // If not running in a CI environment, return false. + if !CiEnv::is_ci() { + return false; + } + + // In rust-lang/rust managed CI, assert the existence of the LLVM submodule. + if CiEnv::is_rust_lang_managed_ci_job() { + assert!( + config.in_tree_llvm_info.is_managed_git_subrepository(), + "LLVM submodule must be fetched in rust-lang/rust managed CI builders." + ); } + // If LLVM submodule isn't present, skip the change check as it won't work. + else if !config.in_tree_llvm_info.is_managed_git_subrepository() { + return false; + } + + let llvm_sha = detect_llvm_sha(config, true); + let head_sha = crate::output( + helpers::git(Some(&config.src)).arg("rev-parse").arg("HEAD").as_command_mut(), + ); + let head_sha = head_sha.trim(); + llvm_sha == head_sha } #[derive(Debug, Clone, Hash, PartialEq, Eq)] @@ -1215,6 +1228,9 @@ fn supported_sanitizers( "aarch64-unknown-linux-ohos" => { common_libs("linux", "aarch64", &["asan", "lsan", "msan", "tsan", "hwasan"]) } + "loongarch64-unknown-linux-gnu" | "loongarch64-unknown-linux-musl" => { + common_libs("linux", "loongarch64", &["asan", "lsan", "msan", "tsan"]) + } "x86_64-apple-darwin" => darwin_libs("osx", &["asan", "lsan", "tsan"]), "x86_64-unknown-fuchsia" => common_libs("fuchsia", "x86_64", &["asan"]), "x86_64-apple-ios" => darwin_libs("iossim", &["asan", "tsan"]), diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index 0ee2cb451f3..51964977933 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -35,21 +35,6 @@ pub enum Profile { static PROFILE_DIR: &str = "src/bootstrap/defaults"; -/// A list of historical hashes of `src/etc/rust_analyzer_settings.json`. -/// New entries should be appended whenever this is updated so we can detect -/// outdated vs. user-modified settings files. -static SETTINGS_HASHES: &[&str] = &[ - "ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8", - "56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922", - "af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0", - "3468fea433c25fff60be6b71e8a215a732a7b1268b6a83bf10d024344e140541", - "47d227f424bf889b0d899b9cc992d5695e1b78c406e183cd78eafefbe5488923", - "b526bd58d0262dd4dda2bff5bc5515b705fb668a46235ace3e057f807963a11a", - "828666b021d837a33e78d870b56d34c88a5e2c85de58b693607ec574f0c27000", - "811fb3b063c739d261fd8590dd30242e117908f5a095d594fa04585daa18ec4d", -]; -static RUST_ANALYZER_SETTINGS: &str = include_str!("../../../../etc/rust_analyzer_settings.json"); - impl Profile { fn include_path(&self, src_path: &Path) -> PathBuf { PathBuf::from(format!("{}/{PROFILE_DIR}/config.{}.toml", src_path.display(), self)) @@ -533,46 +518,162 @@ undesirable, simply delete the `pre-push` file from .git/hooks." Ok(()) } -/// Sets up or displays `src/etc/rust_analyzer_settings.json` +/// Handles editor-specific setup differences +#[derive(Clone, Debug, Eq, PartialEq)] +enum EditorKind { + Vscode, + Vim, + Emacs, + Helix, +} + +impl EditorKind { + fn prompt_user() -> io::Result<Option<EditorKind>> { + let prompt_str = "Available editors: +1. vscode +2. vim +3. emacs +4. helix + +Select which editor you would like to set up [default: None]: "; + + let mut input = String::new(); + loop { + print!("{}", prompt_str); + io::stdout().flush()?; + input.clear(); + io::stdin().read_line(&mut input)?; + match input.trim().to_lowercase().as_str() { + "1" | "vscode" => return Ok(Some(EditorKind::Vscode)), + "2" | "vim" => return Ok(Some(EditorKind::Vim)), + "3" | "emacs" => return Ok(Some(EditorKind::Emacs)), + "4" | "helix" => return Ok(Some(EditorKind::Helix)), + "" => return Ok(None), + _ => { + eprintln!("ERROR: unrecognized option '{}'", input.trim()); + eprintln!("NOTE: press Ctrl+C to exit"); + } + }; + } + } + + /// A list of historical hashes of each LSP settings file + /// New entries should be appended whenever this is updated so we can detect + /// outdated vs. user-modified settings files. + fn hashes(&self) -> Vec<&str> { + match self { + EditorKind::Vscode | EditorKind::Vim => vec![ + "ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8", + "56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922", + "af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0", + "3468fea433c25fff60be6b71e8a215a732a7b1268b6a83bf10d024344e140541", + "47d227f424bf889b0d899b9cc992d5695e1b78c406e183cd78eafefbe5488923", + "b526bd58d0262dd4dda2bff5bc5515b705fb668a46235ace3e057f807963a11a", + "828666b021d837a33e78d870b56d34c88a5e2c85de58b693607ec574f0c27000", + "811fb3b063c739d261fd8590dd30242e117908f5a095d594fa04585daa18ec4d", + ], + EditorKind::Emacs => vec![ + "51068d4747a13732440d1a8b8f432603badb1864fa431d83d0fd4f8fa57039e0", + "d29af4d949bbe2371eac928a3c31cf9496b1701aa1c45f11cd6c759865ad5c45", + ], + EditorKind::Helix => { + vec!["2d3069b8cf1b977e5d4023965eb6199597755e6c96c185ed5f2854f98b83d233"] + } + } + } + + fn settings_path(&self, config: &Config) -> PathBuf { + config.src.join(self.settings_short_path()) + } + + fn settings_short_path(&self) -> PathBuf { + self.settings_folder().join(match self { + EditorKind::Vscode => "settings.json", + EditorKind::Vim => "coc-settings.json", + EditorKind::Emacs => ".dir-locals.el", + EditorKind::Helix => "languages.toml", + }) + } + + fn settings_folder(&self) -> PathBuf { + match self { + EditorKind::Vscode => PathBuf::from(".vscode"), + EditorKind::Vim => PathBuf::from(".vim"), + EditorKind::Emacs => PathBuf::new(), + EditorKind::Helix => PathBuf::from(".helix"), + } + } + + fn settings_template(&self) -> &str { + match self { + EditorKind::Vscode | EditorKind::Vim => { + include_str!("../../../../etc/rust_analyzer_settings.json") + } + EditorKind::Emacs => include_str!("../../../../etc/rust_analyzer_eglot.el"), + EditorKind::Helix => include_str!("../../../../etc/rust_analyzer_helix.toml"), + } + } + + fn backup_extension(&self) -> String { + format!("{}.bak", self.settings_short_path().extension().unwrap().to_str().unwrap()) + } +} + +/// Sets up or displays the LSP config for one of the supported editors #[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct Vscode; +pub struct Editor; -impl Step for Vscode { +impl Step for Editor { type Output = (); const DEFAULT: bool = true; + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.alias("vscode") + run.alias("editor") } + fn make_run(run: RunConfig<'_>) { if run.builder.config.dry_run() { return; } if let [cmd] = &run.paths[..] { - if cmd.assert_single_path().path.as_path().as_os_str() == "vscode" { - run.builder.ensure(Vscode); + if cmd.assert_single_path().path.as_path().as_os_str() == "editor" { + run.builder.ensure(Editor); } } } + fn run(self, builder: &Builder<'_>) -> Self::Output { let config = &builder.config; if config.dry_run() { return; } - while !t!(create_vscode_settings_maybe(config)) {} + match EditorKind::prompt_user() { + Ok(editor_kind) => { + if let Some(editor_kind) = editor_kind { + while !t!(create_editor_settings_maybe(config, editor_kind.clone())) {} + } else { + println!("Ok, skipping editor setup!"); + } + } + Err(e) => eprintln!("Could not determine the editor: {e}"), + } } } -/// Create a `.vscode/settings.json` file for rustc development, or just print it +/// Create the recommended editor LSP config file for rustc development, or just print it /// If this method should be re-called, it returns `false`. -fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> { - let (current_hash, historical_hashes) = SETTINGS_HASHES.split_last().unwrap(); - let vscode_settings = config.src.join(".vscode").join("settings.json"); - // If None, no settings.json exists +fn create_editor_settings_maybe(config: &Config, editor: EditorKind) -> io::Result<bool> { + let hashes = editor.hashes(); + let (current_hash, historical_hashes) = hashes.split_last().unwrap(); + let settings_path = editor.settings_path(config); + let settings_short_path = editor.settings_short_path(); + let settings_filename = settings_short_path.to_str().unwrap(); + // If None, no settings file exists // If Some(true), is a previous version of settings.json // If Some(false), is not a previous version (i.e. user modified) // If it's up to date we can just skip this let mut mismatched_settings = None; - if let Ok(current) = fs::read_to_string(&vscode_settings) { + if let Ok(current) = fs::read_to_string(&settings_path) { let mut hasher = sha2::Sha256::new(); hasher.update(¤t); let hash = hex_encode(hasher.finalize().as_slice()); @@ -585,20 +686,21 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> { } } println!( - "\nx.py can automatically install the recommended `.vscode/settings.json` file for rustc development" + "\nx.py can automatically install the recommended `{settings_filename}` file for rustc development" ); + match mismatched_settings { - Some(true) => eprintln!( - "WARNING: existing `.vscode/settings.json` is out of date, x.py will update it" - ), + Some(true) => { + eprintln!("WARNING: existing `{settings_filename}` is out of date, x.py will update it") + } Some(false) => eprintln!( - "WARNING: existing `.vscode/settings.json` has been modified by user, x.py will back it up and replace it" + "WARNING: existing `{settings_filename}` has been modified by user, x.py will back it up and replace it" ), _ => (), } - let should_create = match prompt_user( - "Would you like to create/update settings.json? (Press 'p' to preview values): [y/N]", - )? { + let should_create = match prompt_user(&format!( + "Would you like to create/update `{settings_filename}`? (Press 'p' to preview values): [y/N]" + ))? { Some(PromptResult::Yes) => true, Some(PromptResult::Print) => false, _ => { @@ -607,9 +709,9 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> { } }; if should_create { - let path = config.src.join(".vscode"); - if !path.exists() { - fs::create_dir(&path)?; + let settings_folder_path = config.src.join(editor.settings_folder()); + if !settings_folder_path.exists() { + fs::create_dir(settings_folder_path)?; } let verb = match mismatched_settings { // exists but outdated, we can replace this @@ -617,18 +719,21 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> { // exists but user modified, back it up Some(false) => { // exists and is not current version or outdated, so back it up - let mut backup = vscode_settings.clone(); - backup.set_extension("json.bak"); - eprintln!("WARNING: copying `settings.json` to `settings.json.bak`"); - fs::copy(&vscode_settings, &backup)?; + let backup = settings_path.clone().with_extension(editor.backup_extension()); + eprintln!( + "WARNING: copying `{}` to `{}`", + settings_path.file_name().unwrap().to_str().unwrap(), + backup.file_name().unwrap().to_str().unwrap(), + ); + fs::copy(&settings_path, &backup)?; "Updated" } _ => "Created", }; - fs::write(&vscode_settings, RUST_ANALYZER_SETTINGS)?; - println!("{verb} `.vscode/settings.json`"); + fs::write(&settings_path, editor.settings_template())?; + println!("{verb} `{}`", settings_filename); } else { - println!("\n{RUST_ANALYZER_SETTINGS}"); + println!("\n{}", editor.settings_template()); } Ok(should_create) } diff --git a/src/bootstrap/src/core/build_steps/setup/tests.rs b/src/bootstrap/src/core/build_steps/setup/tests.rs index 3552224f33b..f3d4b6aa4db 100644 --- a/src/bootstrap/src/core/build_steps/setup/tests.rs +++ b/src/bootstrap/src/core/build_steps/setup/tests.rs @@ -1,16 +1,17 @@ use sha2::Digest; -use super::{RUST_ANALYZER_SETTINGS, SETTINGS_HASHES}; +use super::EditorKind; use crate::utils::helpers::hex_encode; #[test] fn check_matching_settings_hash() { + let editor = EditorKind::Vscode; let mut hasher = sha2::Sha256::new(); - hasher.update(&RUST_ANALYZER_SETTINGS); + hasher.update(&editor.settings_template()); let hash = hex_encode(hasher.finalize().as_slice()); assert_eq!( &hash, - SETTINGS_HASHES.last().unwrap(), - "Update `SETTINGS_HASHES` with the new hash of `src/etc/rust_analyzer_settings.json`" + editor.hashes().last().unwrap(), + "Update `EditorKind::hashes()` with the new hash of `src/etc/rust_analyzer_settings.json`" ); } diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 7283b0e9574..e25b571acba 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -1394,12 +1394,6 @@ default_test!(Ui { path: "tests/ui", mode: "ui", suite: "ui" }); default_test!(Crashes { path: "tests/crashes", mode: "crashes", suite: "crashes" }); -default_test!(RunPassValgrind { - path: "tests/run-pass-valgrind", - mode: "run-pass-valgrind", - suite: "run-pass-valgrind" -}); - default_test!(Codegen { path: "tests/codegen", mode: "codegen", suite: "codegen" }); default_test!(CodegenUnits { @@ -1703,7 +1697,11 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the builder.ensure(TestHelpers { target: compiler.host }); // ensure that `libproc_macro` is available on the host. - builder.ensure(compile::Std::new(compiler, compiler.host)); + if suite == "mir-opt" { + builder.ensure(compile::Std::new_for_mir_opt_tests(compiler, compiler.host)); + } else { + builder.ensure(compile::Std::new(compiler, compiler.host)); + } // As well as the target if suite != "mir-opt" { @@ -2088,7 +2086,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the } if builder.config.profiler_enabled(target) { - cmd.arg("--profiler-support"); + cmd.arg("--profiler-runtime"); } cmd.env("RUST_TEST_TMPDIR", builder.tempdir()); diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index 64dfe054d9c..a01497c2bb9 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -209,11 +209,28 @@ pub fn prepare_tool_cargo( // See https://github.com/rust-lang/rust/issues/116538 cargo.rustflag("-Zunstable-options"); - // `-Zon-broken-pipe=kill` breaks cargo tests + // NOTE: The root cause of needing `-Zon-broken-pipe=kill` in the first place is because `rustc` + // and `rustdoc` doesn't gracefully handle I/O errors due to usages of raw std `println!` macros + // which panics upon encountering broken pipes. `-Zon-broken-pipe=kill` just papers over that + // and stops rustc/rustdoc ICEing on e.g. `rustc --print=sysroot | false`. + // + // cargo explicitly does not want the `-Zon-broken-pipe=kill` paper because it does actually use + // variants of `println!` that handles I/O errors gracefully. It's also a breaking change for a + // spawn process not written in Rust, especially if the language default handler is not + // `SIG_IGN`. Thankfully cargo tests will break if we do set the flag. + // + // For the cargo discussion, see + // <https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Applying.20.60-Zon-broken-pipe.3Dkill.60.20flags.20in.20bootstrap.3F>. + // + // For the rustc discussion, see + // <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Internal.20lint.20for.20raw.20.60print!.60.20and.20.60println!.60.3F> + // for proper solutions. if !path.ends_with("cargo") { - // If the output is piped to e.g. `head -n1` we want the process to be killed, - // rather than having an error bubble up and cause a panic. - cargo.rustflag("-Zon-broken-pipe=kill"); + // Use an untracked env var `FORCE_ON_BROKEN_PIPE_KILL` here instead of `RUSTFLAGS`. + // `RUSTFLAGS` is tracked by cargo. Conditionally omitting `-Zon-broken-pipe=kill` from + // `RUSTFLAGS` causes unnecessary tool rebuilds due to cache invalidation from building e.g. + // cargo *without* `-Zon-broken-pipe=kill` but then rustdoc *with* `-Zon-broken-pipe=kill`. + cargo.env("FORCE_ON_BROKEN_PIPE_KILL", "-Zon-broken-pipe=kill"); } cargo @@ -872,8 +889,11 @@ impl Step for LlvmBitcodeLinker { fn run(self, builder: &Builder<'_>) -> PathBuf { let bin_name = "llvm-bitcode-linker"; - builder.ensure(compile::Std::new(self.compiler, self.compiler.host)); - builder.ensure(compile::Rustc::new(self.compiler, self.target)); + // If enabled, use ci-rustc and skip building the in-tree compiler. + if !builder.download_rustc() { + builder.ensure(compile::Std::new(self.compiler, self.compiler.host)); + builder.ensure(compile::Rustc::new(self.compiler, self.target)); + } let cargo = prepare_tool_cargo( builder, diff --git a/src/bootstrap/src/core/builder.rs b/src/bootstrap/src/core/builder.rs index c35384ce3c0..9ac0b0a01f7 100644 --- a/src/bootstrap/src/core/builder.rs +++ b/src/bootstrap/src/core/builder.rs @@ -72,36 +72,40 @@ impl<'a> Deref for Builder<'a> { } pub trait Step: 'static + Clone + Debug + PartialEq + Eq + Hash { - /// `PathBuf` when directories are created or to return a `Compiler` once - /// it's been assembled. + /// Result type of `Step::run`. type Output: Clone; - /// Whether this step is run by default as part of its respective phase. - /// `true` here can still be overwritten by `should_run` calling `default_condition`. + /// Whether this step is run by default as part of its respective phase, as defined by the `describe` + /// macro in [`Builder::get_step_descriptions`]. + /// + /// Note: Even if set to `true`, it can still be overridden with [`ShouldRun::default_condition`] + /// by `Step::should_run`. const DEFAULT: bool = false; /// If true, then this rule should be skipped if --target was specified, but --host was not const ONLY_HOSTS: bool = false; - /// Primary function to execute this rule. Can call `builder.ensure()` - /// with other steps to run those. + /// Primary function to implement `Step` logic. + /// + /// This function can be triggered in two ways: + /// 1. Directly from [`Builder::execute_cli`]. + /// 2. Indirectly by being called from other `Step`s using [`Builder::ensure`]. /// - /// This gets called twice during a normal `./x.py` execution: first - /// with `dry_run() == true`, and then for real. + /// When called with [`Builder::execute_cli`] (as done by `Build::build`), this function executed twice: + /// - First in "dry-run" mode to validate certain things (like cyclic Step invocations, + /// directory creation, etc) super quickly. + /// - Then it's called again to run the actual, very expensive process. + /// + /// When triggered indirectly from other `Step`s, it may still run twice (as dry-run and real mode) + /// depending on the `Step::run` implementation of the caller. fn run(self, builder: &Builder<'_>) -> Self::Output; - /// When bootstrap is passed a set of paths, this controls whether this rule - /// will execute. However, it does not get called in a "default" context - /// when we are not passed any paths; in that case, `make_run` is called - /// directly. + /// Determines if this `Step` should be run when given specific paths (e.g., `x build $path`). fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_>; - /// Builds up a "root" rule, either as a default rule or from a path passed - /// to us. - /// - /// When path is `None`, we are executing in a context where no paths were - /// passed. When `./x.py build` is run, for example, this rule could get - /// called if it is in the correct list below with a path of `None`. + /// Called directly by the bootstrap `Step` handler when not triggered indirectly by other `Step`s using [`Builder::ensure`]. + /// For example, `./x.py test bootstrap` runs this for `test::Bootstrap`. Similarly, `./x.py test` runs it for every step + /// that is listed by the `describe` macro in [`Builder::get_step_descriptions`]. fn make_run(_run: RunConfig<'_>) { // It is reasonable to not have an implementation of make_run for rules // who do not want to get called from the root context. This means that @@ -327,7 +331,6 @@ const PATH_REMAP: &[(&str, &[&str])] = &[ "tests/mir-opt", "tests/pretty", "tests/run-make", - "tests/run-pass-valgrind", "tests/rustdoc", "tests/rustdoc-gui", "tests/rustdoc-js", @@ -415,6 +418,15 @@ impl StepDescription { .map(|desc| (desc.should_run)(ShouldRun::new(builder, desc.kind))) .collect::<Vec<_>>(); + if builder.download_rustc() && (builder.kind == Kind::Dist || builder.kind == Kind::Install) + { + eprintln!( + "ERROR: '{}' subcommand is incompatible with `rust.download-rustc`.", + builder.kind.as_str() + ); + crate::exit!(1); + } + // sanity checks on rules for (desc, should_run) in v.iter().zip(&should_runs) { assert!( @@ -852,7 +864,6 @@ impl<'a> Builder<'a> { test::Tidy, test::Ui, test::Crashes, - test::RunPassValgrind, test::Coverage, test::CoverageMap, test::CoverageRun, @@ -1000,7 +1011,9 @@ impl<'a> Builder<'a> { run::GenerateWindowsSys, run::GenerateCompletions, ), - Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode), + Kind::Setup => { + describe!(setup::Profile, setup::Hook, setup::Link, setup::Editor) + } Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std), Kind::Vendor => describe!(vendor::Vendor), // special-cased in Build::build() @@ -1685,10 +1698,24 @@ impl<'a> Builder<'a> { match mode { Mode::Std | Mode::ToolBootstrap | Mode::ToolStd => {} Mode::Rustc | Mode::Codegen | Mode::ToolRustc => { - // Build proc macros both for the host and the target + // Build proc macros both for the host and the target unless proc-macros are not + // supported by the target. if target != compiler.host && cmd_kind != Kind::Check { - cargo.arg("-Zdual-proc-macros"); - rustflags.arg("-Zdual-proc-macros"); + let error = command(self.rustc(compiler)) + .arg("--target") + .arg(target.rustc_target_arg()) + .arg("--print=file-names") + .arg("--crate-type=proc-macro") + .arg("-") + .run_capture(self) + .stderr(); + let not_supported = error + .lines() + .any(|line| line.contains("unsupported crate type `proc-macro`")); + if !not_supported { + cargo.arg("-Zdual-proc-macros"); + rustflags.arg("-Zdual-proc-macros"); + } } } } diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index bd81dc930be..695a66834d4 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -212,6 +212,39 @@ fn alias_and_path_for_library() { assert_eq!(first(cache.all::<doc::Std>()), &[doc_std!(A => A, stage = 0)]); } +#[test] +fn ci_rustc_if_unchanged_logic() { + let config = Config::parse_inner( + Flags::parse(&[ + "build".to_owned(), + "--dry-run".to_owned(), + "--set=rust.download-rustc='if-unchanged'".to_owned(), + ]), + |&_| Ok(Default::default()), + ); + + let build = Build::new(config.clone()); + let builder = Builder::new(&build); + + if config.out.exists() { + fs::remove_dir_all(&config.out).unwrap(); + } + + builder.run_step_descriptions(&Builder::get_step_descriptions(config.cmd.kind()), &[]); + + // Make sure "if-unchanged" logic doesn't try to use CI rustc while there are changes + // in compiler and/or library. + if config.download_rustc_commit.is_some() { + let has_changes = + config.last_modified_commit(&["compiler", "library"], "download-rustc", true).is_none(); + + assert!( + !has_changes, + "CI-rustc can't be used with 'if-unchanged' while there are changes in compiler and/or library." + ); + } +} + mod defaults { use pretty_assertions::assert_eq; diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 07460b81412..f768470c4ff 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -4,7 +4,7 @@ //! how the build runs. use std::cell::{Cell, RefCell}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::{self, Display}; use std::io::IsTerminal; use std::path::{Path, PathBuf, absolute}; @@ -13,6 +13,7 @@ use std::str::FromStr; use std::sync::OnceLock; use std::{cmp, env, fs}; +use build_helper::ci::CiEnv; use build_helper::exit; use build_helper::git::{GitConfig, get_closest_merge_commit, output_result}; use serde::{Deserialize, Deserializer}; @@ -22,6 +23,7 @@ use crate::core::build_steps::compile::CODEGEN_BACKEND_PREFIX; use crate::core::build_steps::llvm; pub use crate::core::config::flags::Subcommand; use crate::core::config::flags::{Color, Flags, Warnings}; +use crate::core::download::is_download_ci_available; use crate::utils::cache::{INTERNER, Interned}; use crate::utils::channel::{self, GitInfo}; use crate::utils::helpers::{self, exe, output, t}; @@ -287,6 +289,7 @@ pub struct Config { pub rust_profile_generate: Option<String>, pub rust_lto: RustcLto, pub rust_validate_mir_opts: Option<u32>, + pub rust_std_features: BTreeSet<String>, pub llvm_profile_use: Option<String>, pub llvm_profile_generate: bool, pub llvm_libunwind_default: Option<LlvmLibunwind>, @@ -1152,6 +1155,7 @@ define_config! { download_rustc: Option<StringOrBool> = "download-rustc", lto: Option<String> = "lto", validate_mir_opts: Option<u32> = "validate-mir-opts", + std_features: Option<BTreeSet<String>> = "std-features", } } @@ -1625,9 +1629,11 @@ impl Config { config.mandir = mandir.map(PathBuf::from); } + config.llvm_assertions = + toml.llvm.as_ref().map_or(false, |llvm| llvm.assertions.unwrap_or(false)); + // Store off these values as options because if they're not provided // we'll infer default values for them later - let mut llvm_assertions = None; let mut llvm_tests = None; let mut llvm_enzyme = None; let mut llvm_plugins = None; @@ -1645,6 +1651,7 @@ impl Config { let mut optimize = None; let mut omit_git_hash = None; let mut lld_enabled = None; + let mut std_features = None; let mut is_user_configured_rust_channel = false; @@ -1703,12 +1710,14 @@ impl Config { stack_protector, strip, lld_mode, + std_features: std_features_toml, } = rust; is_user_configured_rust_channel = channel.is_some(); set(&mut config.channel, channel.clone()); - config.download_rustc_commit = config.download_ci_rustc_commit(download_rustc); + config.download_rustc_commit = + config.download_ci_rustc_commit(download_rustc, config.llvm_assertions); debug = debug_toml; debug_assertions = debug_assertions_toml; @@ -1722,6 +1731,7 @@ impl Config { debuginfo_level_tools = debuginfo_level_tools_toml; debuginfo_level_tests = debuginfo_level_tests_toml; lld_enabled = lld_enabled_toml; + std_features = std_features_toml; optimize = optimize_toml; omit_git_hash = omit_git_hash_toml; @@ -1843,7 +1853,7 @@ impl Config { optimize: optimize_toml, thin_lto, release_debuginfo, - assertions, + assertions: _, tests, enzyme, plugins, @@ -1877,7 +1887,6 @@ impl Config { Some(StringOrBool::Bool(false)) | None => {} } set(&mut config.ninja_in_file, ninja); - llvm_assertions = assertions; llvm_tests = tests; llvm_enzyme = enzyme; llvm_plugins = plugins; @@ -1906,8 +1915,8 @@ impl Config { config.llvm_enable_warnings = enable_warnings.unwrap_or(false); config.llvm_build_config = build_config.clone().unwrap_or(Default::default()); - let asserts = llvm_assertions.unwrap_or(false); - config.llvm_from_ci = config.parse_download_ci_llvm(download_ci_llvm, asserts); + config.llvm_from_ci = + config.parse_download_ci_llvm(download_ci_llvm, config.llvm_assertions); if config.llvm_from_ci { let warn = |option: &str| { @@ -2075,7 +2084,6 @@ impl Config { // Now that we've reached the end of our configuration, infer the // default values for all options that we haven't otherwise stored yet. - config.llvm_assertions = llvm_assertions.unwrap_or(false); config.llvm_tests = llvm_tests.unwrap_or(false); config.llvm_enzyme = llvm_enzyme.unwrap_or(false); config.llvm_plugins = llvm_plugins.unwrap_or(false); @@ -2118,6 +2126,9 @@ impl Config { ); } + let default_std_features = BTreeSet::from([String::from("panic-unwind")]); + config.rust_std_features = std_features.unwrap_or(default_std_features); + let default = debug == Some(true); config.rust_debug_assertions = debug_assertions.unwrap_or(default); config.rust_debug_assertions_std = @@ -2388,6 +2399,20 @@ impl Config { Some(commit) => { self.download_ci_rustc(commit); + // CI-rustc can't be used without CI-LLVM. If `self.llvm_from_ci` is false, it means the "if-unchanged" + // logic has detected some changes in the LLVM submodule (download-ci-llvm=false can't happen here as + // we don't allow it while parsing the configuration). + if !self.llvm_from_ci { + // This happens when LLVM submodule is updated in CI, we should disable ci-rustc without an error + // to not break CI. For non-CI environments, we should return an error. + if CiEnv::is_ci() { + println!("WARNING: LLVM submodule has changes, `download-rustc` will be disabled."); + return None; + } else { + panic!("ERROR: LLVM submodule has changes, `download-rustc` can't be used."); + } + } + if let Some(config_path) = &self.config { let ci_config_toml = match self.get_builder_toml("ci-rustc") { Ok(ci_config_toml) => ci_config_toml, @@ -2411,8 +2436,9 @@ impl Config { ci_config_toml, ); - let disable_ci_rustc_if_incompatible = - env::var_os("DISABLE_CI_RUSTC_IF_INCOMPATIBLE") + // Primarily used by CI runners to avoid handling download-rustc incompatible + // options one by one on shell scripts. + let disable_ci_rustc_if_incompatible = env::var_os("DISABLE_CI_RUSTC_IF_INCOMPATIBLE") .is_some_and(|s| s == "1" || s == "true"); if disable_ci_rustc_if_incompatible && res.is_err() { @@ -2703,7 +2729,15 @@ impl Config { } /// Returns the commit to download, or `None` if we shouldn't download CI artifacts. - fn download_ci_rustc_commit(&self, download_rustc: Option<StringOrBool>) -> Option<String> { + fn download_ci_rustc_commit( + &self, + download_rustc: Option<StringOrBool>, + llvm_assertions: bool, + ) -> Option<String> { + if !is_download_ci_available(&self.build.triple, llvm_assertions) { + return None; + } + // If `download-rustc` is not set, default to rebuilding. let if_unchanged = match download_rustc { None | Some(StringOrBool::Bool(false)) => return None, @@ -2714,9 +2748,18 @@ impl Config { } }; + let files_to_track = &[ + self.src.join("compiler"), + self.src.join("library"), + self.src.join("src/version"), + self.src.join("src/stage0"), + self.src.join("src/ci/channel"), + ]; + // Look for a version to compare to based on the current commit. // Only commits merged by bors will have CI artifacts. - let commit = get_closest_merge_commit(Some(&self.src), &self.git_config(), &[]).unwrap(); + let commit = + get_closest_merge_commit(Some(&self.src), &self.git_config(), files_to_track).unwrap(); if commit.is_empty() { println!("ERROR: could not find commit hash for downloading rustc"); println!("HELP: maybe your repository history is too shallow?"); @@ -2725,11 +2768,24 @@ impl Config { crate::exit!(1); } + if CiEnv::is_ci() && { + let head_sha = + output(helpers::git(Some(&self.src)).arg("rev-parse").arg("HEAD").as_command_mut()); + let head_sha = head_sha.trim(); + commit == head_sha + } { + eprintln!("CI rustc commit matches with HEAD and we are in CI."); + eprintln!( + "`rustc.download-ci` functionality will be skipped as artifacts are not available." + ); + return None; + } + // Warn if there were changes to the compiler or standard library since the ancestor commit. let has_changes = !t!(helpers::git(Some(&self.src)) .args(["diff-index", "--quiet", &commit]) .arg("--") - .args([self.src.join("compiler"), self.src.join("library")]) + .args(files_to_track) .as_command_mut() .status()) .success(); @@ -3016,6 +3072,7 @@ fn check_incompatible_options_for_ci_rustc( description, incremental, default_linker, + std_features, // Rest of the options can simply be ignored. debug: _, @@ -3077,6 +3134,7 @@ fn check_incompatible_options_for_ci_rustc( err!(current_rust_config.default_linker, default_linker); err!(current_rust_config.stack_protector, stack_protector); err!(current_rust_config.lto, lto); + err!(current_rust_config.std_features, std_features); warn!(current_rust_config.channel, channel); warn!(current_rust_config.description, description); diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index 87db5f93fb0..3aefe517a5b 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -447,14 +447,14 @@ Arguments: The profile is optional and you will be prompted interactively if it is not given. The following profiles are available: {} - To only set up the git hook, VS Code config or toolchain link, you may use + To only set up the git hook, editor config or toolchain link, you may use ./x.py setup hook - ./x.py setup vscode + ./x.py setup editor ./x.py setup link", Profile::all_for_help(" ").trim_end()))] Setup { /// Either the profile for `config.toml` or another setup action. /// May be omitted to set up interactively - #[arg(value_name = "<PROFILE>|hook|vscode|link")] + #[arg(value_name = "<PROFILE>|hook|editor|link")] profile: Option<PathBuf>, }, /// Suggest a subset of tests to run, based on modified files diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs index e38d4eac051..2611b6cf51b 100644 --- a/src/bootstrap/src/core/config/tests.rs +++ b/src/bootstrap/src/core/config/tests.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::env; use std::fs::{File, remove_file}; use std::io::Write; @@ -9,9 +10,10 @@ use serde::Deserialize; use super::flags::Flags; use super::{ChangeIdWrapper, Config}; use crate::core::build_steps::clippy::get_clippy_rules_in_order; +use crate::core::build_steps::llvm; use crate::core::config::{LldMode, Target, TargetSelection, TomlConfig}; -fn parse(config: &str) -> Config { +pub(crate) fn parse(config: &str) -> Config { Config::parse_inner( Flags::parse(&["check".to_string(), "--config=/does/not/exist".to_string()]), |&_| toml::from_str(&config), @@ -20,29 +22,32 @@ fn parse(config: &str) -> Config { #[test] fn download_ci_llvm() { - if crate::core::build_steps::llvm::is_ci_llvm_modified(&parse("")) { - eprintln!("Detected LLVM as non-available: running in CI and modified LLVM in this change"); - return; + let config = parse(""); + let is_available = llvm::is_ci_llvm_available(&config, config.llvm_assertions); + if is_available { + assert!(config.llvm_from_ci); } - let parse_llvm = |s| parse(s).llvm_from_ci; - let if_unchanged = parse_llvm("llvm.download-ci-llvm = \"if-unchanged\""); + let config = parse("llvm.download-ci-llvm = true"); + let is_available = llvm::is_ci_llvm_available(&config, config.llvm_assertions); + if is_available { + assert!(config.llvm_from_ci); + } - assert!(parse_llvm("llvm.download-ci-llvm = true")); - assert!(!parse_llvm("llvm.download-ci-llvm = false")); - assert_eq!(parse_llvm(""), if_unchanged); - assert_eq!(parse_llvm("rust.channel = \"dev\""), if_unchanged); - assert!(parse_llvm("rust.channel = \"stable\"")); - assert_eq!(parse_llvm("build.build = \"x86_64-unknown-linux-gnu\""), if_unchanged); - assert_eq!( - parse_llvm( - "llvm.assertions = true \r\n build.build = \"x86_64-unknown-linux-gnu\" \r\n llvm.download-ci-llvm = \"if-unchanged\"" - ), - if_unchanged - ); - assert!(!parse_llvm( - "llvm.assertions = true \r\n build.build = \"aarch64-apple-darwin\" \r\n llvm.download-ci-llvm = \"if-unchanged\"" - )); + let config = parse("llvm.download-ci-llvm = false"); + assert!(!config.llvm_from_ci); + + let if_unchanged_config = parse("llvm.download-ci-llvm = \"if-unchanged\""); + if if_unchanged_config.llvm_from_ci { + let has_changes = if_unchanged_config + .last_modified_commit(&["src/llvm-project"], "download-ci-llvm", true) + .is_none(); + + assert!( + !has_changes, + "CI LLVM can't be enabled with 'if-unchanged' while there are changes in LLVM submodule." + ); + } } // FIXME(onur-ozkan): extend scope of the test @@ -326,3 +331,24 @@ fn verbose_tests_default_value() { let config = Config::parse(Flags::parse(&["build".into(), "compiler".into(), "-v".into()])); assert_eq!(config.verbose_tests, true); } + +#[test] +fn parse_rust_std_features() { + let config = parse("rust.std-features = [\"panic-unwind\", \"backtrace\"]"); + let expected_features: BTreeSet<String> = + ["panic-unwind", "backtrace"].into_iter().map(|s| s.to_string()).collect(); + assert_eq!(config.rust_std_features, expected_features); +} + +#[test] +fn parse_rust_std_features_empty() { + let config = parse("rust.std-features = []"); + let expected_features: BTreeSet<String> = BTreeSet::new(); + assert_eq!(config.rust_std_features, expected_features); +} + +#[test] +#[should_panic] +fn parse_rust_std_features_invalid() { + parse("rust.std-features = \"backtrace\""); +} diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs index 444b75876f2..db1f5b08338 100644 --- a/src/bootstrap/src/core/download.rs +++ b/src/bootstrap/src/core/download.rs @@ -832,3 +832,43 @@ fn path_is_dylib(path: &Path) -> bool { // The .so is not necessarily the extension, it might be libLLVM.so.18.1 path.to_str().map_or(false, |path| path.contains(".so")) } + +/// Checks whether the CI rustc is available for the given target triple. +pub(crate) fn is_download_ci_available(target_triple: &str, llvm_assertions: bool) -> bool { + // All tier 1 targets and tier 2 targets with host tools. + const SUPPORTED_PLATFORMS: &[&str] = &[ + "aarch64-apple-darwin", + "aarch64-pc-windows-msvc", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "arm-unknown-linux-gnueabi", + "arm-unknown-linux-gnueabihf", + "armv7-unknown-linux-gnueabihf", + "i686-pc-windows-gnu", + "i686-pc-windows-msvc", + "i686-unknown-linux-gnu", + "loongarch64-unknown-linux-gnu", + "powerpc-unknown-linux-gnu", + "powerpc64-unknown-linux-gnu", + "powerpc64le-unknown-linux-gnu", + "riscv64gc-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-gnu", + "x86_64-pc-windows-msvc", + "x86_64-unknown-freebsd", + "x86_64-unknown-illumos", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "x86_64-unknown-netbsd", + ]; + + const SUPPORTED_PLATFORMS_WITH_ASSERTIONS: &[&str] = + &["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]; + + if llvm_assertions { + SUPPORTED_PLATFORMS_WITH_ASSERTIONS.contains(&target_triple) + } else { + SUPPORTED_PLATFORMS.contains(&target_triple) + } +} diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 888ba8e2a3f..6fbdd76ed5b 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -13,8 +13,6 @@ use std::ffi::{OsStr, OsString}; use std::path::PathBuf; use std::{env, fs}; -use build_helper::git::warn_old_master_branch; - use crate::Build; #[cfg(not(feature = "bootstrap-self-test"))] use crate::builder::Builder; @@ -37,6 +35,9 @@ pub struct Finder { const STAGE0_MISSING_TARGETS: &[&str] = &[ // just a dummy comment so the list doesn't get onelined "armv7-rtems-eabihf", + "riscv32e-unknown-none-elf", + "riscv32em-unknown-none-elf", + "riscv32emc-unknown-none-elf", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM @@ -379,6 +380,4 @@ $ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake if let Some(ref s) = build.config.ccache { cmd_finder.must_have(s); } - - warn_old_master_branch(&build.config.git_config(), &build.config.src); } diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 75659f46431..3924a6d714e 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -17,7 +17,7 @@ //! also check out the `src/bootstrap/README.md` file for more information. use std::cell::{Cell, RefCell}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Display; use std::fs::{self, File}; use std::path::{Path, PathBuf}; @@ -470,7 +470,7 @@ impl Build { crate::core::metadata::build(&mut build); } - // Make a symbolic link so we can use a consistent directory in the documentation. + // Create symbolic link to use host sysroot from a consistent path (e.g., in the rust-analyzer config file). let build_triple = build.out.join(build.build); t!(fs::create_dir_all(&build_triple)); let host = build.out.join("host"); @@ -645,28 +645,31 @@ impl Build { &self.config.rust_info } - /// Gets the space-separated set of activated features for the standard - /// library. + /// Gets the space-separated set of activated features for the standard library. + /// This can be configured with the `std-features` key in config.toml. fn std_features(&self, target: TargetSelection) -> String { - let mut features = " panic-unwind".to_string(); + let mut features: BTreeSet<&str> = + self.config.rust_std_features.iter().map(|s| s.as_str()).collect(); match self.config.llvm_libunwind(target) { - LlvmLibunwind::InTree => features.push_str(" llvm-libunwind"), - LlvmLibunwind::System => features.push_str(" system-llvm-libunwind"), - LlvmLibunwind::No => {} - } + LlvmLibunwind::InTree => features.insert("llvm-libunwind"), + LlvmLibunwind::System => features.insert("system-llvm-libunwind"), + LlvmLibunwind::No => false, + }; + if self.config.backtrace { - features.push_str(" backtrace"); + features.insert("backtrace"); } if self.config.profiler_enabled(target) { - features.push_str(" profiler"); + features.insert("profiler"); } // Generate memcpy, etc. FIXME: Remove this once compiler-builtins // automatically detects this target. if target.contains("zkvm") { - features.push_str(" compiler-builtins-mem"); + features.insert("compiler-builtins-mem"); } - features + + features.into_iter().collect::<Vec<_>>().join(" ") } /// Gets the space-separated set of activated features for the compiler. @@ -1572,9 +1575,11 @@ Executed at: {executed_at}"#, fn rust_version(&self) -> String { let mut version = self.rust_info().version(self, &self.version); if let Some(ref s) = self.config.description { - version.push_str(" ("); - version.push_str(s); - version.push(')'); + if !s.is_empty() { + version.push_str(" ("); + version.push_str(s); + version.push(')'); + } } version } diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index e6f7f105fa2..b37786496cb 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -270,4 +270,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "If `llvm.download-ci-llvm` is not defined, it defaults to `true`.", }, + ChangeInfo { + change_id: 131075, + severity: ChangeSeverity::Info, + summary: "New option `./x setup editor` added, replacing `./x setup vscode` and adding support for vim, emacs and helix.", + }, ]; diff --git a/src/bootstrap/src/utils/shared_helpers.rs b/src/bootstrap/src/utils/shared_helpers.rs index 7150c84313c..6d3c276cc05 100644 --- a/src/bootstrap/src/utils/shared_helpers.rs +++ b/src/bootstrap/src/utils/shared_helpers.rs @@ -49,6 +49,8 @@ pub fn exe(name: &str, target: &str) -> String { format!("{name}.exe") } else if target.contains("uefi") { format!("{name}.efi") + } else if target.contains("wasm") { + format!("{name}.wasm") } else { name.to_string() } diff --git a/src/ci/docker/host-x86_64/dist-loongarch64-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-loongarch64-linux/Dockerfile index 865a9e32fa9..71eb72686b0 100644 --- a/src/ci/docker/host-x86_64/dist-loongarch64-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-loongarch64-linux/Dockerfile @@ -47,6 +47,7 @@ ENV RUST_CONFIGURE_ARGS \ --enable-extended \ --enable-full-tools \ --enable-profiler \ + --enable-sanitizers \ --disable-docs ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $TARGETS diff --git a/src/ci/docker/host-x86_64/dist-loongarch64-musl/Dockerfile b/src/ci/docker/host-x86_64/dist-loongarch64-musl/Dockerfile index 62dbfaaa673..5081f25e567 100644 --- a/src/ci/docker/host-x86_64/dist-loongarch64-musl/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-loongarch64-musl/Dockerfile @@ -29,6 +29,7 @@ ENV RUST_CONFIGURE_ARGS \ --enable-extended \ --enable-full-tools \ --enable-profiler \ + --enable-sanitizers \ --disable-docs \ --set target.loongarch64-unknown-linux-musl.crt-static=false \ --musl-root-loongarch64=/x-tools/loongarch64-unknown-linux-musl/loongarch64-unknown-linux-musl/sysroot/usr diff --git a/src/ci/docker/host-x86_64/mingw-check/Dockerfile b/src/ci/docker/host-x86_64/mingw-check/Dockerfile index 571378774be..0f8ebb987c3 100644 --- a/src/ci/docker/host-x86_64/mingw-check/Dockerfile +++ b/src/ci/docker/host-x86_64/mingw-check/Dockerfile @@ -46,7 +46,8 @@ ENV RUN_CHECK_WITH_PARALLEL_QUERIES 1 # Check library crates on all tier 1 targets. # We disable optimized compiler built-ins because that requires a C toolchain for the target. # We also skip the x86_64-unknown-linux-gnu target as it is well-tested by other jobs. -ENV SCRIPT python3 ../x.py check --stage 0 --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \ +ENV SCRIPT \ + python3 ../x.py check --stage 0 --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \ /scripts/check-default-config-profiles.sh && \ python3 ../x.py check --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu && \ python3 ../x.py clippy bootstrap -Dwarnings && \ diff --git a/src/ci/docker/host-x86_64/x86_64-fuchsia/Dockerfile b/src/ci/docker/host-x86_64/x86_64-fuchsia/Dockerfile index ba3e8bdb687..0cae83a85b3 100644 --- a/src/ci/docker/host-x86_64/x86_64-fuchsia/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-fuchsia/Dockerfile @@ -58,6 +58,9 @@ RUN mkdir -p $RUST_INSTALL_DIR/etc # Fuchsia only supports LLVM. ENV CODEGEN_BACKENDS llvm +# download-rustc is not allowed for `x install` +ENV NO_DOWNLOAD_CI_RUSTC 1 + ENV RUST_CONFIGURE_ARGS \ --prefix=$RUST_INSTALL_DIR \ --sysconfdir=etc \ @@ -70,6 +73,7 @@ ENV RUST_CONFIGURE_ARGS \ --set target.x86_64-unknown-fuchsia.ar=/usr/local/bin/llvm-ar \ --set target.x86_64-unknown-fuchsia.ranlib=/usr/local/bin/llvm-ranlib \ --set target.x86_64-unknown-fuchsia.linker=/usr/local/bin/ld.lld + ENV SCRIPT \ python3 ../x.py install --target $TARGETS compiler/rustc library/std clippy && \ bash ../src/ci/docker/host-x86_64/x86_64-fuchsia/build-fuchsia.sh diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile index 6a09ab3065f..17fc1a57492 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile @@ -84,6 +84,7 @@ ENV RUST_CONFIGURE_ARGS \ --enable-new-symbol-mangling ENV HOST_TARGET x86_64-unknown-linux-gnu +ENV FORCE_CI_RUSTC 1 COPY host-x86_64/dist-x86_64-linux/shared.sh /scripts/ COPY host-x86_64/dist-x86_64-linux/build-gccjit.sh /scripts/ @@ -100,4 +101,4 @@ RUN /scripts/build-gccjit.sh /scripts # the local version of the package is different than the one used by the CI. ENV SCRIPT /tmp/checktools.sh ../x.py && \ npm install browser-ui-test@$(head -n 1 /tmp/browser-ui-test.version) --unsafe-perm=true && \ - python3 ../x.py test tests/rustdoc-gui --stage 2 --test-args "'--no-sandbox --jobs 1'" + python3 ../x.py test tests/rustdoc-gui --stage 2 --test-args "'--jobs 1'" diff --git a/src/ci/docker/run.sh b/src/ci/docker/run.sh index fad4b5af095..28487bce482 100755 --- a/src/ci/docker/run.sh +++ b/src/ci/docker/run.sh @@ -343,6 +343,7 @@ docker \ --env PR_CI_JOB \ --env OBJDIR_ON_HOST="$objdir" \ --env CODEGEN_BACKENDS \ + --env DISABLE_CI_RUSTC_IF_INCOMPATIBLE="$DISABLE_CI_RUSTC_IF_INCOMPATIBLE" \ --init \ --rm \ rust-ci \ diff --git a/src/ci/docker/scripts/emscripten.sh b/src/ci/docker/scripts/emscripten.sh index 3f5e2c6ff1d..8b2b39ee162 100644 --- a/src/ci/docker/scripts/emscripten.sh +++ b/src/ci/docker/scripts/emscripten.sh @@ -20,5 +20,5 @@ exit 1 git clone https://github.com/emscripten-core/emsdk.git /emsdk-portable cd /emsdk-portable -hide_output ./emsdk install 2.0.5 -./emsdk activate 2.0.5 +hide_output ./emsdk install 3.1.68 +./emsdk activate 3.1.68 diff --git a/src/ci/docker/scripts/rfl-build.sh b/src/ci/docker/scripts/rfl-build.sh index 8011e07e92e..27dbfc6040c 100755 --- a/src/ci/docker/scripts/rfl-build.sh +++ b/src/ci/docker/scripts/rfl-build.sh @@ -2,7 +2,7 @@ set -euo pipefail -LINUX_VERSION=4c7864e81d8bbd51036dacf92fb0a400e13aaeee +LINUX_VERSION=v6.12-rc2 # Build rustc, rustdoc, cargo, clippy-driver and rustfmt ../x.py build --stage 2 library rustdoc clippy rustfmt diff --git a/src/ci/docker/scripts/x86_64-gnu-llvm.sh b/src/ci/docker/scripts/x86_64-gnu-llvm.sh index 98290f5a72c..dea38b6fd2a 100755 --- a/src/ci/docker/scripts/x86_64-gnu-llvm.sh +++ b/src/ci/docker/scripts/x86_64-gnu-llvm.sh @@ -2,6 +2,22 @@ set -ex +if [ "$READ_ONLY_SRC" = "0" ]; then + # `core::builder::tests::ci_rustc_if_unchanged_logic` bootstrap test ensures that + # "download-rustc=if-unchanged" logic don't use CI rustc while there are changes on + # compiler and/or library. Here we are adding a dummy commit on compiler and running + # that test to make sure we never download CI rustc with a change on the compiler tree. + echo "" >> ../compiler/rustc/src/main.rs + git config --global user.email "dummy@dummy.com" + git config --global user.name "dummy" + git add ../compiler/rustc/src/main.rs + git commit -m "test commit for rust.download-rustc=if-unchanged logic" + DISABLE_CI_RUSTC_IF_INCOMPATIBLE=0 ../x.py test bootstrap \ + -- core::builder::tests::ci_rustc_if_unchanged_logic + # Revert the dummy commit + git reset --hard HEAD~1 +fi + # Only run the stage 1 tests on merges, not on PR CI jobs. if [[ -z "${PR_CI_JOB}" ]]; then ../x.py --stage 1 test --skip src/tools/tidy diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 6379f1ade1c..8f49f623afa 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -92,6 +92,8 @@ pr: - image: x86_64-gnu-llvm-18 env: ENABLE_GCC_CODEGEN: "1" + # We are adding (temporarily) a dummy commit on the compiler + READ_ONLY_SRC: "0" <<: *job-linux-16c - image: x86_64-gnu-tools <<: *job-linux-16c @@ -259,6 +261,7 @@ auto: - image: x86_64-gnu-llvm-18 env: RUST_BACKTRACE: 1 + READ_ONLY_SRC: "0" <<: *job-linux-8c - image: x86_64-gnu-nopt diff --git a/src/ci/run.sh b/src/ci/run.sh index c8201d9bcfd..3962c354c10 100755 --- a/src/ci/run.sh +++ b/src/ci/run.sh @@ -52,6 +52,13 @@ if [ "$CI" != "" ]; then RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set change-id=99999999" fi +# If runner uses an incompatible option and `FORCE_CI_RUSTC` is not defined, +# switch to in-tree rustc. +if [ "$FORCE_CI_RUSTC" == "" ]; then + echo 'debug: `DISABLE_CI_RUSTC_IF_INCOMPATIBLE` configured.' + DISABLE_CI_RUSTC_IF_INCOMPATIBLE=1 +fi + if ! isCI || isCiBranch auto || isCiBranch beta || isCiBranch try || isCiBranch try-perf || \ isCiBranch automation/bors/try; then RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set build.print-step-timings --enable-verbose-tests" @@ -169,10 +176,16 @@ else if [ "$NO_DOWNLOAD_CI_LLVM" = "" ]; then RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set llvm.download-ci-llvm=if-unchanged" else + # CI rustc requires CI LLVM to be enabled (see https://github.com/rust-lang/rust/issues/123586). + NO_DOWNLOAD_CI_RUSTC=1 # When building for CI we want to use the static C++ Standard library # included with LLVM, since a dynamic libstdcpp may not be available. RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set llvm.static-libstdcpp" fi + + if [ "$NO_DOWNLOAD_CI_RUSTC" = "" ]; then + RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.download-rustc=if-unchanged" + fi fi if [ "$ENABLE_GCC_CODEGEN" = "1" ]; then diff --git a/src/ci/scripts/setup-upstream-remote.sh b/src/ci/scripts/setup-upstream-remote.sh new file mode 100755 index 00000000000..52b4c98a890 --- /dev/null +++ b/src/ci/scripts/setup-upstream-remote.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# In CI environments, bootstrap is forced to use the remote upstream based +# on "git_repository" and "nightly_branch" values from src/stage0 file. +# This script configures the remote as it may not exist by default. + +set -euo pipefail +IFS=$'\n\t' + +ci_dir=$(cd $(dirname $0) && pwd)/.. +source "$ci_dir/shared.sh" + +git_repository=$(parse_stage0_file_by_key "git_repository") +nightly_branch=$(parse_stage0_file_by_key "nightly_branch") + +# Configure "rust-lang/rust" upstream remote only when it's not origin. +if [ -z "$(git config remote.origin.url | grep $git_repository)" ]; then + echo "Configuring https://github.com/$git_repository remote as upstream." + git remote add upstream "https://github.com/$git_repository" + REMOTE_NAME="upstream" +else + REMOTE_NAME="origin" +fi + +git fetch $REMOTE_NAME $nightly_branch diff --git a/src/ci/shared.sh b/src/ci/shared.sh index 2b0a10e4d08..1e6a008a5de 100644 --- a/src/ci/shared.sh +++ b/src/ci/shared.sh @@ -136,3 +136,15 @@ function releaseChannel { echo $RUST_CI_OVERRIDE_RELEASE_CHANNEL fi } + +# Parse values from src/stage0 file by key +function parse_stage0_file_by_key { + local key="$1" + local file="$ci_dir/../stage0" + local value=$(awk -F= '{a[$1]=$2} END {print(a["'$key'"])}' $file) + if [ -z "$value" ]; then + echo "ERROR: Key '$key' not found in '$file'." + exit 1 + fi + echo "$value" +} diff --git a/src/doc/book b/src/doc/book -Subproject 99cf75a5414fa8adbe3974bd0836661ca901708 +Subproject f38ce8baef98cb20229e56f1be2d50e345f1179 diff --git a/src/doc/embedded-book b/src/doc/embedded-book -Subproject dbae36bf3f8410aa4313b3bad42e374735d48a9 +Subproject f40a8b420ec4b4505d9489965e261f1d5c28ba2 diff --git a/src/doc/nomicon b/src/doc/nomicon -Subproject 14649f15d232d509478206ee9ed5105641aa60d +Subproject 456b904f791751892b01282fd2757904993c4c2 diff --git a/src/doc/reference b/src/doc/reference -Subproject 24fb2687cdbc54fa18ae4acf5d879cfceca77b2 +Subproject c64e52a3d306eac0129f3ad6c6d8806ab99ae2e diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example -Subproject c79ec345f08a1e94494cdc8c999709a90203fd8 +Subproject 8bede1b919a81ab7d0c961f6bbf68d3efa297bd diff --git a/src/doc/rustc-dev-guide b/src/doc/rustc-dev-guide -Subproject 555f3de2fa0d61c4294b74d245f1cbad6fcbf58 +Subproject 07bc9ca9eb1cd6d9fbbf758c2753b748804a134 diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 207eb5d6d4f..0ef95ba64a1 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -388,6 +388,7 @@ target | std | host | notes [`x86_64-unknown-hermit`](platform-support/hermit.md) | ✓ | | x86_64 Hermit `x86_64-unknown-l4re-uclibc` | ? | | [`x86_64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | 64-bit OpenBSD +[`x86_64-unknown-trusty`](platform-support/trusty.md) | ? | | `x86_64-uwp-windows-gnu` | ✓ | | `x86_64-uwp-windows-msvc` | ✓ | | [`x86_64-win7-windows-msvc`](platform-support/win7-windows-msvc.md) | ✓ | | 64-bit Windows 7 support @@ -412,5 +413,8 @@ target | std | host | notes [`riscv32imafc-unknown-nuttx-elf`](platform-support/nuttx.md) | * | | RISC-V 32bit with NuttX [`riscv64imac-unknown-nuttx-elf`](platform-support/nuttx.md) | * | | RISC-V 64bit with NuttX [`riscv64gc-unknown-nuttx-elf`](platform-support/nuttx.md) | * | | RISC-V 64bit with NuttX +[`riscv32e-unknown-none-elf`](platform-support/riscv32-unknown-none-elf.md) | * | | Bare RISC-V (RV32E ISA) +[`riscv32em-unknown-none-elf`](platform-support/riscv32-unknown-none-elf.md) | * | | Bare RISC-V (RV32EM ISA) +[`riscv32emc-unknown-none-elf`](platform-support/riscv32-unknown-none-elf.md) | * | | Bare RISC-V (RV32EMC ISA) [runs on NVIDIA GPUs]: https://github.com/japaric-archived/nvptx#targets diff --git a/src/doc/rustc/src/platform-support/csky-unknown-linux-gnuabiv2.md b/src/doc/rustc/src/platform-support/csky-unknown-linux-gnuabiv2.md index e72bfb8bae7..32e4f855313 100644 --- a/src/doc/rustc/src/platform-support/csky-unknown-linux-gnuabiv2.md +++ b/src/doc/rustc/src/platform-support/csky-unknown-linux-gnuabiv2.md @@ -80,7 +80,7 @@ cd hello_world ```sh CARGO_TARGET_CSKY_UNKNOWN_LINUX_GNUABIV2_RUNNER=${QEMU_PATH}/bin/qemu-cskyv2 -L ${TOOLCHAIN_PATH}/csky-linux-gnuabiv2/libc \ CARGO_TARGET_CSKY_UNKNOWN_LINUX_GNUABIV2_LINKER=${TOOLCHAIN_PATH}/bin/csky-linux-gnuabiv2-gcc \ -RUSTFLAGS="-C target-features=+crt-static" \ +RUSTFLAGS="-C target-feature=+crt-static" \ cargo +stage2 run --target csky-unknown-linux-gnuabiv2 ``` diff --git a/src/doc/rustc/src/platform-support/riscv32-unknown-none-elf.md b/src/doc/rustc/src/platform-support/riscv32-unknown-none-elf.md index 9a27a568b57..38742143c4b 100644 --- a/src/doc/rustc/src/platform-support/riscv32-unknown-none-elf.md +++ b/src/doc/rustc/src/platform-support/riscv32-unknown-none-elf.md @@ -35,4 +35,4 @@ Rust test-suite on this target. ## Cross-compilation toolchains and C code This target supports C code. If interlinking with C or C++, you may need to use -`riscv64-unknown-elf-gcc` as a linker instead of `rust-lld`. +`riscv32-unknown-elf-gcc` as a linker instead of `rust-lld`. diff --git a/src/doc/rustc/src/platform-support/riscv32e-unknown-none-elf.md b/src/doc/rustc/src/platform-support/riscv32e-unknown-none-elf.md new file mode 100644 index 00000000000..69f08774f83 --- /dev/null +++ b/src/doc/rustc/src/platform-support/riscv32e-unknown-none-elf.md @@ -0,0 +1,30 @@ +# `riscv32{e,em,emc}-unknown-none-elf` + +**Tier: 3** + +Bare-metal target for RISC-V CPUs with the RV32E, RV32EM and RV32EMC ISAs. + +## Target maintainers + +* Henri Lunnikivi, <henri.lunnikivi@gmail.com>, [@hegza](https://github.com/hegza) + +## Requirements + +The target is cross-compiled, and uses static linking. No external toolchain is +required and the default `rust-lld` linker works, but you must specify a linker +script. + +## Building the target + +This target is included in Rust and can be installed via `rustup`. + +## Testing + +This is a cross-compiled `no-std` target, which must be run either in a +simulator or by programming them onto suitable hardware. It is not possible to +run the Rust test-suite on this target. + +## Cross-compilation toolchains and C code + +This target supports C code. If interlinking with C or C++, you may need to use +`riscv32-unknown-elf-gcc` as a linker instead of `rust-lld`. diff --git a/src/doc/unstable-book/src/language-features/cfg-boolean-literals.md b/src/doc/unstable-book/src/language-features/cfg-boolean-literals.md new file mode 100644 index 00000000000..ad795ff9d9b --- /dev/null +++ b/src/doc/unstable-book/src/language-features/cfg-boolean-literals.md @@ -0,0 +1,22 @@ +# `cfg_boolean_literals` + +The tracking issue for this feature is: [#131204] + +[#131204]: https://github.com/rust-lang/rust/issues/131204 + +------------------------ + +The `cfg_boolean_literals` feature makes it possible to use the `true`/`false` +literal as cfg predicate. They always evaluate to true/false respectively. + +## Examples + +```rust +#![feature(cfg_boolean_literals)] + +#[cfg(true)] +const A: i32 = 5; + +#[cfg(all(false))] +const A: i32 = 58 * 89; +``` diff --git a/src/etc/completions/x.py.sh b/src/etc/completions/x.py.sh index 2aa0d9f69cc..ba7f2c9fb5d 100644 --- a/src/etc/completions/x.py.sh +++ b/src/etc/completions/x.py.sh @@ -2741,7 +2741,7 @@ _x.py() { return 0 ;; x.py__setup) - opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [<PROFILE>|hook|vscode|link] [PATHS]... [ARGS]..." + opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [<PROFILE>|hook|editor|link] [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/src/etc/rust_analyzer_eglot.el b/src/etc/rust_analyzer_eglot.el index e55d80d98de..7b4309f8e18 100644 --- a/src/etc/rust_analyzer_eglot.el +++ b/src/etc/rust_analyzer_eglot.el @@ -2,28 +2,28 @@ .((eglot-workspace-configuration . (:rust-analyzer ( :check ( :invocationLocation "root" - :invocationStrategy "once" - :overrideCommand ["python3" - "x.py" - "check" - "--json-output"]) - :linkedProjects ["Cargo.toml" - "src/tools/x/Cargo.toml" - "src/bootstrap/Cargo.toml" - "src/tools/rust-analyzer/Cargo.toml" - "compiler/rustc_codegen_cranelift/Cargo.toml" - "compiler/rustc_codegen_gcc/Cargo.toml"] - :rustfmt ( :overrideCommand ["build/host/rustfmt/bin/rustfmt" - "--edition=2021"]) - :procMacro ( :server "build/host/stage0/libexec/rust-analyzer-proc-macro-srv" - :enable t) - :cargo ( :buildScripts ( :enable t - :invocationLocation "root" - :invocationStrategy "once" - :overrideCommand ["python3" - "x.py" - "check" - "--json-output"]) - :sysrootSrc "./library" - :extraEnv (:RUSTC_BOOTSTRAP "1")) - :rustc ( :source "./Cargo.toml" ))))))) + :invocationStrategy "once" + :overrideCommand ["python3" + "x.py" + "check" + "--json-output"]) + :linkedProjects ["Cargo.toml" + "src/tools/x/Cargo.toml" + "src/bootstrap/Cargo.toml" + "src/tools/rust-analyzer/Cargo.toml" + "compiler/rustc_codegen_cranelift/Cargo.toml" + "compiler/rustc_codegen_gcc/Cargo.toml"] + :rustfmt ( :overrideCommand ["build/host/rustfmt/bin/rustfmt" + "--edition=2021"]) + :procMacro ( :server "build/host/stage0/libexec/rust-analyzer-proc-macro-srv" + :enable t) + :cargo ( :buildScripts ( :enable t + :invocationLocation "root" + :invocationStrategy "once" + :overrideCommand ["python3" + "x.py" + "check" + "--json-output"]) + :sysrootSrc "./library" + :extraEnv (:RUSTC_BOOTSTRAP "1")) + :rustc ( :source "./Cargo.toml" ))))))) diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 26739219085..9255242611a 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -6,7 +6,7 @@ use std::fmt::{self, Write}; use std::{mem, ops}; -use rustc_ast::{LitKind, MetaItem, MetaItemKind, NestedMetaItem}; +use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; use rustc_data_structures::fx::FxHashSet; use rustc_feature::Features; use rustc_session::parse::ParseSess; @@ -41,14 +41,18 @@ pub(crate) struct InvalidCfgError { } impl Cfg { - /// Parses a `NestedMetaItem` into a `Cfg`. + /// Parses a `MetaItemInner` into a `Cfg`. fn parse_nested( - nested_cfg: &NestedMetaItem, + nested_cfg: &MetaItemInner, exclude: &FxHashSet<Cfg>, ) -> Result<Option<Cfg>, InvalidCfgError> { match nested_cfg { - NestedMetaItem::MetaItem(ref cfg) => Cfg::parse_without(cfg, exclude), - NestedMetaItem::Lit(ref lit) => { + MetaItemInner::MetaItem(ref cfg) => Cfg::parse_without(cfg, exclude), + MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => match *b { + true => Ok(Some(Cfg::True)), + false => Ok(Some(Cfg::False)), + }, + MetaItemInner::Lit(ref lit) => { Err(InvalidCfgError { msg: "unexpected literal", span: lit.span }) } } @@ -120,8 +124,8 @@ impl Cfg { /// /// If the content is not properly formatted, it will return an error indicating what and where /// the error is. - pub(crate) fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> { - Self::parse_without(cfg, &FxHashSet::default()).map(|ret| ret.unwrap()) + pub(crate) fn parse(cfg: &MetaItemInner) -> Result<Cfg, InvalidCfgError> { + Self::parse_nested(cfg, &FxHashSet::default()).map(|ret| ret.unwrap()) } /// Checks whether the given configuration can be matched in the current session. diff --git a/src/librustdoc/clean/cfg/tests.rs b/src/librustdoc/clean/cfg/tests.rs index 0ab655103e2..2c62e12c96d 100644 --- a/src/librustdoc/clean/cfg/tests.rs +++ b/src/librustdoc/clean/cfg/tests.rs @@ -1,4 +1,5 @@ -use rustc_ast::{MetaItemLit, Path, Safety, StrStyle}; +use rustc_ast::ast::LitIntType; +use rustc_ast::{MetaItemInner, MetaItemLit, Path, Safety, StrStyle}; use rustc_span::symbol::{Ident, kw}; use rustc_span::{DUMMY_SP, create_default_session_globals_then}; use thin_vec::thin_vec; @@ -13,52 +14,52 @@ fn name_value_cfg(name: &str, value: &str) -> Cfg { Cfg::Cfg(Symbol::intern(name), Some(Symbol::intern(value))) } -fn dummy_meta_item_word(name: &str) -> MetaItem { - MetaItem { +fn dummy_lit(symbol: Symbol, kind: LitKind) -> MetaItemInner { + MetaItemInner::Lit(MetaItemLit { symbol, suffix: None, kind, span: DUMMY_SP }) +} + +fn dummy_meta_item_word(name: &str) -> MetaItemInner { + MetaItemInner::MetaItem(MetaItem { unsafety: Safety::Default, path: Path::from_ident(Ident::from_str(name)), kind: MetaItemKind::Word, span: DUMMY_SP, - } + }) } -fn dummy_meta_item_name_value(name: &str, symbol: Symbol, kind: LitKind) -> MetaItem { +fn dummy_meta_item_name_value(name: &str, symbol: Symbol, kind: LitKind) -> MetaItemInner { let lit = MetaItemLit { symbol, suffix: None, kind, span: DUMMY_SP }; - MetaItem { + MetaItemInner::MetaItem(MetaItem { unsafety: Safety::Default, path: Path::from_ident(Ident::from_str(name)), kind: MetaItemKind::NameValue(lit), span: DUMMY_SP, - } + }) } macro_rules! dummy_meta_item_list { ($name:ident, [$($list:ident),* $(,)?]) => { - MetaItem { + MetaItemInner::MetaItem(MetaItem { unsafety: Safety::Default, path: Path::from_ident(Ident::from_str(stringify!($name))), kind: MetaItemKind::List(thin_vec![ $( - NestedMetaItem::MetaItem( - dummy_meta_item_word(stringify!($list)), - ), + dummy_meta_item_word(stringify!($list)), )* ]), span: DUMMY_SP, - } + }) }; ($name:ident, [$($list:expr),* $(,)?]) => { - MetaItem { + MetaItemInner::MetaItem(MetaItem { unsafety: Safety::Default, path: Path::from_ident(Ident::from_str(stringify!($name))), kind: MetaItemKind::List(thin_vec![ - $( - NestedMetaItem::MetaItem($list), - )* + $($list,)* ]), span: DUMMY_SP, - } + }) }; } @@ -251,6 +252,14 @@ fn test_cfg_or() { #[test] fn test_parse_ok() { create_default_session_globals_then(|| { + let r#true = Symbol::intern("true"); + let mi = dummy_lit(r#true, LitKind::Bool(true)); + assert_eq!(Cfg::parse(&mi), Ok(Cfg::True)); + + let r#false = Symbol::intern("false"); + let mi = dummy_lit(r#false, LitKind::Bool(false)); + assert_eq!(Cfg::parse(&mi), Ok(Cfg::False)); + let mi = dummy_meta_item_word("all"); assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all"))); @@ -309,6 +318,14 @@ fn test_parse_err() { let mi = dummy_meta_item_list!(not, [dummy_meta_item_list!(foo, []),]); assert!(Cfg::parse(&mi).is_err()); + + let c = Symbol::intern("e"); + let mi = dummy_lit(c, LitKind::Char('e')); + assert!(Cfg::parse(&mi).is_err()); + + let five = Symbol::intern("5"); + let mi = dummy_lit(five, LitKind::Int(5.into(), LitIntType::Unsuffixed)); + assert!(Cfg::parse(&mi).is_err()); }) } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index b79458eaa78..fa73733360c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1828,13 +1828,8 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T Array(Box::new(clean_ty(ty, cx)), length.into()) } TyKind::Tup(tys) => Tuple(tys.iter().map(|ty| clean_ty(ty, cx)).collect()), - TyKind::OpaqueDef(item_id, _) => { - let item = cx.tcx.hir().item(item_id); - if let hir::ItemKind::OpaqueTy(ty) = item.kind { - ImplTrait(ty.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect()) - } else { - unreachable!() - } + TyKind::OpaqueDef(ty, _) => { + ImplTrait(ty.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect()) } TyKind::Path(_) => clean_qpath(ty, cx), TyKind::TraitObject(bounds, lifetime, _) => { @@ -2736,9 +2731,6 @@ fn clean_maybe_renamed_item<'tcx>( type_: clean_ty(ty, cx), kind: ConstantKind::Local { body: body_id, def_id }, })), - // clean_ty changes types which reference an OpaqueTy item to instead be - // an ImplTrait, so it's ok to return nothing here. - ItemKind::OpaqueTy(_) => return vec![], ItemKind::TyAlias(hir_ty, generics) => { *cx.current_type_aliases.entry(def_id).or_insert(0) += 1; let rustdoc_ty = clean_ty(hir_ty, cx); diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index a3277e8ca92..bc5bf4c0583 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -5,10 +5,11 @@ use std::sync::{Arc, OnceLock as OnceCell}; use std::{fmt, iter}; use arrayvec::ArrayVec; +use rustc_ast::MetaItemInner; use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, Stability, StableSince}; use rustc_const_eval::const_eval::is_unstable_const_fn; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::lang_items::LangItem; @@ -113,7 +114,7 @@ impl From<DefId> for ItemId { pub(crate) struct Crate { pub(crate) module: Item, /// Only here so that they can be filtered through the rustdoc passes. - pub(crate) external_traits: Box<FxHashMap<DefId, Trait>>, + pub(crate) external_traits: Box<FxIndexMap<DefId, Trait>>, } impl Crate { @@ -952,7 +953,7 @@ pub(crate) struct Module { } pub(crate) trait AttributesExt { - type AttributeIterator<'a>: Iterator<Item = ast::NestedMetaItem> + type AttributeIterator<'a>: Iterator<Item = ast::MetaItemInner> where Self: 'a; type Attributes<'a>: Iterator<Item = &'a ast::Attribute> @@ -986,7 +987,7 @@ pub(crate) trait AttributesExt { .peekable(); if doc_cfg.peek().is_some() && doc_cfg_active { doc_cfg - .filter_map(|attr| Cfg::parse(attr.meta_item()?).ok()) + .filter_map(|attr| Cfg::parse(&attr).ok()) .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg) } else if doc_auto_cfg_active { // If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because @@ -1042,7 +1043,7 @@ pub(crate) trait AttributesExt { let mut meta = attr.meta_item().unwrap().clone(); meta.path = ast::Path::from_ident(Ident::with_dummy_span(sym::target_feature)); - if let Ok(feat_cfg) = Cfg::parse(&meta) { + if let Ok(feat_cfg) = Cfg::parse(&MetaItemInner::MetaItem(meta)) { cfg &= feat_cfg; } } @@ -1054,7 +1055,7 @@ pub(crate) trait AttributesExt { } impl AttributesExt for [ast::Attribute] { - type AttributeIterator<'a> = impl Iterator<Item = ast::NestedMetaItem> + 'a; + type AttributeIterator<'a> = impl Iterator<Item = ast::MetaItemInner> + 'a; type Attributes<'a> = impl Iterator<Item = &'a ast::Attribute> + 'a; fn lists(&self, name: Symbol) -> Self::AttributeIterator<'_> { @@ -1071,7 +1072,7 @@ impl AttributesExt for [ast::Attribute] { impl AttributesExt for [(Cow<'_, ast::Attribute>, Option<DefId>)] { type AttributeIterator<'a> - = impl Iterator<Item = ast::NestedMetaItem> + 'a + = impl Iterator<Item = ast::MetaItemInner> + 'a where Self: 'a; type Attributes<'a> @@ -1105,11 +1106,11 @@ pub(crate) trait NestedAttributesExt { /// Returns `Some(attr)` if the attribute list contains 'attr' /// corresponding to a specific `word` - fn get_word_attr(self, word: Symbol) -> Option<ast::NestedMetaItem>; + fn get_word_attr(self, word: Symbol) -> Option<ast::MetaItemInner>; } -impl<I: Iterator<Item = ast::NestedMetaItem>> NestedAttributesExt for I { - fn get_word_attr(mut self, word: Symbol) -> Option<ast::NestedMetaItem> { +impl<I: Iterator<Item = ast::MetaItemInner>> NestedAttributesExt for I { + fn get_word_attr(mut self, word: Symbol) -> Option<ast::MetaItemInner> { self.find(|attr| attr.is_word() && attr.has_name(word)) } } @@ -1156,7 +1157,7 @@ pub(crate) struct Attributes { } impl Attributes { - pub(crate) fn lists(&self, name: Symbol) -> impl Iterator<Item = ast::NestedMetaItem> + '_ { + pub(crate) fn lists(&self, name: Symbol) -> impl Iterator<Item = ast::MetaItemInner> + '_ { self.other_attrs.lists(name) } @@ -1222,7 +1223,7 @@ impl Attributes { } pub(crate) fn get_doc_aliases(&self) -> Box<[Symbol]> { - let mut aliases = FxHashSet::default(); + let mut aliases = FxIndexSet::default(); for attr in self.other_attrs.lists(sym::doc).filter(|a| a.has_name(sym::alias)) { if let Some(values) = attr.meta_item_list() { @@ -1758,7 +1759,7 @@ pub(crate) enum PrimitiveType { Never, } -type SimplifiedTypes = FxHashMap<PrimitiveType, ArrayVec<SimplifiedType, 3>>; +type SimplifiedTypes = FxIndexMap<PrimitiveType, ArrayVec<SimplifiedType, 3>>; impl PrimitiveType { pub(crate) fn from_hir(prim: hir::PrimTy) -> PrimitiveType { use ast::{FloatTy, IntTy, UintTy}; @@ -1926,10 +1927,10 @@ impl PrimitiveType { /// In particular, if a crate depends on both `std` and another crate that also defines /// `rustc_doc_primitive`, then it's entirely random whether `std` or the other crate is picked. /// (no_std crates are usually fine unless multiple dependencies define a primitive.) - pub(crate) fn primitive_locations(tcx: TyCtxt<'_>) -> &FxHashMap<PrimitiveType, DefId> { - static PRIMITIVE_LOCATIONS: OnceCell<FxHashMap<PrimitiveType, DefId>> = OnceCell::new(); + pub(crate) fn primitive_locations(tcx: TyCtxt<'_>) -> &FxIndexMap<PrimitiveType, DefId> { + static PRIMITIVE_LOCATIONS: OnceCell<FxIndexMap<PrimitiveType, DefId>> = OnceCell::new(); PRIMITIVE_LOCATIONS.get_or_init(|| { - let mut primitive_locations = FxHashMap::default(); + let mut primitive_locations = FxIndexMap::default(); // NOTE: technically this misses crates that are only passed with `--extern` and not loaded when checking the crate. // This is a degenerate case that I don't plan to support. for &crate_num in tcx.crates(()) { @@ -2459,7 +2460,7 @@ pub(crate) struct Impl { } impl Impl { - pub(crate) fn provided_trait_methods(&self, tcx: TyCtxt<'_>) -> FxHashSet<Symbol> { + pub(crate) fn provided_trait_methods(&self, tcx: TyCtxt<'_>) -> FxIndexSet<Symbol> { self.trait_ .as_ref() .map(|t| t.def_id()) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 7a37f5c70a5..2cd69474b1c 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fmt, io}; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_errors::DiagCtxtHandle; use rustc_session::config::{ self, CodegenOptions, CrateType, ErrorOutputType, Externs, Input, JsonUnusedExterns, @@ -249,7 +249,7 @@ pub(crate) struct RenderOptions { pub(crate) extern_html_root_takes_precedence: bool, /// A map of the default settings (values are as for DOM storage API). Keys should lack the /// `rustdoc-` prefix. - pub(crate) default_settings: FxHashMap<String, String>, + pub(crate) default_settings: FxIndexMap<String, String>, /// If present, suffix added to CSS/JavaScript files when referencing them in generated pages. pub(crate) resource_suffix: String, /// Whether to create an index page in the root of the output directory. If this is true but diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 4c48c075a94..aaf4c80f997 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -2,7 +2,7 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, LazyLock}; use std::{io, mem}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_data_structures::sync::Lrc; use rustc_data_structures::unord::UnordSet; use rustc_errors::codes::*; @@ -39,7 +39,7 @@ pub(crate) struct DocContext<'tcx> { /// Most of this logic is copied from rustc_lint::late. pub(crate) param_env: ParamEnv<'tcx>, /// Later on moved through `clean::Crate` into `cache` - pub(crate) external_traits: FxHashMap<DefId, clean::Trait>, + pub(crate) external_traits: FxIndexMap<DefId, clean::Trait>, /// Used while populating `external_traits` to ensure we don't process the same trait twice at /// the same time. pub(crate) active_extern_traits: DefIdSet, diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 4e7571a4803..d5bc2a93fa8 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -14,7 +14,7 @@ use std::{panic, str}; pub(crate) use make::DocTestBuilder; pub(crate) use markdown::test as test_markdown; use rustc_ast as ast; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_errors::{ColorConfig, DiagCtxtHandle, ErrorGuaranteed, FatalError}; use rustc_hir::CRATE_HIR_ID; use rustc_hir::def_id::LOCAL_CRATE; @@ -213,12 +213,13 @@ pub(crate) fn run( let unused_extern_reports: Vec<_> = std::mem::take(&mut unused_extern_reports.lock().unwrap()); if unused_extern_reports.len() == compiling_test_count { - let extern_names = externs.iter().map(|(name, _)| name).collect::<FxHashSet<&String>>(); + let extern_names = + externs.iter().map(|(name, _)| name).collect::<FxIndexSet<&String>>(); let mut unused_extern_names = unused_extern_reports .iter() - .map(|uexts| uexts.unused_extern_names.iter().collect::<FxHashSet<&String>>()) + .map(|uexts| uexts.unused_extern_names.iter().collect::<FxIndexSet<&String>>()) .fold(extern_names, |uextsa, uextsb| { - uextsa.intersection(&uextsb).copied().collect::<FxHashSet<&String>>() + uextsa.intersection(&uextsb).copied().collect::<FxIndexSet<&String>>() }) .iter() .map(|v| (*v).clone()) @@ -253,7 +254,7 @@ pub(crate) fn run_tests( rustdoc_options: &Arc<RustdocOptions>, unused_extern_reports: &Arc<Mutex<Vec<UnusedExterns>>>, mut standalone_tests: Vec<test::TestDescAndFn>, - mergeable_tests: FxHashMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>, + mergeable_tests: FxIndexMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>, ) { let mut test_args = Vec::with_capacity(rustdoc_options.test_args.len() + 1); test_args.insert(0, "rustdoctest".to_string()); @@ -775,7 +776,7 @@ pub(crate) trait DocTestVisitor { struct CreateRunnableDocTests { standalone_tests: Vec<test::TestDescAndFn>, - mergeable_tests: FxHashMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>, + mergeable_tests: FxIndexMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>, rustdoc_options: Arc<RustdocOptions>, opts: GlobalTestOptions, @@ -790,7 +791,7 @@ impl CreateRunnableDocTests { let can_merge_doctests = rustdoc_options.edition >= Edition::Edition2024; CreateRunnableDocTests { standalone_tests: Vec::new(), - mergeable_tests: FxHashMap::default(), + mergeable_tests: FxIndexMap::default(), rustdoc_options: Arc::new(rustdoc_options), opts, visited_tests: FxHashMap::default(), diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs index 326ca4ee1e6..942ec8d9936 100644 --- a/src/librustdoc/doctest/runner.rs +++ b/src/librustdoc/doctest/runner.rs @@ -1,6 +1,6 @@ use std::fmt::Write; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::FxIndexSet; use rustc_span::edition::Edition; use crate::doctest::{ @@ -11,7 +11,7 @@ use crate::html::markdown::{Ignore, LangString}; /// Convenient type to merge compatible doctests into one. pub(crate) struct DocTestRunner { - crate_attrs: FxHashSet<String>, + crate_attrs: FxIndexSet<String>, ids: String, output: String, supports_color: bool, @@ -21,7 +21,7 @@ pub(crate) struct DocTestRunner { impl DocTestRunner { pub(crate) fn new() -> Self { Self { - crate_attrs: FxHashSet::default(), + crate_attrs: FxIndexSet::default(), ids: String::new(), output: String::new(), supports_color: true, diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index db1a0bd0af9..ff0d537b19f 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -1,6 +1,6 @@ use std::mem; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet}; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::Symbol; @@ -42,7 +42,7 @@ pub(crate) struct Cache { /// URLs when a type is being linked to. External paths are not located in /// this map because the `External` type itself has all the information /// necessary. - pub(crate) paths: FxHashMap<DefId, (Vec<Symbol>, ItemType)>, + pub(crate) paths: FxIndexMap<DefId, (Vec<Symbol>, ItemType)>, /// Similar to `paths`, but only holds external paths. This is only used for /// generating explicit hyperlinks to other crates. @@ -64,18 +64,18 @@ pub(crate) struct Cache { /// Implementations of a crate should inherit the documentation of the /// parent trait if no extra documentation is specified, and default methods /// should show up in documentation about trait implementations. - pub(crate) traits: FxHashMap<DefId, clean::Trait>, + pub(crate) traits: FxIndexMap<DefId, clean::Trait>, /// When rendering traits, it's often useful to be able to list all /// implementors of the trait, and this mapping is exactly, that: a mapping /// of trait ids to the list of known implementors of the trait - pub(crate) implementors: FxHashMap<DefId, Vec<Impl>>, + pub(crate) implementors: FxIndexMap<DefId, Vec<Impl>>, /// Cache of where external crate documentation can be found. - pub(crate) extern_locations: FxHashMap<CrateNum, ExternalLocation>, + pub(crate) extern_locations: FxIndexMap<CrateNum, ExternalLocation>, /// Cache of where documentation for primitives can be found. - pub(crate) primitive_locations: FxHashMap<clean::PrimitiveType, DefId>, + pub(crate) primitive_locations: FxIndexMap<clean::PrimitiveType, DefId>, // Note that external items for which `doc(hidden)` applies to are shown as // non-reachable while local items aren't. This is because we're reusing @@ -118,7 +118,7 @@ pub(crate) struct Cache { // crawl. In order to prevent crashes when looking for notable traits or // when gathering trait documentation on a type, hold impls here while // folding and add them to the cache later on if we find the trait. - orphan_trait_impls: Vec<(DefId, FxHashSet<DefId>, Impl)>, + orphan_trait_impls: Vec<(DefId, FxIndexSet<DefId>, Impl)>, /// All intra-doc links resolved so far. /// @@ -376,7 +376,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // Figure out the id of this impl. This may map to a // primitive rather than always to a struct/enum. // Note: matching twice to restrict the lifetime of the `i` borrow. - let mut dids = FxHashSet::default(); + let mut dids = FxIndexSet::default(); match i.for_ { clean::Type::Path { ref path } | clean::BorrowedRef { type_: box clean::Type::Path { ref path }, .. } => { diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs index 691f86847b5..31a2701f06a 100644 --- a/src/librustdoc/html/escape.rs +++ b/src/librustdoc/html/escape.rs @@ -108,7 +108,17 @@ impl<'a> fmt::Display for EscapeBodyTextWithWbr<'a> { || pk.map_or(true, |(_, t)| t.chars().any(|c| c.is_uppercase())); let next_is_underscore = || pk.map_or(true, |(_, t)| t.contains('_')); let next_is_colon = || pk.map_or(true, |(_, t)| t.contains(':')); - if i - last > 3 && is_uppercase() && !next_is_uppercase() { + // Check for CamelCase. + // + // `i - last > 3` avoids turning FmRadio into Fm<wbr>Radio, which is technically + // correct, but needlessly bloated. + // + // is_uppercase && !next_is_uppercase checks for camelCase. HTTPSProxy, + // for example, should become HTTPS<wbr>Proxy. + // + // !next_is_underscore avoids turning TEST_RUN into TEST<wbr>_<wbr>RUN, which is also + // needlessly bloated. + if i - last > 3 && is_uppercase() && !next_is_uppercase() && !next_is_underscore() { EscapeBodyText(&text[last..i]).fmt(fmt)?; fmt.write_str("<wbr>")?; last = i; diff --git a/src/librustdoc/html/escape/tests.rs b/src/librustdoc/html/escape/tests.rs index a09649e9e18..de702e16063 100644 --- a/src/librustdoc/html/escape/tests.rs +++ b/src/librustdoc/html/escape/tests.rs @@ -24,6 +24,10 @@ fn escape_body_text_with_wbr() { assert_eq!(&E("first:second").to_string(), "first:<wbr>second"); assert_eq!(&E("first::second").to_string(), "first::<wbr>second"); assert_eq!(&E("MY_CONSTANT").to_string(), "MY_<wbr>CONSTANT"); + assert_eq!( + &E("_SIDD_MASKED_NEGATIVE_POLARITY").to_string(), + "_SIDD_<wbr>MASKED_<wbr>NEGATIVE_<wbr>POLARITY" + ); // a string won't get wrapped if it's less than 8 bytes assert_eq!(&E("HashSet").to_string(), "HashSet"); // an individual word won't get wrapped if it's less than 4 bytes diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 69b3421f888..b68b7295096 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -8,7 +8,7 @@ use std::collections::VecDeque; use std::fmt::{Display, Write}; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_lexer::{Cursor, LiteralKind, TokenKind}; use rustc_span::edition::Edition; use rustc_span::symbol::Symbol; @@ -34,7 +34,7 @@ pub(crate) struct HrefContext<'a, 'tcx> { /// Decorations are represented as a map from CSS class to vector of character ranges. /// Each range will be wrapped in a span with that class. #[derive(Default)] -pub(crate) struct DecorationInfo(pub(crate) FxHashMap<&'static str, Vec<(u32, u32)>>); +pub(crate) struct DecorationInfo(pub(crate) FxIndexMap<&'static str, Vec<(u32, u32)>>); #[derive(Eq, PartialEq, Clone, Copy)] pub(crate) enum Tooltip { @@ -845,6 +845,7 @@ impl<'src> Classifier<'src> { // Number literals. LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number, }, + TokenKind::GuardedStrPrefix => return no_highlight(sink), TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => { self.in_macro = true; sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) }); diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index 75328e724fe..fd5275189d6 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -1,5 +1,5 @@ use expect_test::expect_file; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_span::create_default_session_globals_then; use super::{DecorationInfo, write_code}; @@ -73,7 +73,7 @@ fn test_decorations() { let y = 2; let z = 3; let a = 4;"; - let mut decorations = FxHashMap::default(); + let mut decorations = FxIndexMap::default(); decorations.insert("example", vec![(0, 10), (11, 21)]); decorations.insert("example2", vec![(22, 32)]); diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index 3684dc42ac7..31ccfeb3873 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use rinja::Template; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use super::static_files::{STATIC_FILES, StaticFiles}; use crate::externalfiles::ExternalHtml; @@ -13,7 +13,7 @@ pub(crate) struct Layout { pub(crate) logo: String, pub(crate) favicon: String, pub(crate) external_html: ExternalHtml, - pub(crate) default_settings: FxHashMap<String, String>, + pub(crate) default_settings: FxIndexMap<String, String>, pub(crate) krate: String, pub(crate) krate_version: String, /// The given user css file which allow to customize the generated diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 8ae5484feda..5dacabd031e 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -35,10 +35,9 @@ use std::str::{self, CharIndices}; use std::sync::OnceLock; use pulldown_cmark::{ - BrokenLink, BrokenLinkCallback, CodeBlockKind, CowStr, Event, LinkType, OffsetIter, Options, - Parser, Tag, TagEnd, html, + BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html, }; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::{Diag, DiagMessage}; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::TyCtxt; @@ -651,12 +650,12 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> { /// references. struct Footnotes<'a, I> { inner: I, - footnotes: FxHashMap<String, (Vec<Event<'a>>, u16)>, + footnotes: FxIndexMap<String, (Vec<Event<'a>>, u16)>, } impl<'a, I> Footnotes<'a, I> { fn new(iter: I) -> Self { - Footnotes { inner: iter, footnotes: FxHashMap::default() } + Footnotes { inner: iter, footnotes: FxIndexMap::default() } } fn get_entry(&mut self, key: &str) -> &mut (Vec<Event<'a>>, u16) { @@ -694,7 +693,7 @@ impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> { Some(e) => return Some(e), None => { if !self.footnotes.is_empty() { - let mut v: Vec<_> = self.footnotes.drain().map(|(_, x)| x).collect(); + 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("<div class=\"footnotes\"><hr><ol>"); for (mut content, id) in v { @@ -1686,7 +1685,6 @@ pub(crate) fn html_text_from_events<'a>( pub(crate) struct MarkdownLink { pub kind: LinkType, pub link: String, - pub display_text: Option<String>, pub range: MarkdownLinkRange, } @@ -1848,23 +1846,9 @@ pub(crate) fn markdown_links<'md, R>( LinkType::Autolink | LinkType::Email => unreachable!(), }; - let display_text = if matches!( - link_type, - LinkType::Inline - | LinkType::ReferenceUnknown - | LinkType::Reference - | LinkType::Shortcut - | LinkType::ShortcutUnknown - ) { - collect_link_data(&mut event_iter) - } else { - None - }; - if let Some(link) = preprocess_link(MarkdownLink { kind: link_type, link: dest_url.into_string(), - display_text, range, }) { links.push(link); @@ -1877,37 +1861,6 @@ pub(crate) fn markdown_links<'md, R>( links } -/// Collects additional data of link. -fn collect_link_data<'input, F: BrokenLinkCallback<'input>>( - event_iter: &mut OffsetIter<'input, F>, -) -> Option<String> { - let mut display_text: Option<String> = None; - let mut append_text = |text: CowStr<'_>| { - if let Some(display_text) = &mut display_text { - display_text.push_str(&text); - } else { - display_text = Some(text.to_string()); - } - }; - - while let Some((event, _span)) = event_iter.next() { - match event { - Event::Text(text) => { - append_text(text); - } - Event::Code(code) => { - append_text(code); - } - Event::End(_) => { - break; - } - _ => {} - } - } - - display_text -} - #[derive(Debug)] pub(crate) struct RustCodeBlock { /// The range in the markdown that the code block occupies. Note that this includes the fences diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index bce3f218908..dc4d45e592e 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use std::sync::mpsc::{Receiver, channel}; use rinja::Template; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; @@ -69,16 +69,16 @@ pub(crate) struct Context<'tcx> { /// `true`. pub(crate) include_sources: bool, /// Collection of all types with notable traits referenced in the current module. - pub(crate) types_with_notable_traits: FxHashSet<clean::Type>, + pub(crate) types_with_notable_traits: FxIndexSet<clean::Type>, /// Field used during rendering, to know if we're inside an inlined item. pub(crate) is_inside_inlined_module: bool, } // `Context` is cloned a lot, so we don't want the size to grow unexpectedly. #[cfg(all(not(windows), target_pointer_width = "64"))] -rustc_data_structures::static_assert_size!(Context<'_>, 160); +rustc_data_structures::static_assert_size!(Context<'_>, 184); #[cfg(all(windows, target_pointer_width = "64"))] -rustc_data_structures::static_assert_size!(Context<'_>, 168); +rustc_data_structures::static_assert_size!(Context<'_>, 192); /// Shared mutable state used in [`Context`] and elsewhere. pub(crate) struct SharedContext<'tcx> { @@ -90,7 +90,7 @@ pub(crate) struct SharedContext<'tcx> { /// creation of the context (contains info like the favicon and added html). pub(crate) layout: layout::Layout, /// The local file sources we've emitted and their respective url-paths. - pub(crate) local_sources: FxHashMap<PathBuf, String>, + pub(crate) local_sources: FxIndexMap<PathBuf, String>, /// Show the memory layout of types in the docs. pub(super) show_type_layout: bool, /// The base-URL of the issue tracker for when an item has been tagged with @@ -567,7 +567,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { deref_id_map: Default::default(), shared: Rc::new(scx), include_sources, - types_with_notable_traits: FxHashSet::default(), + types_with_notable_traits: FxIndexSet::default(), is_inside_inlined_module: false, }; @@ -591,7 +591,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { id_map: IdMap::new(), shared: Rc::clone(&self.shared), include_sources: self.include_sources, - types_with_notable_traits: FxHashSet::default(), + types_with_notable_traits: FxIndexSet::default(), is_inside_inlined_module: self.is_inside_inlined_module, } } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 227df0c5f39..399730a01c8 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -47,7 +47,7 @@ use std::{fs, str}; use rinja::Template; use rustc_attr::{ConstStability, DeprecatedSince, Deprecation, StabilityLevel, StableSince}; use rustc_data_structures::captures::Captures; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::Mutability; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_middle::ty::print::PrintTraitRefExt; @@ -328,24 +328,24 @@ impl Ord for ItemEntry { #[derive(Debug)] struct AllTypes { - structs: FxHashSet<ItemEntry>, - enums: FxHashSet<ItemEntry>, - unions: FxHashSet<ItemEntry>, - primitives: FxHashSet<ItemEntry>, - traits: FxHashSet<ItemEntry>, - macros: FxHashSet<ItemEntry>, - functions: FxHashSet<ItemEntry>, - type_aliases: FxHashSet<ItemEntry>, - statics: FxHashSet<ItemEntry>, - constants: FxHashSet<ItemEntry>, - attribute_macros: FxHashSet<ItemEntry>, - derive_macros: FxHashSet<ItemEntry>, - trait_aliases: FxHashSet<ItemEntry>, + structs: FxIndexSet<ItemEntry>, + enums: FxIndexSet<ItemEntry>, + unions: FxIndexSet<ItemEntry>, + primitives: FxIndexSet<ItemEntry>, + traits: FxIndexSet<ItemEntry>, + macros: FxIndexSet<ItemEntry>, + functions: FxIndexSet<ItemEntry>, + type_aliases: FxIndexSet<ItemEntry>, + statics: FxIndexSet<ItemEntry>, + constants: FxIndexSet<ItemEntry>, + attribute_macros: FxIndexSet<ItemEntry>, + derive_macros: FxIndexSet<ItemEntry>, + trait_aliases: FxIndexSet<ItemEntry>, } impl AllTypes { fn new() -> AllTypes { - let new_set = |cap| FxHashSet::with_capacity_and_hasher(cap, Default::default()); + let new_set = |cap| FxIndexSet::with_capacity_and_hasher(cap, Default::default()); AllTypes { structs: new_set(100), enums: new_set(100), @@ -437,7 +437,7 @@ impl AllTypes { } fn print(self, f: &mut Buffer) { - fn print_entries(f: &mut Buffer, e: &FxHashSet<ItemEntry>, kind: ItemSection) { + fn print_entries(f: &mut Buffer, e: &FxIndexSet<ItemEntry>, kind: ItemSection) { if !e.is_empty() { let mut e: Vec<&ItemEntry> = e.iter().collect(); e.sort(); @@ -1151,7 +1151,7 @@ fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Con #[derive(Copy, Clone)] enum AssocItemLink<'a> { Anchor(Option<&'a str>), - GotoSource(ItemId, &'a FxHashSet<Symbol>), + GotoSource(ItemId, &'a FxIndexSet<Symbol>), } impl<'a> AssocItemLink<'a> { @@ -1494,7 +1494,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { for it in &impl_.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { out.push_str("<div class=\"where\"> "); - let empty_set = FxHashSet::default(); + let empty_set = FxIndexSet::default(); let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); assoc_type( &mut out, @@ -2526,7 +2526,7 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &c })() .unwrap_or(DUMMY_SP); - let mut decoration_info = FxHashMap::default(); + let mut decoration_info = FxIndexMap::default(); decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]); decoration_info.insert("highlight", byte_ranges); diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 38276e4d20c..3c96f873681 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use itertools::Itertools; use rinja::Template; use rustc_data_structures::captures::Captures; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; use rustc_hir as hir; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; @@ -932,7 +932,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: let cloned_shared = Rc::clone(&cx.shared); let cache = &cloned_shared.cache; - let mut extern_crates = FxHashSet::default(); + let mut extern_crates = FxIndexSet::default(); if !t.is_object_safe(cx.tcx()) { write_section_heading( diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 660ca3b2594..c958458b662 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -774,7 +774,7 @@ pub(crate) fn get_function_type_for_search<'tcx>( fn get_index_type( clean_type: &clean::Type, generics: Vec<RenderType>, - rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>, + rgen: &mut FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)>, ) -> RenderType { RenderType { id: get_index_type_id(clean_type, rgen), @@ -785,7 +785,7 @@ fn get_index_type( fn get_index_type_id( clean_type: &clean::Type, - rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>, + rgen: &mut FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)>, ) -> Option<RenderTypeId> { use rustc_hir::def::{DefKind, Res}; match *clean_type { @@ -854,7 +854,7 @@ fn simplify_fn_type<'a, 'tcx>( tcx: TyCtxt<'tcx>, recurse: usize, res: &mut Vec<RenderType>, - rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>, + rgen: &mut FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)>, is_return: bool, cache: &Cache, ) { @@ -1198,7 +1198,7 @@ fn simplify_fn_constraint<'a, 'tcx>( tcx: TyCtxt<'tcx>, recurse: usize, res: &mut Vec<(RenderTypeId, Vec<RenderType>)>, - rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>, + rgen: &mut FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)>, is_return: bool, cache: &Cache, ) { @@ -1285,7 +1285,7 @@ fn get_fn_inputs_and_outputs<'tcx>( ) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) { let decl = &func.decl; - let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default(); + let mut rgen: FxIndexMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default(); let combined_generics; let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_or_trait_generics { diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 2143f1ff236..b314b060368 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::intravisit::{self, Visitor}; @@ -44,7 +44,7 @@ pub(crate) fn collect_spans_and_sources( src_root: &Path, include_sources: bool, generate_link_to_definition: bool, -) -> (FxHashMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) { +) -> (FxIndexMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) { let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() }; if include_sources { @@ -243,7 +243,6 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { | ItemKind::ExternCrate(_) | ItemKind::ForeignMod { .. } | ItemKind::GlobalAsm(_) - | ItemKind::OpaqueTy(_) // We already have "visit_mod" above so no need to check it here. | ItemKind::Mod(_) => {} } diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index af94b1042c0..12b63460056 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -28,7 +28,7 @@ use indexmap::IndexMap; use itertools::Itertools; use regex::Regex; use rustc_data_structures::flock; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_middle::ty::TyCtxt; use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_span::Symbol; @@ -505,8 +505,8 @@ createSrcSidebar();", struct Hierarchy { parent: Weak<Self>, elem: OsString, - children: RefCell<FxHashMap<OsString, Rc<Self>>>, - elems: RefCell<FxHashSet<OsString>>, + children: RefCell<FxIndexMap<OsString, Rc<Self>>>, + elems: RefCell<FxIndexSet<OsString>>, } impl Hierarchy { @@ -961,8 +961,8 @@ impl Serialize for AliasSerializableImpl { fn get_path_parts<T: CciPart>( dst: &Path, crates_info: &[CrateInfo], -) -> FxHashMap<PathBuf, Vec<String>> { - let mut templates: FxHashMap<PathBuf, Vec<String>> = FxHashMap::default(); +) -> FxIndexMap<PathBuf, Vec<String>> { + let mut templates: FxIndexMap<PathBuf, Vec<String>> = FxIndexMap::default(); crates_info .iter() .map(|crate_info| T::from_crate_info(crate_info).parts.iter()) diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index 7be9cc0b885..f4a0ef01c25 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use std::{fmt, fs}; use rinja::Template; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::ty::TyCtxt; use rustc_session::Session; @@ -39,15 +39,15 @@ pub(crate) fn collect_local_sources<'tcx>( tcx: TyCtxt<'tcx>, src_root: &Path, krate: &clean::Crate, -) -> FxHashMap<PathBuf, String> { - let mut lsc = LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root }; +) -> FxIndexMap<PathBuf, String> { + let mut lsc = LocalSourcesCollector { tcx, local_sources: FxIndexMap::default(), src_root }; lsc.visit_crate(krate); lsc.local_sources } struct LocalSourcesCollector<'a, 'tcx> { tcx: TyCtxt<'tcx>, - local_sources: FxHashMap<PathBuf, String>, + local_sources: FxIndexMap<PathBuf, String>, src_root: &'a Path, } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index beac7e73c62..df9776ff5f8 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -230,6 +230,9 @@ h4.code-header { padding: 0; white-space: pre-wrap; } +.structfield { + margin: 0.6em 0; +} #crate-search, h1, h2, h3, h4, h5, h6, @@ -961,10 +964,13 @@ pre, .rustdoc.src .example-wrap, .example-wrap .src-line-numbers { } .docblock li { - margin-bottom: .8em; + margin-bottom: .4em; } -.docblock li p { - margin-bottom: .1em; +.docblock li p:not(:last-child) { + /* This margin is voluntarily smaller than `.docblock li` to keep the visual + list element items separated while also having a visual separation (although + smaller) for paragraphs. */ + margin-bottom: .3em; } /* "where ..." clauses with block display are also smaller */ @@ -2432,7 +2438,7 @@ in src-script.js and main.js } /* Position of the "[-]" element. */ - details.toggle:not(.top-doc) > summary { + details.toggle:not(.top-doc) > summary, .impl-items > section { margin-left: 10px; } .impl-items > details.toggle > summary:not(.hideme)::before, diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index b411f9a1a52..b8791c9918b 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -4,20 +4,17 @@ #![allow(rustc::default_hash_types)] -use std::fmt; - use rustc_ast::ast; use rustc_attr::DeprecatedSince; use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def_id::DefId; use rustc_metadata::rendered_const; -use rustc_middle::bug; -use rustc_middle::ty::{self, TyCtxt}; -use rustc_span::symbol::sym; -use rustc_span::{Pos, Symbol}; +use rustc_middle::{bug, ty}; +use rustc_span::{Pos, Symbol, sym}; use rustc_target::spec::abi::Abi as RustcAbi; use rustdoc_json_types::*; +use super::FullItemId; use crate::clean::{self, ItemId}; use crate::formats::FormatRenderer; use crate::formats::item_type::ItemType; @@ -40,7 +37,7 @@ impl JsonRenderer<'_> { Some(UrlFragment::UserWritten(_)) | None => *page_id, }; - (String::from(&**link), id_from_item_default(id.into(), self.tcx)) + (String::from(&**link), self.id_from_item_default(id.into())) }) .collect(); let docs = item.opt_doc_value(); @@ -48,7 +45,7 @@ impl JsonRenderer<'_> { let span = item.span(self.tcx); let visibility = item.visibility(self.tcx); let clean::Item { name, item_id, .. } = item; - let id = id_from_item(&item, self.tcx); + let id = self.id_from_item(&item); let inner = match item.kind { clean::KeywordItem => return None, clean::StrippedItem(ref inner) => { @@ -59,12 +56,12 @@ impl JsonRenderer<'_> { clean::ModuleItem(_) if self.imported_items.contains(&item_id.expect_def_id()) => { - from_clean_item(item, self.tcx) + from_clean_item(item, self) } _ => return None, } } - _ => from_clean_item(item, self.tcx), + _ => from_clean_item(item, self), }; Some(Item { id, @@ -105,37 +102,116 @@ impl JsonRenderer<'_> { Some(ty::Visibility::Public) => Visibility::Public, Some(ty::Visibility::Restricted(did)) if did.is_crate_root() => Visibility::Crate, Some(ty::Visibility::Restricted(did)) => Visibility::Restricted { - parent: id_from_item_default(did.into(), self.tcx), + parent: self.id_from_item_default(did.into()), path: self.tcx.def_path(did).to_string_no_crate_verbose(), }, } } + + pub(crate) fn id_from_item_default(&self, item_id: ItemId) -> Id { + self.id_from_item_inner(item_id, None, None) + } + + pub(crate) fn id_from_item_inner( + &self, + item_id: ItemId, + name: Option<Symbol>, + extra: Option<Id>, + ) -> Id { + let make_part = |def_id: DefId, name: Option<Symbol>, extra: Option<Id>| { + let name = match name { + Some(name) => Some(name), + None => { + // We need this workaround because primitive types' DefId actually refers to + // their parent module, which isn't present in the output JSON items. So + // instead, we directly get the primitive symbol + if matches!(self.tcx.def_kind(def_id), DefKind::Mod) + && let Some(prim) = self + .tcx + .get_attrs(def_id, sym::rustc_doc_primitive) + .find_map(|attr| attr.value_str()) + { + Some(prim) + } else { + self.tcx.opt_item_name(def_id) + } + } + }; + + FullItemId { def_id, name, extra } + }; + + let key = match item_id { + ItemId::DefId(did) => (make_part(did, name, extra), None), + ItemId::Blanket { for_, impl_id } => { + (make_part(impl_id, None, None), Some(make_part(for_, name, extra))) + } + ItemId::Auto { for_, trait_ } => { + (make_part(trait_, None, None), Some(make_part(for_, name, extra))) + } + }; + + let mut interner = self.id_interner.borrow_mut(); + let len = interner.len(); + *interner + .entry(key) + .or_insert_with(|| Id(len.try_into().expect("too many items in a crate"))) + } + + pub(crate) fn id_from_item(&self, item: &clean::Item) -> Id { + match item.kind { + clean::ItemKind::ImportItem(ref import) => { + let extra = + import.source.did.map(ItemId::from).map(|i| self.id_from_item_default(i)); + self.id_from_item_inner(item.item_id, item.name, extra) + } + _ => self.id_from_item_inner(item.item_id, item.name, None), + } + } + + fn ids(&self, items: impl IntoIterator<Item = clean::Item>) -> Vec<Id> { + items + .into_iter() + .filter(|x| !x.is_stripped() && !x.is_keyword()) + .map(|i| self.id_from_item(&i)) + .collect() + } + + fn ids_keeping_stripped( + &self, + items: impl IntoIterator<Item = clean::Item>, + ) -> Vec<Option<Id>> { + items + .into_iter() + .map(|i| (!i.is_stripped() && !i.is_keyword()).then(|| self.id_from_item(&i))) + .collect() + } } -pub(crate) trait FromWithTcx<T> { - fn from_tcx(f: T, tcx: TyCtxt<'_>) -> Self; +pub(crate) trait FromClean<T> { + fn from_clean(f: T, renderer: &JsonRenderer<'_>) -> Self; } -pub(crate) trait IntoWithTcx<T> { - fn into_tcx(self, tcx: TyCtxt<'_>) -> T; +pub(crate) trait IntoJson<T> { + fn into_json(self, renderer: &JsonRenderer<'_>) -> T; } -impl<T, U> IntoWithTcx<U> for T +impl<T, U> IntoJson<U> for T where - U: FromWithTcx<T>, + U: FromClean<T>, { - fn into_tcx(self, tcx: TyCtxt<'_>) -> U { - U::from_tcx(self, tcx) + fn into_json(self, renderer: &JsonRenderer<'_>) -> U { + U::from_clean(self, renderer) } } -impl<I, T, U> FromWithTcx<I> for Vec<U> +impl<I, T, U> FromClean<I> for Vec<U> where I: IntoIterator<Item = T>, - U: FromWithTcx<T>, + U: FromClean<T>, { - fn from_tcx(f: I, tcx: TyCtxt<'_>) -> Vec<U> { - f.into_iter().map(|x| x.into_tcx(tcx)).collect() + fn from_clean(f: I, renderer: &JsonRenderer<'_>) -> Vec<U> { + f.into_iter().map(|x| x.into_json(renderer)).collect() } } @@ -150,37 +226,38 @@ pub(crate) fn from_deprecation(deprecation: rustc_attr::Deprecation) -> Deprecat Deprecation { since, note: note.map(|s| s.to_string()) } } -impl FromWithTcx<clean::GenericArgs> for GenericArgs { - fn from_tcx(args: clean::GenericArgs, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::GenericArgs> for GenericArgs { + fn from_clean(args: clean::GenericArgs, renderer: &JsonRenderer<'_>) -> Self { use clean::GenericArgs::*; match args { AngleBracketed { args, constraints } => GenericArgs::AngleBracketed { - args: args.into_vec().into_tcx(tcx), - constraints: constraints.into_tcx(tcx), + args: args.into_vec().into_json(renderer), + constraints: constraints.into_json(renderer), }, Parenthesized { inputs, output } => GenericArgs::Parenthesized { - inputs: inputs.into_vec().into_tcx(tcx), - output: output.map(|a| (*a).into_tcx(tcx)), + inputs: inputs.into_vec().into_json(renderer), + output: output.map(|a| (*a).into_json(renderer)), }, } } } -impl FromWithTcx<clean::GenericArg> for GenericArg { - fn from_tcx(arg: clean::GenericArg, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::GenericArg> for GenericArg { + fn from_clean(arg: clean::GenericArg, renderer: &JsonRenderer<'_>) -> Self { use clean::GenericArg::*; match arg { Lifetime(l) => GenericArg::Lifetime(convert_lifetime(l)), - Type(t) => GenericArg::Type(t.into_tcx(tcx)), - Const(box c) => GenericArg::Const(c.into_tcx(tcx)), + Type(t) => GenericArg::Type(t.into_json(renderer)), + Const(box c) => GenericArg::Const(c.into_json(renderer)), Infer => GenericArg::Infer, } } } -impl FromWithTcx<clean::Constant> for Constant { +impl FromClean<clean::Constant> for Constant { // FIXME(generic_const_items): Add support for generic const items. - fn from_tcx(constant: clean::Constant, tcx: TyCtxt<'_>) -> Self { + fn from_clean(constant: clean::Constant, renderer: &JsonRenderer<'_>) -> Self { + let tcx = renderer.tcx; let expr = constant.expr(tcx); let value = constant.value(tcx); let is_literal = constant.is_literal(tcx); @@ -188,9 +265,10 @@ impl FromWithTcx<clean::Constant> for Constant { } } -impl FromWithTcx<clean::ConstantKind> for Constant { +impl FromClean<clean::ConstantKind> for Constant { // FIXME(generic_const_items): Add support for generic const items. - fn from_tcx(constant: clean::ConstantKind, tcx: TyCtxt<'_>) -> Self { + fn from_clean(constant: clean::ConstantKind, renderer: &JsonRenderer<'_>) -> Self { + let tcx = renderer.tcx; let expr = constant.expr(tcx); let value = constant.value(tcx); let is_literal = constant.is_literal(tcx); @@ -198,147 +276,62 @@ impl FromWithTcx<clean::ConstantKind> for Constant { } } -impl FromWithTcx<clean::AssocItemConstraint> for AssocItemConstraint { - fn from_tcx(constraint: clean::AssocItemConstraint, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::AssocItemConstraint> for AssocItemConstraint { + fn from_clean(constraint: clean::AssocItemConstraint, renderer: &JsonRenderer<'_>) -> Self { AssocItemConstraint { name: constraint.assoc.name.to_string(), - args: constraint.assoc.args.into_tcx(tcx), - binding: constraint.kind.into_tcx(tcx), + args: constraint.assoc.args.into_json(renderer), + binding: constraint.kind.into_json(renderer), } } } -impl FromWithTcx<clean::AssocItemConstraintKind> for AssocItemConstraintKind { - fn from_tcx(kind: clean::AssocItemConstraintKind, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::AssocItemConstraintKind> for AssocItemConstraintKind { + fn from_clean(kind: clean::AssocItemConstraintKind, renderer: &JsonRenderer<'_>) -> Self { use clean::AssocItemConstraintKind::*; match kind { - Equality { term } => AssocItemConstraintKind::Equality(term.into_tcx(tcx)), - Bound { bounds } => AssocItemConstraintKind::Constraint(bounds.into_tcx(tcx)), - } - } -} - -#[inline] -pub(crate) fn id_from_item_default(item_id: ItemId, tcx: TyCtxt<'_>) -> Id { - id_from_item_inner(item_id, tcx, None, None) -} - -/// It generates an ID as follows: -/// -/// `CRATE_ID:ITEM_ID[:NAME_ID][-EXTRA]`: -/// * If there is no `name`, `NAME_ID` is not generated. -/// * If there is no `extra`, `EXTRA` is not generated. -/// -/// * `name` is the item's name if available (it's not for impl blocks for example). -/// * `extra` is used for reexports: it contains the ID of the reexported item. It is used to allow -/// to have items with the same name but different types to both appear in the generated JSON. -pub(crate) fn id_from_item_inner( - item_id: ItemId, - tcx: TyCtxt<'_>, - name: Option<Symbol>, - extra: Option<&Id>, -) -> Id { - struct DisplayDefId<'a, 'b>(DefId, TyCtxt<'a>, Option<&'b Id>, Option<Symbol>); - - impl<'a, 'b> fmt::Display for DisplayDefId<'a, 'b> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let DisplayDefId(def_id, tcx, extra, name) = self; - // We need this workaround because primitive types' DefId actually refers to - // their parent module, which isn't present in the output JSON items. So - // instead, we directly get the primitive symbol and convert it to u32 to - // generate the ID. - let s; - let extra = if let Some(e) = extra { - s = format!("-{}", e.0); - &s - } else { - "" - }; - let name = match name { - Some(name) => format!(":{}", name.as_u32()), - None => { - // We need this workaround because primitive types' DefId actually refers to - // their parent module, which isn't present in the output JSON items. So - // instead, we directly get the primitive symbol and convert it to u32 to - // generate the ID. - if matches!(tcx.def_kind(def_id), DefKind::Mod) - && let Some(prim) = tcx - .get_attrs(*def_id, sym::rustc_doc_primitive) - .find_map(|attr| attr.value_str()) - { - format!(":{}", prim.as_u32()) - } else { - tcx.opt_item_name(*def_id) - .map(|n| format!(":{}", n.as_u32())) - .unwrap_or_default() - } - } - }; - write!(f, "{}:{}{name}{extra}", def_id.krate.as_u32(), u32::from(def_id.index)) - } - } - - match item_id { - ItemId::DefId(did) => Id(format!("{}", DisplayDefId(did, tcx, extra, name))), - ItemId::Blanket { for_, impl_id } => Id(format!( - "b:{}-{}", - DisplayDefId(impl_id, tcx, None, None), - DisplayDefId(for_, tcx, extra, name) - )), - ItemId::Auto { for_, trait_ } => Id(format!( - "a:{}-{}", - DisplayDefId(trait_, tcx, None, None), - DisplayDefId(for_, tcx, extra, name) - )), - } -} - -pub(crate) fn id_from_item(item: &clean::Item, tcx: TyCtxt<'_>) -> Id { - match item.kind { - clean::ItemKind::ImportItem(ref import) => { - let extra = - import.source.did.map(ItemId::from).map(|i| id_from_item_inner(i, tcx, None, None)); - id_from_item_inner(item.item_id, tcx, item.name, extra.as_ref()) + Equality { term } => AssocItemConstraintKind::Equality(term.into_json(renderer)), + Bound { bounds } => AssocItemConstraintKind::Constraint(bounds.into_json(renderer)), } - _ => id_from_item_inner(item.item_id, tcx, item.name, None), } } -fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { +fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum { use clean::ItemKind::*; let name = item.name; let is_crate = item.is_crate(); - let header = item.fn_header(tcx); + let header = item.fn_header(renderer.tcx); match item.inner.kind { ModuleItem(m) => { - ItemEnum::Module(Module { is_crate, items: ids(m.items, tcx), is_stripped: false }) - } - ImportItem(i) => ItemEnum::Use(i.into_tcx(tcx)), - StructItem(s) => ItemEnum::Struct(s.into_tcx(tcx)), - UnionItem(u) => ItemEnum::Union(u.into_tcx(tcx)), - StructFieldItem(f) => ItemEnum::StructField(f.into_tcx(tcx)), - EnumItem(e) => ItemEnum::Enum(e.into_tcx(tcx)), - VariantItem(v) => ItemEnum::Variant(v.into_tcx(tcx)), - FunctionItem(f) => ItemEnum::Function(from_function(f, true, header.unwrap(), tcx)), + ItemEnum::Module(Module { is_crate, items: renderer.ids(m.items), is_stripped: false }) + } + ImportItem(i) => ItemEnum::Use(i.into_json(renderer)), + StructItem(s) => ItemEnum::Struct(s.into_json(renderer)), + UnionItem(u) => ItemEnum::Union(u.into_json(renderer)), + StructFieldItem(f) => ItemEnum::StructField(f.into_json(renderer)), + EnumItem(e) => ItemEnum::Enum(e.into_json(renderer)), + VariantItem(v) => ItemEnum::Variant(v.into_json(renderer)), + FunctionItem(f) => ItemEnum::Function(from_function(f, true, header.unwrap(), renderer)), ForeignFunctionItem(f, _) => { - ItemEnum::Function(from_function(f, false, header.unwrap(), tcx)) - } - TraitItem(t) => ItemEnum::Trait((*t).into_tcx(tcx)), - TraitAliasItem(t) => ItemEnum::TraitAlias(t.into_tcx(tcx)), - MethodItem(m, _) => ItemEnum::Function(from_function(m, true, header.unwrap(), tcx)), - TyMethodItem(m) => ItemEnum::Function(from_function(m, false, header.unwrap(), tcx)), - ImplItem(i) => ItemEnum::Impl((*i).into_tcx(tcx)), - StaticItem(s) => ItemEnum::Static(s.into_tcx(tcx)), - ForeignStaticItem(s, _) => ItemEnum::Static(s.into_tcx(tcx)), + ItemEnum::Function(from_function(f, false, header.unwrap(), renderer)) + } + TraitItem(t) => ItemEnum::Trait((*t).into_json(renderer)), + TraitAliasItem(t) => ItemEnum::TraitAlias(t.into_json(renderer)), + MethodItem(m, _) => ItemEnum::Function(from_function(m, true, header.unwrap(), renderer)), + TyMethodItem(m) => ItemEnum::Function(from_function(m, false, header.unwrap(), renderer)), + ImplItem(i) => ItemEnum::Impl((*i).into_json(renderer)), + StaticItem(s) => ItemEnum::Static(s.into_json(renderer)), + ForeignStaticItem(s, _) => ItemEnum::Static(s.into_json(renderer)), ForeignTypeItem => ItemEnum::ExternType, - TypeAliasItem(t) => ItemEnum::TypeAlias(t.into_tcx(tcx)), + TypeAliasItem(t) => ItemEnum::TypeAlias(t.into_json(renderer)), // FIXME(generic_const_items): Add support for generic free consts - ConstantItem(ci) => { - ItemEnum::Constant { type_: ci.type_.into_tcx(tcx), const_: ci.kind.into_tcx(tcx) } - } + ConstantItem(ci) => ItemEnum::Constant { + type_: ci.type_.into_json(renderer), + const_: ci.kind.into_json(renderer), + }, MacroItem(m) => ItemEnum::Macro(m.source), - ProcMacroItem(m) => ItemEnum::ProcMacro(m.into_tcx(tcx)), + ProcMacroItem(m) => ItemEnum::ProcMacro(m.into_json(renderer)), PrimitiveItem(p) => { ItemEnum::Primitive(Primitive { name: p.as_sym().to_string(), @@ -347,19 +340,22 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { } // FIXME(generic_const_items): Add support for generic associated consts. TyAssocConstItem(_generics, ty) => { - ItemEnum::AssocConst { type_: (*ty).into_tcx(tcx), value: None } + ItemEnum::AssocConst { type_: (*ty).into_json(renderer), value: None } } // FIXME(generic_const_items): Add support for generic associated consts. - AssocConstItem(ci) => { - ItemEnum::AssocConst { type_: ci.type_.into_tcx(tcx), value: Some(ci.kind.expr(tcx)) } - } - TyAssocTypeItem(g, b) => { - ItemEnum::AssocType { generics: g.into_tcx(tcx), bounds: b.into_tcx(tcx), type_: None } - } + AssocConstItem(ci) => ItemEnum::AssocConst { + type_: ci.type_.into_json(renderer), + value: Some(ci.kind.expr(renderer.tcx)), + }, + TyAssocTypeItem(g, b) => ItemEnum::AssocType { + generics: g.into_json(renderer), + bounds: b.into_json(renderer), + type_: None, + }, AssocTypeItem(t, b) => ItemEnum::AssocType { - generics: t.generics.into_tcx(tcx), - bounds: b.into_tcx(tcx), - type_: Some(t.item_type.unwrap_or(t.type_).into_tcx(tcx)), + generics: t.generics.into_json(renderer), + bounds: b.into_json(renderer), + type_: Some(t.item_type.unwrap_or(t.type_).into_json(renderer)), }, // `convert_item` early returns `None` for stripped items and keywords. KeywordItem => unreachable!(), @@ -367,7 +363,7 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { match *inner { ModuleItem(m) => ItemEnum::Module(Module { is_crate, - items: ids(m.items, tcx), + items: renderer.ids(m.items), is_stripped: true, }), // `convert_item` early returns `None` for stripped items we're not including @@ -381,36 +377,36 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { } } -impl FromWithTcx<clean::Struct> for Struct { - fn from_tcx(struct_: clean::Struct, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Struct> for Struct { + fn from_clean(struct_: clean::Struct, renderer: &JsonRenderer<'_>) -> Self { let has_stripped_fields = struct_.has_stripped_entries(); let clean::Struct { ctor_kind, generics, fields } = struct_; let kind = match ctor_kind { - Some(CtorKind::Fn) => StructKind::Tuple(ids_keeping_stripped(fields, tcx)), + Some(CtorKind::Fn) => StructKind::Tuple(renderer.ids_keeping_stripped(fields)), Some(CtorKind::Const) => { assert!(fields.is_empty()); StructKind::Unit } - None => StructKind::Plain { fields: ids(fields, tcx), has_stripped_fields }, + None => StructKind::Plain { fields: renderer.ids(fields), has_stripped_fields }, }; Struct { kind, - generics: generics.into_tcx(tcx), + generics: generics.into_json(renderer), impls: Vec::new(), // Added in JsonRenderer::item } } } -impl FromWithTcx<clean::Union> for Union { - fn from_tcx(union_: clean::Union, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Union> for Union { + fn from_clean(union_: clean::Union, renderer: &JsonRenderer<'_>) -> Self { let has_stripped_fields = union_.has_stripped_entries(); let clean::Union { generics, fields } = union_; Union { - generics: generics.into_tcx(tcx), + generics: generics.into_json(renderer), has_stripped_fields, - fields: ids(fields, tcx), + fields: renderer.ids(fields), impls: Vec::new(), // Added in JsonRenderer::item } } @@ -444,51 +440,51 @@ fn convert_lifetime(l: clean::Lifetime) -> String { l.0.to_string() } -impl FromWithTcx<clean::Generics> for Generics { - fn from_tcx(generics: clean::Generics, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Generics> for Generics { + fn from_clean(generics: clean::Generics, renderer: &JsonRenderer<'_>) -> Self { Generics { - params: generics.params.into_tcx(tcx), - where_predicates: generics.where_predicates.into_tcx(tcx), + params: generics.params.into_json(renderer), + where_predicates: generics.where_predicates.into_json(renderer), } } } -impl FromWithTcx<clean::GenericParamDef> for GenericParamDef { - fn from_tcx(generic_param: clean::GenericParamDef, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::GenericParamDef> for GenericParamDef { + fn from_clean(generic_param: clean::GenericParamDef, renderer: &JsonRenderer<'_>) -> Self { GenericParamDef { name: generic_param.name.to_string(), - kind: generic_param.kind.into_tcx(tcx), + kind: generic_param.kind.into_json(renderer), } } } -impl FromWithTcx<clean::GenericParamDefKind> for GenericParamDefKind { - fn from_tcx(kind: clean::GenericParamDefKind, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::GenericParamDefKind> for GenericParamDefKind { + fn from_clean(kind: clean::GenericParamDefKind, renderer: &JsonRenderer<'_>) -> Self { use clean::GenericParamDefKind::*; match kind { Lifetime { outlives } => GenericParamDefKind::Lifetime { outlives: outlives.into_iter().map(convert_lifetime).collect(), }, Type { bounds, default, synthetic } => GenericParamDefKind::Type { - bounds: bounds.into_tcx(tcx), - default: default.map(|x| (*x).into_tcx(tcx)), + bounds: bounds.into_json(renderer), + default: default.map(|x| (*x).into_json(renderer)), is_synthetic: synthetic, }, Const { ty, default, synthetic: _ } => GenericParamDefKind::Const { - type_: (*ty).into_tcx(tcx), + type_: (*ty).into_json(renderer), default: default.map(|x| *x), }, } } } -impl FromWithTcx<clean::WherePredicate> for WherePredicate { - fn from_tcx(predicate: clean::WherePredicate, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::WherePredicate> for WherePredicate { + fn from_clean(predicate: clean::WherePredicate, renderer: &JsonRenderer<'_>) -> Self { use clean::WherePredicate::*; match predicate { BoundPredicate { ty, bounds, bound_params } => WherePredicate::BoundPredicate { - type_: ty.into_tcx(tcx), - bounds: bounds.into_tcx(tcx), + type_: ty.into_json(renderer), + bounds: bounds.into_json(renderer), generic_params: bound_params .into_iter() .map(|x| { @@ -503,15 +499,15 @@ impl FromWithTcx<clean::WherePredicate> for WherePredicate { GenericParamDefKind::Type { bounds: bounds .into_iter() - .map(|bound| bound.into_tcx(tcx)) + .map(|bound| bound.into_json(renderer)) .collect(), - default: default.map(|ty| (*ty).into_tcx(tcx)), + default: default.map(|ty| (*ty).into_json(renderer)), is_synthetic: synthetic, } } clean::GenericParamDefKind::Const { ty, default, synthetic: _ } => { GenericParamDefKind::Const { - type_: (*ty).into_tcx(tcx), + type_: (*ty).into_json(renderer), default: default.map(|d| *d), } } @@ -530,21 +526,22 @@ impl FromWithTcx<clean::WherePredicate> for WherePredicate { }) .collect(), }, - EqPredicate { lhs, rhs } => { - WherePredicate::EqPredicate { lhs: lhs.into_tcx(tcx), rhs: rhs.into_tcx(tcx) } - } + EqPredicate { lhs, rhs } => WherePredicate::EqPredicate { + lhs: lhs.into_json(renderer), + rhs: rhs.into_json(renderer), + }, } } } -impl FromWithTcx<clean::GenericBound> for GenericBound { - fn from_tcx(bound: clean::GenericBound, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::GenericBound> for GenericBound { + fn from_clean(bound: clean::GenericBound, renderer: &JsonRenderer<'_>) -> Self { use clean::GenericBound::*; match bound { TraitBound(clean::PolyTrait { trait_, generic_params }, modifier) => { GenericBound::TraitBound { - trait_: trait_.into_tcx(tcx), - generic_params: generic_params.into_tcx(tcx), + trait_: trait_.into_json(renderer), + generic_params: generic_params.into_json(renderer), modifier: from_trait_bound_modifier(modifier), } } @@ -572,73 +569,75 @@ pub(crate) fn from_trait_bound_modifier( } } -impl FromWithTcx<clean::Type> for Type { - fn from_tcx(ty: clean::Type, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Type> for Type { + fn from_clean(ty: clean::Type, renderer: &JsonRenderer<'_>) -> Self { use clean::Type::{ Array, BareFunction, BorrowedRef, Generic, ImplTrait, Infer, Primitive, QPath, RawPointer, SelfTy, Slice, Tuple, }; match ty { - clean::Type::Path { path } => Type::ResolvedPath(path.into_tcx(tcx)), + clean::Type::Path { path } => Type::ResolvedPath(path.into_json(renderer)), clean::Type::DynTrait(bounds, lt) => Type::DynTrait(DynTrait { lifetime: lt.map(convert_lifetime), - traits: bounds.into_tcx(tcx), + traits: bounds.into_json(renderer), }), Generic(s) => Type::Generic(s.to_string()), // FIXME: add dedicated variant to json Type? SelfTy => Type::Generic("Self".to_owned()), Primitive(p) => Type::Primitive(p.as_sym().to_string()), - BareFunction(f) => Type::FunctionPointer(Box::new((*f).into_tcx(tcx))), - Tuple(t) => Type::Tuple(t.into_tcx(tcx)), - Slice(t) => Type::Slice(Box::new((*t).into_tcx(tcx))), - Array(t, s) => Type::Array { type_: Box::new((*t).into_tcx(tcx)), len: s.to_string() }, + BareFunction(f) => Type::FunctionPointer(Box::new((*f).into_json(renderer))), + Tuple(t) => Type::Tuple(t.into_json(renderer)), + Slice(t) => Type::Slice(Box::new((*t).into_json(renderer))), + Array(t, s) => { + Type::Array { type_: Box::new((*t).into_json(renderer)), len: s.to_string() } + } clean::Type::Pat(t, p) => Type::Pat { - type_: Box::new((*t).into_tcx(tcx)), + type_: Box::new((*t).into_json(renderer)), __pat_unstable_do_not_use: p.to_string(), }, - ImplTrait(g) => Type::ImplTrait(g.into_tcx(tcx)), + ImplTrait(g) => Type::ImplTrait(g.into_json(renderer)), Infer => Type::Infer, RawPointer(mutability, type_) => Type::RawPointer { is_mutable: mutability == ast::Mutability::Mut, - type_: Box::new((*type_).into_tcx(tcx)), + type_: Box::new((*type_).into_json(renderer)), }, BorrowedRef { lifetime, mutability, type_ } => Type::BorrowedRef { lifetime: lifetime.map(convert_lifetime), is_mutable: mutability == ast::Mutability::Mut, - type_: Box::new((*type_).into_tcx(tcx)), + type_: Box::new((*type_).into_json(renderer)), }, QPath(box clean::QPathData { assoc, self_type, trait_, .. }) => Type::QualifiedPath { name: assoc.name.to_string(), - args: Box::new(assoc.args.into_tcx(tcx)), - self_type: Box::new(self_type.into_tcx(tcx)), - trait_: trait_.map(|trait_| trait_.into_tcx(tcx)), + args: Box::new(assoc.args.into_json(renderer)), + self_type: Box::new(self_type.into_json(renderer)), + trait_: trait_.map(|trait_| trait_.into_json(renderer)), }, } } } -impl FromWithTcx<clean::Path> for Path { - fn from_tcx(path: clean::Path, tcx: TyCtxt<'_>) -> Path { +impl FromClean<clean::Path> for Path { + fn from_clean(path: clean::Path, renderer: &JsonRenderer<'_>) -> Path { Path { name: path.whole_name(), - id: id_from_item_default(path.def_id().into(), tcx), - args: path.segments.last().map(|args| Box::new(args.clone().args.into_tcx(tcx))), + id: renderer.id_from_item_default(path.def_id().into()), + args: path.segments.last().map(|args| Box::new(args.clone().args.into_json(renderer))), } } } -impl FromWithTcx<clean::Term> for Term { - fn from_tcx(term: clean::Term, tcx: TyCtxt<'_>) -> Term { +impl FromClean<clean::Term> for Term { + fn from_clean(term: clean::Term, renderer: &JsonRenderer<'_>) -> Term { match term { - clean::Term::Type(ty) => Term::Type(FromWithTcx::from_tcx(ty, tcx)), - clean::Term::Constant(c) => Term::Constant(FromWithTcx::from_tcx(c, tcx)), + clean::Term::Type(ty) => Term::Type(ty.into_json(renderer)), + clean::Term::Constant(c) => Term::Constant(c.into_json(renderer)), } } } -impl FromWithTcx<clean::BareFunctionDecl> for FunctionPointer { - fn from_tcx(bare_decl: clean::BareFunctionDecl, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::BareFunctionDecl> for FunctionPointer { + fn from_clean(bare_decl: clean::BareFunctionDecl, renderer: &JsonRenderer<'_>) -> Self { let clean::BareFunctionDecl { safety, generic_params, decl, abi } = bare_decl; FunctionPointer { header: FunctionHeader { @@ -647,29 +646,30 @@ impl FromWithTcx<clean::BareFunctionDecl> for FunctionPointer { is_async: false, abi: convert_abi(abi), }, - generic_params: generic_params.into_tcx(tcx), - sig: decl.into_tcx(tcx), + generic_params: generic_params.into_json(renderer), + sig: decl.into_json(renderer), } } } -impl FromWithTcx<clean::FnDecl> for FunctionSignature { - fn from_tcx(decl: clean::FnDecl, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::FnDecl> for FunctionSignature { + fn from_clean(decl: clean::FnDecl, renderer: &JsonRenderer<'_>) -> Self { let clean::FnDecl { inputs, output, c_variadic } = decl; FunctionSignature { inputs: inputs .values .into_iter() - .map(|arg| (arg.name.to_string(), arg.type_.into_tcx(tcx))) + .map(|arg| (arg.name.to_string(), arg.type_.into_json(renderer))) .collect(), - output: if output.is_unit() { None } else { Some(output.into_tcx(tcx)) }, + output: if output.is_unit() { None } else { Some(output.into_json(renderer)) }, is_c_variadic: c_variadic, } } } -impl FromWithTcx<clean::Trait> for Trait { - fn from_tcx(trait_: clean::Trait, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Trait> for Trait { + fn from_clean(trait_: clean::Trait, renderer: &JsonRenderer<'_>) -> Self { + 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_object_safe(tcx); @@ -678,26 +678,29 @@ impl FromWithTcx<clean::Trait> for Trait { is_auto, is_unsafe, is_object_safe, - items: ids(items, tcx), - generics: generics.into_tcx(tcx), - bounds: bounds.into_tcx(tcx), + items: renderer.ids(items), + generics: generics.into_json(renderer), + bounds: bounds.into_json(renderer), implementations: Vec::new(), // Added in JsonRenderer::item } } } -impl FromWithTcx<clean::PolyTrait> for PolyTrait { - fn from_tcx( +impl FromClean<clean::PolyTrait> for PolyTrait { + fn from_clean( clean::PolyTrait { trait_, generic_params }: clean::PolyTrait, - tcx: TyCtxt<'_>, + renderer: &JsonRenderer<'_>, ) -> Self { - PolyTrait { trait_: trait_.into_tcx(tcx), generic_params: generic_params.into_tcx(tcx) } + PolyTrait { + trait_: trait_.into_json(renderer), + generic_params: generic_params.into_json(renderer), + } } } -impl FromWithTcx<clean::Impl> for Impl { - fn from_tcx(impl_: clean::Impl, tcx: TyCtxt<'_>) -> Self { - let provided_trait_methods = impl_.provided_trait_methods(tcx); +impl FromClean<clean::Impl> for Impl { + fn from_clean(impl_: clean::Impl, renderer: &JsonRenderer<'_>) -> Self { + let provided_trait_methods = impl_.provided_trait_methods(renderer.tcx); let clean::Impl { safety, generics, trait_, for_, items, polarity, kind } = impl_; // FIXME: use something like ImplKind in JSON? let (is_synthetic, blanket_impl) = match kind { @@ -711,17 +714,17 @@ impl FromWithTcx<clean::Impl> for Impl { }; Impl { is_unsafe: safety == rustc_hir::Safety::Unsafe, - generics: generics.into_tcx(tcx), + generics: generics.into_json(renderer), provided_trait_methods: provided_trait_methods .into_iter() .map(|x| x.to_string()) .collect(), - trait_: trait_.map(|path| path.into_tcx(tcx)), - for_: for_.into_tcx(tcx), - items: ids(items, tcx), + trait_: trait_.map(|path| path.into_json(renderer)), + for_: for_.into_json(renderer), + items: renderer.ids(items), is_negative, is_synthetic, - blanket_impl: blanket_impl.map(|x| x.into_tcx(tcx)), + blanket_impl: blanket_impl.map(|x| x.into_json(renderer)), } } } @@ -730,42 +733,42 @@ pub(crate) fn from_function( function: Box<clean::Function>, has_body: bool, header: rustc_hir::FnHeader, - tcx: TyCtxt<'_>, + renderer: &JsonRenderer<'_>, ) -> Function { let clean::Function { decl, generics } = *function; Function { - sig: decl.into_tcx(tcx), - generics: generics.into_tcx(tcx), + sig: decl.into_json(renderer), + generics: generics.into_json(renderer), header: from_fn_header(&header), has_body, } } -impl FromWithTcx<clean::Enum> for Enum { - fn from_tcx(enum_: clean::Enum, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Enum> for Enum { + fn from_clean(enum_: clean::Enum, renderer: &JsonRenderer<'_>) -> Self { let has_stripped_variants = enum_.has_stripped_entries(); let clean::Enum { variants, generics } = enum_; Enum { - generics: generics.into_tcx(tcx), + generics: generics.into_json(renderer), has_stripped_variants, - variants: ids(variants, tcx), + variants: renderer.ids(variants), impls: Vec::new(), // Added in JsonRenderer::item } } } -impl FromWithTcx<clean::Variant> for Variant { - fn from_tcx(variant: clean::Variant, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Variant> for Variant { + fn from_clean(variant: clean::Variant, renderer: &JsonRenderer<'_>) -> Self { use clean::VariantKind::*; - let discriminant = variant.discriminant.map(|d| d.into_tcx(tcx)); + let discriminant = variant.discriminant.map(|d| d.into_json(renderer)); let kind = match variant.kind { CLike => VariantKind::Plain, - Tuple(fields) => VariantKind::Tuple(ids_keeping_stripped(fields, tcx)), + Tuple(fields) => VariantKind::Tuple(renderer.ids_keeping_stripped(fields)), Struct(s) => VariantKind::Struct { has_stripped_fields: s.has_stripped_entries(), - fields: ids(s.fields, tcx), + fields: renderer.ids(s.fields), }, }; @@ -773,8 +776,9 @@ impl FromWithTcx<clean::Variant> for Variant { } } -impl FromWithTcx<clean::Discriminant> for Discriminant { - fn from_tcx(disr: clean::Discriminant, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Discriminant> for Discriminant { + fn from_clean(disr: clean::Discriminant, renderer: &JsonRenderer<'_>) -> Self { + let tcx = renderer.tcx; Discriminant { // expr is only none if going through the inlining path, which gets // `rustc_middle` types, not `rustc_hir`, but because JSON never inlines @@ -785,8 +789,8 @@ impl FromWithTcx<clean::Discriminant> for Discriminant { } } -impl FromWithTcx<clean::Import> for Use { - fn from_tcx(import: clean::Import, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Import> for Use { + fn from_clean(import: clean::Import, renderer: &JsonRenderer<'_>) -> Self { use clean::ImportKind::*; let (name, is_glob) = match import.kind { Simple(s) => (s.to_string(), false), @@ -798,14 +802,14 @@ impl FromWithTcx<clean::Import> for Use { Use { source: import.source.path.whole_name(), name, - id: import.source.did.map(ItemId::from).map(|i| id_from_item_default(i, tcx)), + id: import.source.did.map(ItemId::from).map(|i| renderer.id_from_item_default(i)), is_glob, } } } -impl FromWithTcx<clean::ProcMacro> for ProcMacro { - fn from_tcx(mac: clean::ProcMacro, _tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::ProcMacro> for ProcMacro { + fn from_clean(mac: clean::ProcMacro, _renderer: &JsonRenderer<'_>) -> Self { ProcMacro { kind: from_macro_kind(mac.kind), helpers: mac.helpers.iter().map(|x| x.to_string()).collect(), @@ -822,17 +826,18 @@ pub(crate) fn from_macro_kind(kind: rustc_span::hygiene::MacroKind) -> MacroKind } } -impl FromWithTcx<Box<clean::TypeAlias>> for TypeAlias { - fn from_tcx(type_alias: Box<clean::TypeAlias>, tcx: TyCtxt<'_>) -> Self { +impl FromClean<Box<clean::TypeAlias>> for TypeAlias { + fn from_clean(type_alias: Box<clean::TypeAlias>, renderer: &JsonRenderer<'_>) -> Self { let clean::TypeAlias { type_, generics, item_type: _, inner_type: _ } = *type_alias; - TypeAlias { type_: type_.into_tcx(tcx), generics: generics.into_tcx(tcx) } + TypeAlias { type_: type_.into_json(renderer), generics: generics.into_json(renderer) } } } -impl FromWithTcx<clean::Static> for Static { - fn from_tcx(stat: clean::Static, tcx: TyCtxt<'_>) -> Self { +impl FromClean<clean::Static> for Static { + fn from_clean(stat: clean::Static, renderer: &JsonRenderer<'_>) -> Self { + let tcx = renderer.tcx; Static { - type_: (*stat.type_).into_tcx(tcx), + type_: (*stat.type_).into_json(renderer), is_mutable: stat.mutability == ast::Mutability::Mut, expr: stat .expr @@ -842,14 +847,17 @@ impl FromWithTcx<clean::Static> for Static { } } -impl FromWithTcx<clean::TraitAlias> for TraitAlias { - fn from_tcx(alias: clean::TraitAlias, tcx: TyCtxt<'_>) -> Self { - TraitAlias { generics: alias.generics.into_tcx(tcx), params: alias.bounds.into_tcx(tcx) } +impl FromClean<clean::TraitAlias> for TraitAlias { + fn from_clean(alias: clean::TraitAlias, renderer: &JsonRenderer<'_>) -> Self { + TraitAlias { + generics: alias.generics.into_json(renderer), + params: alias.bounds.into_json(renderer), + } } } -impl FromWithTcx<ItemType> for ItemKind { - fn from_tcx(kind: ItemType, _tcx: TyCtxt<'_>) -> Self { +impl FromClean<ItemType> for ItemKind { + fn from_clean(kind: ItemType, _renderer: &JsonRenderer<'_>) -> Self { use ItemType::*; match kind { Module => ItemKind::Module, @@ -878,25 +886,3 @@ impl FromWithTcx<ItemType> for ItemKind { } } } - -fn ids(items: impl IntoIterator<Item = clean::Item>, tcx: TyCtxt<'_>) -> Vec<Id> { - items - .into_iter() - .filter(|x| !x.is_stripped() && !x.is_keyword()) - .map(|i| id_from_item(&i, tcx)) - .collect() -} - -fn ids_keeping_stripped( - items: impl IntoIterator<Item = clean::Item>, - tcx: TyCtxt<'_>, -) -> Vec<Option<Id>> { - items - .into_iter() - .map( - |i| { - if !i.is_stripped() && !i.is_keyword() { Some(id_from_item(&i, tcx)) } else { None } - }, - ) - .collect() -} diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index b7a683eed1c..df97c5ea263 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -16,6 +16,7 @@ use std::rc::Rc; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; +use rustc_span::Symbol; use rustc_span::def_id::LOCAL_CRATE; use rustdoc_json_types as types; // It's important to use the FxHashMap from rustdoc_json_types here, instead of @@ -31,9 +32,17 @@ use crate::docfs::PathError; use crate::error::Error; use crate::formats::FormatRenderer; use crate::formats::cache::Cache; -use crate::json::conversions::{IntoWithTcx, id_from_item, id_from_item_default}; +use crate::json::conversions::IntoJson; use crate::{clean, try_err}; +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +struct FullItemId { + def_id: DefId, + name: Option<Symbol>, + /// Used to distinguish imports of different items with the same name + extra: Option<types::Id>, +} + #[derive(Clone)] pub(crate) struct JsonRenderer<'tcx> { tcx: TyCtxt<'tcx>, @@ -46,6 +55,7 @@ pub(crate) struct JsonRenderer<'tcx> { out_dir: Option<PathBuf>, cache: Rc<Cache>, imported_items: DefIdSet, + id_interner: Rc<RefCell<FxHashMap<(FullItemId, Option<FullItemId>), types::Id>>>, } impl<'tcx> JsonRenderer<'tcx> { @@ -63,7 +73,7 @@ impl<'tcx> JsonRenderer<'tcx> { .map(|i| { let item = &i.impl_item; self.item(item.clone()).unwrap(); - id_from_item(&item, self.tcx) + self.id_from_item(&item) }) .collect() }) @@ -94,7 +104,7 @@ impl<'tcx> JsonRenderer<'tcx> { if item.item_id.is_local() || is_primitive_impl { self.item(item.clone()).unwrap(); - Some(id_from_item(&item, self.tcx)) + Some(self.id_from_item(&item)) } else { None } @@ -145,6 +155,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { out_dir: if options.output_to_stdout { None } else { Some(options.output) }, cache: Rc::new(cache), imported_items, + id_interner: Default::default(), }, krate, )) @@ -243,7 +254,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { debug!("Constructing Output"); let output_crate = types::Crate { - root: types::Id(format!("0:0:{}", e.name(self.tcx).as_u32())), + root: self.id_from_item_default(e.def_id().into()), crate_version: self.cache.crate_version.clone(), includes_private: self.cache.document_private, index, @@ -253,10 +264,10 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { .iter() .chain(&self.cache.external_paths) .map(|(&k, &(ref path, kind))| { - (id_from_item_default(k.into(), self.tcx), types::ItemSummary { + (self.id_from_item_default(k.into()), types::ItemSummary { crate_id: k.krate.as_u32(), path: path.iter().map(|s| s.to_string()).collect(), - kind: kind.into_tcx(self.tcx), + kind: kind.into_json(self), }) }) .collect(), diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 2be415e2e0e..10fc0ea9990 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -20,7 +20,6 @@ #![warn(rustc::internal)] #![allow(clippy::collapsible_if, clippy::collapsible_else_if)] #![allow(rustc::diagnostic_outside_of_impl)] -#![allow(rustc::potential_query_instability)] #![allow(rustc::untranslatable_diagnostic)] extern crate thin_vec; @@ -99,7 +98,7 @@ use crate::clean::utils::DOC_RUST_LANG_ORG_CHANNEL; /// Commas between elements are required (even if the expression is a block). macro_rules! map { ($( $key: expr => $val: expr ),* $(,)*) => {{ - let mut map = ::rustc_data_structures::fx::FxHashMap::default(); + let mut map = ::rustc_data_structures::fx::FxIndexMap::default(); $( map.insert($key, $val); )* map }} diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 0dba16cbaf3..db235786cf4 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -9,7 +9,7 @@ use std::ops::Range; use pulldown_cmark::LinkType; use rustc_ast::util::comments::may_have_doc_links; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_errors::{Applicability, Diag, DiagMessage}; use rustc_hir::def::Namespace::*; @@ -778,9 +778,9 @@ fn trait_impls_for<'a>( cx: &mut DocContext<'a>, ty: Ty<'a>, module: DefId, -) -> FxHashSet<(DefId, DefId)> { +) -> FxIndexSet<(DefId, DefId)> { let tcx = cx.tcx; - let mut impls = FxHashSet::default(); + let mut impls = FxIndexSet::default(); for &trait_ in tcx.doc_link_traits_in_scope(module) { tcx.for_each_relevant_impl(trait_, ty, |impl_| { @@ -1040,21 +1040,6 @@ impl LinkCollector<'_, '_> { false, )?; - if ori_link.display_text.is_some() { - self.resolve_display_text( - path_str, - ResolutionInfo { - item_id, - module_id, - dis: disambiguator, - path_str: ori_link.display_text.clone()?.into_boxed_str(), - extra_fragment: extra_fragment.clone(), - }, - &ori_link, - &diag_info, - ); - } - // Check for a primitive which might conflict with a module // Report the ambiguity and require that the user specify which one they meant. // FIXME: could there ever be a primitive not in the type namespace? @@ -1088,7 +1073,7 @@ impl LinkCollector<'_, '_> { // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677 // for discussion on the matter. let kind = self.cx.tcx.def_kind(id); - self.verify_disambiguator(path_str, kind, id, disambiguator, item, &diag_info)?; + self.verify_disambiguator(path_str, kind, id, disambiguator, &diag_info)?; } else { match disambiguator { Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {} @@ -1117,7 +1102,6 @@ impl LinkCollector<'_, '_> { kind_for_dis, id_for_dis, disambiguator, - item, &diag_info, )?; @@ -1138,7 +1122,6 @@ impl LinkCollector<'_, '_> { kind: DefKind, id: DefId, disambiguator: Option<Disambiguator>, - item: &Item, diag_info: &DiagnosticInfo<'_>, ) -> Option<()> { debug!("intra-doc link to {path_str} resolved to {:?}", (kind, id)); @@ -1165,7 +1148,7 @@ impl LinkCollector<'_, '_> { // item can be non-local e.g. when using `#[rustc_doc_primitive = "pointer"]` if let Some((src_id, dst_id)) = id.as_local().and_then(|dst_id| { - item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id)) + diag_info.item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id)) }) { if self.cx.tcx.effective_visibilities(()).is_exported(src_id) && !self.cx.tcx.effective_visibilities(()).is_exported(dst_id) @@ -1398,58 +1381,6 @@ impl LinkCollector<'_, '_> { } } } - - /// Resolve display text if the provided link has separated parts of links. - /// - /// For example: - /// Inline link `[display_text](dest_link)` and reference link `[display_text][reference_link]` has - /// separated parts of links. - fn resolve_display_text( - &mut self, - explicit_link: &Box<str>, - display_res_info: ResolutionInfo, - ori_link: &MarkdownLink, - diag_info: &DiagnosticInfo<'_>, - ) { - // Check if explicit resolution's path is same as resolution of original link's display text path, see - // tests/rustdoc-ui/lint/redundant_explicit_links.rs for more cases. - // - // To avoid disambiguator from panicking, we check if display text path is possible to be disambiguated - // into explicit path. - if !matches!( - ori_link.kind, - LinkType::Inline | LinkType::Reference | LinkType::ReferenceUnknown - ) { - return; - } - - // Algorithm to check if display text could possibly be the explicit link: - // - // Consider 2 links which are display text and explicit link, pick the shorter - // one as symbol and longer one as full qualified path, and tries to match symbol - // to the full qualified path's last symbol. - // - // Otherwise, check if 2 links are same, if so, skip the resolve process. - // - // Notice that this algorithm is passive, might possibly miss actual redundant cases. - let explicit_link = explicit_link.to_string(); - let display_text = ori_link.display_text.as_ref().unwrap(); - - if display_text.len() == explicit_link.len() { - // Whether they are same or not, skip the resolve process. - return; - } - - if explicit_link.ends_with(&display_text[..]) || display_text.ends_with(&explicit_link[..]) - { - self.resolve_with_disambiguator_cached( - display_res_info, - diag_info.clone(), // this struct should really be Copy, but Range is not :( - false, - true, - ); - } - } } /// Get the section of a link between the backticks, @@ -1996,11 +1927,22 @@ fn resolution_failure( &diag_info, ); - format!( - "this link resolves to {}, which is not in the {} namespace", - item(res), - expected_ns.descr() - ) + if let Some(disambiguator) = disambiguator + && !matches!(disambiguator, Disambiguator::Namespace(..)) + { + format!( + "this link resolves to {}, which is not {} {}", + item(res), + disambiguator.article(), + disambiguator.descr() + ) + } else { + format!( + "this link resolves to {}, which is not in the {} namespace", + item(res), + expected_ns.descr() + ) + } } }; if let Some(span) = sp { diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index b307e84e42e..d1a1f0df3e7 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -219,7 +219,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> panic!("collect-trait-impls can't run"); }; - krate.external_traits.extend(cx.external_traits.drain()); + krate.external_traits.extend(cx.external_traits.drain(..)); krate } diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs index 848e70a7bdb..e0dc5b4c513 100644 --- a/src/librustdoc/passes/lint/check_code_block_syntax.rs +++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs @@ -145,7 +145,7 @@ struct BufferEmitter { } impl Translate for BufferEmitter { - fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> { + fn fluent_bundle(&self) -> Option<&rustc_errors::FluentBundle> { None } @@ -169,7 +169,7 @@ impl Emitter for BufferEmitter { } } - fn source_map(&self) -> Option<&Lrc<SourceMap>> { + fn source_map(&self) -> Option<&SourceMap> { None } } diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 9d4d203562c..a59c43bfbf9 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -3,7 +3,7 @@ use std::fs; use std::path::PathBuf; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_errors::DiagCtxtHandle; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{self as hir}; @@ -102,8 +102,8 @@ pub(crate) struct CallData { pub(crate) is_bin: bool, } -pub(crate) type FnCallLocations = FxHashMap<PathBuf, CallData>; -pub(crate) type AllCallLocations = FxHashMap<DefPathHash, FnCallLocations>; +pub(crate) type FnCallLocations = FxIndexMap<PathBuf, CallData>; +pub(crate) type AllCallLocations = FxIndexMap<DefPathHash, FnCallLocations>; /// Visitor for traversing a crate and finding instances of function calls. struct FindCalls<'a, 'tcx> { @@ -293,7 +293,7 @@ pub(crate) fn run( debug!("Scrape examples target_crates: {target_crates:?}"); // Run call-finder on all items - let mut calls = FxHashMap::default(); + let mut calls = FxIndexMap::default(); let mut finder = FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, bin_crate }; tcx.hir().visit_all_item_likes_in_crate(&mut finder); @@ -332,7 +332,7 @@ pub(crate) fn load_call_locations( with_examples: Vec<String>, dcx: DiagCtxtHandle<'_>, ) -> AllCallLocations { - let mut all_calls: AllCallLocations = FxHashMap::default(); + let mut all_calls: AllCallLocations = FxIndexMap::default(); for path in with_examples { let bytes = match fs::read(&path) { Ok(bytes) => bytes, diff --git a/src/librustdoc/theme.rs b/src/librustdoc/theme.rs index 409728a8e86..2c00bb7e132 100644 --- a/src/librustdoc/theme.rs +++ b/src/librustdoc/theme.rs @@ -1,10 +1,9 @@ -use std::collections::hash_map::Entry; use std::fs; use std::iter::Peekable; use std::path::Path; use std::str::Chars; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxIndexMap, IndexEntry}; use rustc_errors::DiagCtxtHandle; #[cfg(test)] @@ -12,8 +11,8 @@ mod tests; #[derive(Debug)] pub(crate) struct CssPath { - pub(crate) rules: FxHashMap<String, String>, - pub(crate) children: FxHashMap<String, CssPath>, + pub(crate) rules: FxIndexMap<String, String>, + pub(crate) children: FxIndexMap<String, CssPath>, } /// When encountering a `"` or a `'`, returns the whole string, including the quote characters. @@ -120,10 +119,10 @@ fn parse_rules( content: &str, selector: String, iter: &mut Peekable<Chars<'_>>, - paths: &mut FxHashMap<String, CssPath>, + paths: &mut FxIndexMap<String, CssPath>, ) -> Result<(), String> { - let mut rules = FxHashMap::default(); - let mut children = FxHashMap::default(); + let mut rules = FxIndexMap::default(); + let mut children = FxIndexMap::default(); loop { // If the parent isn't a "normal" CSS selector, we only expect sub-selectors and not CSS @@ -146,10 +145,10 @@ fn parse_rules( return Err(format!("Found empty value for rule `{rule}` in selector `{selector}`")); } match rules.entry(rule) { - Entry::Occupied(mut o) => { + IndexEntry::Occupied(mut o) => { *o.get_mut() = value; } - Entry::Vacant(v) => { + IndexEntry::Vacant(v) => { v.insert(value); } } @@ -159,7 +158,7 @@ fn parse_rules( } match paths.entry(selector) { - Entry::Occupied(mut o) => { + IndexEntry::Occupied(mut o) => { let v = o.get_mut(); for (key, value) in rules.into_iter() { v.rules.insert(key, value); @@ -168,7 +167,7 @@ fn parse_rules( v.children.insert(sel, child); } } - Entry::Vacant(v) => { + IndexEntry::Vacant(v) => { v.insert(CssPath { rules, children }); } } @@ -178,7 +177,7 @@ fn parse_rules( pub(crate) fn parse_selectors( content: &str, iter: &mut Peekable<Chars<'_>>, - paths: &mut FxHashMap<String, CssPath>, + paths: &mut FxIndexMap<String, CssPath>, ) -> Result<(), String> { let mut selector = String::new(); @@ -202,17 +201,17 @@ pub(crate) fn parse_selectors( /// The entry point to parse the CSS rules. Every time we encounter a `{`, we then parse the rules /// inside it. -pub(crate) fn load_css_paths(content: &str) -> Result<FxHashMap<String, CssPath>, String> { +pub(crate) fn load_css_paths(content: &str) -> Result<FxIndexMap<String, CssPath>, String> { let mut iter = content.chars().peekable(); - let mut paths = FxHashMap::default(); + let mut paths = FxIndexMap::default(); parse_selectors(content, &mut iter, &mut paths)?; Ok(paths) } pub(crate) fn get_differences( - origin: &FxHashMap<String, CssPath>, - against: &FxHashMap<String, CssPath>, + origin: &FxIndexMap<String, CssPath>, + against: &FxIndexMap<String, CssPath>, v: &mut Vec<String>, ) { for (selector, entry) in origin.iter() { @@ -235,7 +234,7 @@ pub(crate) fn get_differences( pub(crate) fn test_theme_against<P: AsRef<Path>>( f: &P, - origin: &FxHashMap<String, CssPath>, + origin: &FxIndexMap<String, CssPath>, dcx: DiagCtxtHandle<'_>, ) -> (bool, Vec<String>) { let against = match fs::read_to_string(f) diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index c44e5ecaba8..f789aca7378 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -164,7 +164,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { .unwrap_or(&[]) .iter() .filter_map(|attr| { - Cfg::parse(attr.meta_item()?) + Cfg::parse(attr) .map_err(|e| self.cx.sess().dcx().span_err(e.span, e.msg)) .ok() }) @@ -505,21 +505,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { | hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::TyAlias(..) - | hir::ItemKind::OpaqueTy(hir::OpaqueTy { - origin: hir::OpaqueTyOrigin::TyAlias { .. }, - .. - }) | hir::ItemKind::Static(..) | hir::ItemKind::Trait(..) | hir::ItemKind::TraitAlias(..) => { self.add_to_current_mod(item, renamed, import_id); } - hir::ItemKind::OpaqueTy(hir::OpaqueTy { - origin: hir::OpaqueTyOrigin::AsyncFn { .. } | hir::OpaqueTyOrigin::FnReturn { .. }, - .. - }) => { - // return-position impl traits are never nameable, and should never be documented. - } hir::ItemKind::Const(..) => { // Underscore constants do not correspond to a nameable item and // so are never useful in documentation. diff --git a/src/llvm-project b/src/llvm-project -Subproject 56997739365e8132cc817e00899480746c09d7d +Subproject 3a17f74904a74565c54cfac0d67026362d03869 diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index b3707cf6157..fc64bc98bb9 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 = 34; +pub const FORMAT_VERSION: u32 = 35; /// The root of the emitted JSON blob. /// @@ -296,9 +296,9 @@ pub enum AssocItemConstraintKind { /// Rustdoc makes no guarantees about the inner value of Id's. Applications /// should treat them as opaque keys to lookup items, and avoid attempting /// to parse them, or otherwise depend on any implementation details. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] // FIXME(aDotInTheVoid): Consider making this non-public in rustdoc-types. -pub struct Id(pub String); +pub struct Id(pub u32); /// The fundamental kind of an item. Unlike [`ItemEnum`], this does not carry any aditional info. /// @@ -677,7 +677,7 @@ pub struct FunctionHeader { /// on unwinding for more info. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Abi { - // We only have a concrete listing here for stable ABI's because their are so many + // We only have a concrete listing here for stable ABI's because there are so many // See rustc_ast_passes::feature_gate::PostExpansionVisitor::check_abi for the list /// The default ABI, but that can also be written explicitly with `extern "Rust"`. Rust, diff --git a/src/tools/build_helper/src/git.rs b/src/tools/build_helper/src/git.rs index 10c5476cd8f..2aad5650fa8 100644 --- a/src/tools/build_helper/src/git.rs +++ b/src/tools/build_helper/src/git.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +use crate::ci::CiEnv; + pub struct GitConfig<'a> { pub git_repository: &'a str, pub nightly_branch: &'a str, @@ -114,8 +116,8 @@ fn git_upstream_merge_base( /// Searches for the nearest merge commit in the repository that also exists upstream. /// -/// If it fails to find the upstream remote, it then looks for the most recent commit made -/// by the merge bot by matching the author's email address with the merge bot's email. +/// It looks for the most recent commit made by the merge bot by matching the author's email +/// address with the merge bot's email. pub fn get_closest_merge_commit( git_dir: Option<&Path>, config: &GitConfig<'_>, @@ -127,7 +129,15 @@ pub fn get_closest_merge_commit( git.current_dir(git_dir); } - let merge_base = git_upstream_merge_base(config, git_dir).unwrap_or_else(|_| "HEAD".into()); + let merge_base = { + if CiEnv::is_ci() { + git_upstream_merge_base(config, git_dir).unwrap() + } else { + // For non-CI environments, ignore rust-lang/rust upstream as it usually gets + // outdated very quickly. + "HEAD".to_string() + } + }; git.args([ "rev-list", @@ -196,67 +206,3 @@ pub fn get_git_untracked_files( .collect(); Ok(Some(files)) } - -/// Print a warning if the branch returned from `updated_master_branch` is old -/// -/// For certain configurations of git repository, this remote will not be -/// updated when running `git pull`. -/// -/// This can result in formatting thousands of files instead of a dozen, -/// so we should warn the user something is wrong. -pub fn warn_old_master_branch(config: &GitConfig<'_>, git_dir: &Path) { - if crate::ci::CiEnv::is_ci() { - // this warning is useless in CI, - // and CI probably won't have the right branches anyway. - return; - } - // this will be overwritten by the actual name, if possible - let mut updated_master = "the upstream master branch".to_string(); - match warn_old_master_branch_(config, git_dir, &mut updated_master) { - Ok(branch_is_old) => { - if !branch_is_old { - return; - } - // otherwise fall through and print the rest of the warning - } - Err(err) => { - eprintln!("warning: unable to check if {updated_master} is old due to error: {err}") - } - } - eprintln!( - "warning: {updated_master} is used to determine if files have been modified\n\ - warning: if it is not updated, this may cause files to be needlessly reformatted" - ); -} - -pub fn warn_old_master_branch_( - config: &GitConfig<'_>, - git_dir: &Path, - updated_master: &mut String, -) -> Result<bool, Box<dyn std::error::Error>> { - use std::time::Duration; - *updated_master = updated_master_branch(config, Some(git_dir))?; - let branch_path = git_dir.join(".git/refs/remotes").join(&updated_master); - const WARN_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 10); - let meta = match std::fs::metadata(&branch_path) { - Ok(meta) => meta, - Err(err) => { - let gcd = git_common_dir(&git_dir)?; - if branch_path.starts_with(&gcd) { - return Err(Box::new(err)); - } - std::fs::metadata(Path::new(&gcd).join("refs/remotes").join(&updated_master))? - } - }; - if meta.modified()?.elapsed()? > WARN_AFTER { - eprintln!("warning: {updated_master} has not been updated in 10 days"); - Ok(true) - } else { - Ok(false) - } -} - -fn git_common_dir(dir: &Path) -> Result<String, String> { - output_result(Command::new("git").arg("-C").arg(dir).arg("rev-parse").arg("--git-common-dir")) - .map(|x| x.trim().to_string()) -} diff --git a/src/tools/cargo b/src/tools/cargo -Subproject 80d82ca22abbee5fb7b51fa1abeb1ae34e99e88 +Subproject 15fbd2f607d4defc87053b8b76bf5038f2483cf diff --git a/src/tools/clippy/clippy_lints/src/attrs/allow_attributes_without_reason.rs b/src/tools/clippy/clippy_lints/src/attrs/allow_attributes_without_reason.rs index 40959eccd3a..5d4e864b9b0 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/allow_attributes_without_reason.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/allow_attributes_without_reason.rs @@ -1,15 +1,15 @@ use super::{ALLOW_ATTRIBUTES_WITHOUT_REASON, Attribute}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; -use rustc_ast::{MetaItemKind, NestedMetaItem}; +use rustc_ast::{MetaItemInner, MetaItemKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_span::sym; use rustc_span::symbol::Symbol; -pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMetaItem], attr: &'cx Attribute) { +pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[MetaItemInner], attr: &'cx Attribute) { // Check if the reason is present - if let Some(item) = items.last().and_then(NestedMetaItem::meta_item) + if let Some(item) = items.last().and_then(MetaItemInner::meta_item) && let MetaItemKind::NameValue(_) = &item.kind && item.path == sym::reason { diff --git a/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs b/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs index 508963a20ea..0baf889faa0 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs @@ -1,12 +1,12 @@ use super::BLANKET_CLIPPY_RESTRICTION_LINTS; use super::utils::extract_clippy_lint; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; -use rustc_ast::NestedMetaItem; +use rustc_ast::MetaItemInner; use rustc_lint::{LateContext, Level, LintContext}; use rustc_span::symbol::Symbol; use rustc_span::{DUMMY_SP, sym}; -pub(super) fn check(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) { +pub(super) fn check(cx: &LateContext<'_>, name: Symbol, items: &[MetaItemInner]) { for lint in items { if let Some(lint_name) = extract_clippy_lint(lint) { if lint_name.as_str() == "restriction" && name != sym::allow { diff --git a/src/tools/clippy/clippy_lints/src/attrs/mod.rs b/src/tools/clippy/clippy_lints/src/attrs/mod.rs index 888f28fa225..1a34ca99fc2 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/mod.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/mod.rs @@ -14,7 +14,7 @@ mod utils; use clippy_config::Conf; use clippy_config::msrvs::{self, Msrv}; -use rustc_ast::{Attribute, MetaItemKind, NestedMetaItem}; +use rustc_ast::{Attribute, MetaItemInner, MetaItemKind}; use rustc_hir::{ImplItem, Item, ItemKind, TraitItem}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -456,7 +456,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { return; } for item in items { - if let NestedMetaItem::MetaItem(mi) = &item + if let MetaItemInner::MetaItem(mi) = &item && let MetaItemKind::NameValue(lit) = &mi.kind && mi.has_name(sym::since) { diff --git a/src/tools/clippy/clippy_lints/src/attrs/non_minimal_cfg.rs b/src/tools/clippy/clippy_lints/src/attrs/non_minimal_cfg.rs index 877025cce4c..7eff5eccfa1 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/non_minimal_cfg.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/non_minimal_cfg.rs @@ -1,7 +1,7 @@ use super::{Attribute, NON_MINIMAL_CFG}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::SpanRangeExt; -use rustc_ast::{MetaItemKind, NestedMetaItem}; +use rustc_ast::{MetaItemInner, MetaItemKind}; use rustc_errors::Applicability; use rustc_lint::EarlyContext; use rustc_span::sym; @@ -14,9 +14,9 @@ pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) { } } -fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) { +fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[MetaItemInner]) { for item in items { - if let NestedMetaItem::MetaItem(meta) = item { + if let MetaItemInner::MetaItem(meta) = item { if !meta.has_name(sym::any) && !meta.has_name(sym::all) { continue; } diff --git a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs index 12668c616c1..72e6ce59d59 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs @@ -2,7 +2,7 @@ use super::utils::{extract_clippy_lint, is_lint_level, is_word}; use super::{Attribute, USELESS_ATTRIBUTE}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{SpanRangeExt, first_line_of_span}; -use rustc_ast::NestedMetaItem; +use rustc_ast::MetaItemInner; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LintContext}; @@ -21,7 +21,7 @@ pub(super) fn check(cx: &LateContext<'_>, item: &Item<'_>, attrs: &[Attribute]) for lint in lint_list { match item.kind { ItemKind::Use(..) => { - if let NestedMetaItem::MetaItem(meta_item) = lint + if let MetaItemInner::MetaItem(meta_item) = lint && meta_item.is_word() && let Some(ident) = meta_item.ident() && matches!( diff --git a/src/tools/clippy/clippy_lints/src/attrs/utils.rs b/src/tools/clippy/clippy_lints/src/attrs/utils.rs index 91ae19acbf7..9b10ae83651 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/utils.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/utils.rs @@ -1,5 +1,5 @@ use clippy_utils::macros::{is_panic, macro_backtrace}; -use rustc_ast::{AttrId, NestedMetaItem}; +use rustc_ast::{AttrId, MetaItemInner}; use rustc_hir::{ Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind, }; @@ -8,8 +8,8 @@ use rustc_middle::ty; use rustc_span::sym; use rustc_span::symbol::Symbol; -pub(super) fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool { - if let NestedMetaItem::MetaItem(mi) = &nmi { +pub(super) fn is_word(nmi: &MetaItemInner, expected: Symbol) -> bool { + if let MetaItemInner::MetaItem(mi) = &nmi { mi.is_word() && mi.has_name(expected) } else { false @@ -74,7 +74,7 @@ fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_> } /// Returns the lint name if it is clippy lint. -pub(super) fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> { +pub(super) fn extract_clippy_lint(lint: &MetaItemInner) -> Option<Symbol> { if let Some(meta_item) = lint.meta_item() && meta_item.path.segments.len() > 1 && let tool_name = meta_item.path.segments[0].ident diff --git a/src/tools/clippy/clippy_lints/src/cfg_not_test.rs b/src/tools/clippy/clippy_lints/src/cfg_not_test.rs index 884d15cabff..84136a2e6c2 100644 --- a/src/tools/clippy/clippy_lints/src/cfg_not_test.rs +++ b/src/tools/clippy/clippy_lints/src/cfg_not_test.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use rustc_ast::NestedMetaItem; +use rustc_ast::MetaItemInner; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; @@ -47,7 +47,7 @@ impl EarlyLintPass for CfgNotTest { } } -fn contains_not_test(list: Option<&[NestedMetaItem]>, not: bool) -> bool { +fn contains_not_test(list: Option<&[MetaItemInner]>, not: bool) -> bool { list.is_some_and(|list| { list.iter().any(|item| { item.ident().is_some_and(|ident| match ident.name { diff --git a/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs b/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs index 796af851bac..6ad879b9fe7 100644 --- a/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs +++ b/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs @@ -6,7 +6,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_impl_item, walk_item, walk_param_bound, walk_ty}; use rustc_hir::{ BodyId, ExprKind, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind, - PredicateOrigin, Ty, TyKind, WherePredicate, + PredicateOrigin, Ty, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter; @@ -199,12 +199,6 @@ impl<'tcx> Visitor<'tcx> for TypeWalker<'_, 'tcx> { fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) { if let Some((def_id, _)) = t.peel_refs().as_generic_param() { self.ty_params.remove(&def_id); - } else if let TyKind::OpaqueDef(id, _) = t.kind { - // Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls - // `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're - // using `OnlyBodies`, so the check ends up failing and the type isn't fully walked. - let item = self.nested_visit_map().item(id); - walk_item(self, item); } else { walk_ty(self, t); } diff --git a/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs b/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs index 6794c6cabfe..5f349d78053 100644 --- a/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs +++ b/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs @@ -3,7 +3,7 @@ use clippy_utils::source::snippet; use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::def_id::DefId; use rustc_hir::{ - AssocItemConstraint, GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier, + AssocItemConstraint, GenericArg, GenericBound, GenericBounds, PredicateOrigin, TraitBoundModifier, TyKind, WherePredicate, }; use rustc_hir_analysis::lower_ty; @@ -342,11 +342,8 @@ impl<'tcx> LateLintPass<'tcx> for ImpliedBoundsInImpls { } } - fn check_ty(&mut self, cx: &LateContext<'_>, ty: &rustc_hir::Ty<'_>) { - if let TyKind::OpaqueDef(item_id, ..) = ty.kind - && let item = cx.tcx.hir().item(item_id) - && let ItemKind::OpaqueTy(opaque_ty) = item.kind - { + fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &rustc_hir::Ty<'tcx>) { + if let TyKind::OpaqueDef(opaque_ty, ..) = ty.kind { check(cx, opaque_ty.bounds); } } diff --git a/src/tools/clippy/clippy_lints/src/index_refutable_slice.rs b/src/tools/clippy/clippy_lints/src/index_refutable_slice.rs index 73ebe6aec15..96550c4d1cb 100644 --- a/src/tools/clippy/clippy_lints/src/index_refutable_slice.rs +++ b/src/tools/clippy/clippy_lints/src/index_refutable_slice.rs @@ -5,7 +5,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::IfLet; use clippy_utils::ty::is_copy; use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local}; -use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::HirId; @@ -133,7 +133,7 @@ fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) { .index_use .iter() .map(|(index, _)| *index) - .collect::<FxHashSet<_>>(); + .collect::<FxIndexSet<_>>(); let value_name = |index| format!("{}_{index}", slice.ident.name); diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs index 45346cd18a6..311bbce14bd 100644 --- a/src/tools/clippy/clippy_lints/src/len_zero.rs +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -308,11 +308,7 @@ enum LenOutput { fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { if let ty::Alias(_, alias_ty) = ty.kind() - && let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(alias_ty.def_id) - && let Item { - kind: ItemKind::OpaqueTy(opaque), - .. - } = item + && let Some(Node::OpaqueTy(opaque)) = cx.tcx.hir().get_if_local(alias_ty.def_id) && let OpaqueTyOrigin::AsyncFn { .. } = opaque.origin && let [GenericBound::Trait(trait_ref, _)] = &opaque.bounds && let Some(segment) = trait_ref.trait_ref.path.segments.last() diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 2eb6d99b761..6ee064a6124 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -1,7 +1,6 @@ #![feature(array_windows)] #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] -#![feature(control_flow_enum)] #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] @@ -27,8 +26,6 @@ unused_qualifications, rustc::internal )] -// Disable this rustc lint for now, as it was also done in rustc -#![allow(rustc::potential_query_instability)] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs index 5c37e735445..ec28671a061 100644 --- a/src/tools/clippy/clippy_lints/src/lifetimes.rs +++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::trait_ref_of_method; use itertools::Itertools; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::Applicability; use rustc_hir::FnRetTy::Return; use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter}; use rustc_hir::intravisit::{ - Visitor, walk_fn_decl, walk_generic_args, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound, + Visitor, walk_fn_decl, walk_generic_args, walk_generics, walk_impl_item_ref, walk_param_bound, walk_poly_trait_ref, walk_trait_ref, walk_ty, walk_where_predicate, }; use rustc_hir::{ @@ -311,7 +311,7 @@ fn could_use_elision<'tcx>( Some((elidable_lts, usages)) } -fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<LocalDefId> { +fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxIndexSet<LocalDefId> { named_generics .iter() .filter_map(|par| { @@ -420,11 +420,9 @@ impl<'tcx> Visitor<'tcx> for RefVisitor<'_, 'tcx> { fn visit_ty(&mut self, ty: &'tcx Ty<'_>) { match ty.kind { - TyKind::OpaqueDef(item, bounds) => { - let map = self.cx.tcx.hir(); - let item = map.item(item); + TyKind::OpaqueDef(opaque, bounds) => { let len = self.lts.len(); - walk_item(self, item); + self.visit_opaque_ty(opaque); self.lts.truncate(len); self.lts.extend(bounds.iter().filter_map(|bound| match bound { GenericArg::Lifetime(&l) => Some(l), @@ -499,7 +497,7 @@ struct Usage { struct LifetimeChecker<'cx, 'tcx, F> { cx: &'cx LateContext<'tcx>, - map: FxHashMap<LocalDefId, Vec<Usage>>, + map: FxIndexMap<LocalDefId, Vec<Usage>>, where_predicate_depth: usize, generic_args_depth: usize, phantom: std::marker::PhantomData<F>, @@ -621,7 +619,7 @@ fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<' fn report_elidable_impl_lifetimes<'tcx>( cx: &LateContext<'tcx>, impl_: &'tcx Impl<'_>, - map: &FxHashMap<LocalDefId, Vec<Usage>>, + map: &FxIndexMap<LocalDefId, Vec<Usage>>, ) { let single_usages = map .iter() diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 745f070a577..214b8b0f379 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -5,7 +5,7 @@ use clippy_utils::ty::has_iter_method; use clippy_utils::visitors::is_local_used; use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, sugg}; use rustc_ast::ast; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{Visitor, walk_expr}; @@ -39,7 +39,7 @@ pub(super) fn check<'tcx>( var: canonical_id, indexed_mut: FxHashSet::default(), indexed_indirectly: FxHashMap::default(), - indexed_directly: FxHashMap::default(), + indexed_directly: FxIndexMap::default(), referenced: FxHashSet::default(), nonindex: false, prefer_mutable: false, @@ -229,7 +229,7 @@ struct VarVisitor<'a, 'tcx> { indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>, /// subset of `indexed` of vars that are indexed directly: `v[i]` /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` - indexed_directly: FxHashMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>, + indexed_directly: FxIndexMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>, /// Any names that are used outside an index operation. /// Used to detect things like `&mut vec` used together with `vec[i]` referenced: FxHashSet<Symbol>, diff --git a/src/tools/clippy/clippy_lints/src/manual_async_fn.rs b/src/tools/clippy/clippy_lints/src/manual_async_fn.rs index 7097c85156c..81115cffdca 100644 --- a/src/tools/clippy/clippy_lints/src/manual_async_fn.rs +++ b/src/tools/clippy/clippy_lints/src/manual_async_fn.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ Block, Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnDecl, - FnRetTy, GenericArg, GenericBound, ImplItem, Item, ItemKind, LifetimeName, Node, TraitRef, Ty, TyKind, + FnRetTy, GenericArg, GenericBound, ImplItem, Item, LifetimeName, Node, TraitRef, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -105,9 +105,7 @@ fn future_trait_ref<'tcx>( cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>, ) -> Option<(&'tcx TraitRef<'tcx>, Vec<LifetimeName>)> { - if let TyKind::OpaqueDef(item_id, bounds) = ty.kind - && let item = cx.tcx.hir().item(item_id) - && let ItemKind::OpaqueTy(opaque) = &item.kind + if let TyKind::OpaqueDef(opaque, bounds) = ty.kind && let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| { if let GenericBound::Trait(poly, _) = bound { Some(&poly.trait_ref) diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 7372f52e1e5..2ce6a8a85a5 100644 --- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -7,6 +7,7 @@ use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; use clippy_utils::{get_attr, is_lint_allowed}; use itertools::Itertools; use rustc_ast::Mutability; +use rustc_data_structures::fx::FxIndexSet; use rustc_errors::{Applicability, Diag}; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; @@ -475,19 +476,19 @@ impl<'tcx> Visitor<'tcx> for SigDropHelper<'_, 'tcx> { struct ArmSigDropHelper<'a, 'tcx> { sig_drop_checker: SigDropChecker<'a, 'tcx>, - found_sig_drop_spans: FxHashSet<Span>, + found_sig_drop_spans: FxIndexSet<Span>, } impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> { fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> { ArmSigDropHelper { sig_drop_checker: SigDropChecker::new(cx), - found_sig_drop_spans: FxHashSet::<Span>::default(), + found_sig_drop_spans: FxIndexSet::<Span>::default(), } } } -fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &[&'tcx Expr<'_>]) -> FxHashSet<Span> { +fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &[&'tcx Expr<'_>]) -> FxIndexSet<Span> { let mut helper = ArmSigDropHelper::new(cx); for arm in arms { helper.visit_expr(arm); diff --git a/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs b/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs index b40d7eba15e..c56a4014b34 100644 --- a/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs @@ -8,7 +8,7 @@ use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{eq_expr_value, hash_expr, higher}; use rustc_ast::{LitKind, RangeLimits}; use rustc_data_structures::packed::Pu128; -use rustc_data_structures::unhash::UnhashMap; +use rustc_data_structures::unhash::UnindexMap; use rustc_errors::{Applicability, Diag}; use rustc_hir::{BinOp, Block, Body, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; @@ -226,7 +226,7 @@ fn upper_index_expr(expr: &Expr<'_>) -> Option<usize> { } /// Checks if the expression is an index into a slice and adds it to `indexes` -fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnhashMap<u64, Vec<IndexEntry<'hir>>>) { +fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnindexMap<u64, Vec<IndexEntry<'hir>>>) { if let ExprKind::Index(slice, index_lit, _) = expr.kind && cx.typeck_results().expr_ty_adjusted(slice).peel_refs().is_slice() && let Some(index) = upper_index_expr(index_lit) @@ -274,7 +274,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Unh } /// Checks if the expression is an `assert!` expression and adds it to `asserts` -fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnhashMap<u64, Vec<IndexEntry<'hir>>>) { +fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnindexMap<u64, Vec<IndexEntry<'hir>>>) { if let Some((comparison, asserted_len, slice)) = assert_len_expr(cx, expr) { let hash = hash_expr(cx, slice); let indexes = map.entry(hash).or_default(); @@ -311,7 +311,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un /// Inspects indexes and reports lints. /// /// Called at the end of this lint after all indexing and `assert!` expressions have been collected. -fn report_indexes(cx: &LateContext<'_>, map: &UnhashMap<u64, Vec<IndexEntry<'_>>>) { +fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>>>) { for bucket in map.values() { for entry in bucket { let Some(full_span) = entry @@ -403,7 +403,7 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnhashMap<u64, Vec<IndexEntry<'_>> impl LateLintPass<'_> for MissingAssertsForIndexing { fn check_body(&mut self, cx: &LateContext<'_>, body: &Body<'_>) { - let mut map = UnhashMap::default(); + let mut map = UnindexMap::default(); for_each_expr_without_closures(body.value, |expr| { check_index(cx, expr, &mut map); diff --git a/src/tools/clippy/clippy_lints/src/missing_doc.rs b/src/tools/clippy/clippy_lints/src/missing_doc.rs index 64fc1a8a1a5..007bcebdff6 100644 --- a/src/tools/clippy/clippy_lints/src/missing_doc.rs +++ b/src/tools/clippy/clippy_lints/src/missing_doc.rs @@ -193,8 +193,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { | hir::ItemKind::Trait(..) | hir::ItemKind::TraitAlias(..) | hir::ItemKind::TyAlias(..) - | hir::ItemKind::Union(..) - | hir::ItemKind::OpaqueTy(..) => {}, + | hir::ItemKind::Union(..) => {} hir::ItemKind::ExternCrate(..) | hir::ItemKind::ForeignMod { .. } | hir::ItemKind::GlobalAsm(..) diff --git a/src/tools/clippy/clippy_lints/src/missing_inline.rs b/src/tools/clippy/clippy_lints/src/missing_inline.rs index d342be4545c..f95a0f63fab 100644 --- a/src/tools/clippy/clippy_lints/src/missing_inline.rs +++ b/src/tools/clippy/clippy_lints/src/missing_inline.rs @@ -130,7 +130,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { | hir::ItemKind::GlobalAsm(..) | hir::ItemKind::TyAlias(..) | hir::ItemKind::Union(..) - | hir::ItemKind::OpaqueTy(..) | hir::ItemKind::ExternCrate(..) | hir::ItemKind::ForeignMod { .. } | hir::ItemKind::Impl { .. } diff --git a/src/tools/clippy/clippy_lints/src/module_style.rs b/src/tools/clippy/clippy_lints/src/module_style.rs index e9c5f64a255..676d608eb31 100644 --- a/src/tools/clippy/clippy_lints/src/module_style.rs +++ b/src/tools/clippy/clippy_lints/src/module_style.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::ast; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LOCAL_CRATE; @@ -87,7 +87,7 @@ impl EarlyLintPass for ModStyle { // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives // `[path, to]` but not foo - let mut folder_segments = FxHashSet::default(); + let mut folder_segments = FxIndexSet::default(); // `mod_folders` is all the unique folder names that contain a mod.rs file let mut mod_folders = FxHashSet::default(); // `file_map` maps file names to the full path including the file name @@ -144,7 +144,7 @@ impl EarlyLintPass for ModStyle { /// is `mod.rs` we add it's parent folder to `mod_folders`. fn process_paths_for_mod_files<'a>( path: &'a Path, - folder_segments: &mut FxHashSet<&'a OsStr>, + folder_segments: &mut FxIndexSet<&'a OsStr>, mod_folders: &mut FxHashSet<&'a OsStr>, ) { let mut comp = path.components().rev().peekable(); diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs index c2facb2fcf6..5c631a176c4 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -5,7 +5,7 @@ use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_expr; use clippy_utils::{inherits_cfg, is_from_proc_macro, is_self}; use core::ops::ControlFlow; -use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ @@ -101,7 +101,7 @@ fn check_closures<'tcx>( ctx: &mut MutablyUsedVariablesCtxt<'tcx>, cx: &LateContext<'tcx>, checked_closures: &mut FxHashSet<LocalDefId>, - closures: FxHashSet<LocalDefId>, + closures: FxIndexSet<LocalDefId>, ) { let hir = cx.tcx.hir(); for closure in closures { @@ -196,7 +196,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { prev_bind: None, prev_move_to_closure: HirIdSet::default(), aliases: HirIdMap::default(), - async_closures: FxHashSet::default(), + async_closures: FxIndexSet::default(), tcx: cx.tcx, }; euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx) @@ -207,7 +207,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { // We retrieve all the closures declared in the function because they will not be found // by `euv::Delegate`. - let mut closures: FxHashSet<LocalDefId> = FxHashSet::default(); + let mut closures: FxIndexSet<LocalDefId> = FxIndexSet::default(); for_each_expr(cx, body, |expr| { if let ExprKind::Closure(closure) = expr.kind { closures.insert(closure.def_id); @@ -307,7 +307,7 @@ struct MutablyUsedVariablesCtxt<'tcx> { /// use of a variable. prev_move_to_closure: HirIdSet, aliases: HirIdMap<HirId>, - async_closures: FxHashSet<LocalDefId>, + async_closures: FxIndexSet<LocalDefId>, tcx: TyCtxt<'tcx>, } diff --git a/src/tools/clippy/clippy_lints/src/returns.rs b/src/tools/clippy/clippy_lints/src/returns.rs index 1f223048ce5..662745e4b5d 100644 --- a/src/tools/clippy/clippy_lints/src/returns.rs +++ b/src/tools/clippy/clippy_lints/src/returns.rs @@ -7,7 +7,7 @@ use clippy_utils::{ path_to_local_id, span_contains_cfg, span_find_starting_semi, }; use core::ops::ControlFlow; -use rustc_ast::NestedMetaItem; +use rustc_ast::MetaItemInner; use rustc_errors::Applicability; use rustc_hir::LangItem::ResultErr; use rustc_hir::intravisit::FnKind; @@ -407,7 +407,7 @@ fn check_final_expr<'tcx>( } } - if ret_span.from_expansion() { + if ret_span.from_expansion() || is_from_proc_macro(cx, expr) { return; } @@ -421,7 +421,7 @@ fn check_final_expr<'tcx>( if matches!(Level::from_attr(attr), Some(Level::Expect(_))) && let metas = attr.meta_item_list() && let Some(lst) = metas - && let [NestedMetaItem::MetaItem(meta_item), ..] = lst.as_slice() + && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() && let [tool, lint_name] = meta_item.path.segments.as_slice() && tool.ident.name == sym::clippy && matches!( diff --git a/src/tools/clippy/clippy_lints/src/swap.rs b/src/tools/clippy/clippy_lints/src/swap.rs index a3145c4647c..6cba560393d 100644 --- a/src/tools/clippy/clippy_lints/src/swap.rs +++ b/src/tools/clippy/clippy_lints/src/swap.rs @@ -6,9 +6,9 @@ use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{can_mut_borrow_both, eq_expr_value, is_in_const_context, std_or_core}; use itertools::Itertools; +use rustc_data_structures::fx::FxIndexSet; use rustc_hir::intravisit::{Visitor, walk_expr}; -use crate::FxHashSet; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, LetStmt, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -334,7 +334,7 @@ struct IndexBinding<'a, 'tcx> { impl<'tcx> IndexBinding<'_, 'tcx> { fn snippet_index_bindings(&mut self, exprs: &[&'tcx Expr<'tcx>]) -> String { - let mut bindings = FxHashSet::default(); + let mut bindings = FxIndexSet::default(); for expr in exprs { bindings.insert(self.snippet_index_binding(expr)); } diff --git a/src/tools/clippy/clippy_lints/src/trait_bounds.rs b/src/tools/clippy/clippy_lints/src/trait_bounds.rs index 00277593622..3c3973857e7 100644 --- a/src/tools/clippy/clippy_lints/src/trait_bounds.rs +++ b/src/tools/clippy/clippy_lints/src/trait_bounds.rs @@ -5,7 +5,7 @@ use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability}; use clippy_utils::{SpanlessEq, SpanlessHash, is_from_proc_macro}; use core::hash::{Hash, Hasher}; use itertools::Itertools; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, IndexEntry}; use rustc_data_structures::unhash::UnhashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -16,7 +16,6 @@ use rustc_hir::{ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::{BytePos, Span}; -use std::collections::hash_map::Entry; declare_clippy_lint! { /// ### What it does @@ -427,7 +426,7 @@ fn rollup_traits( bounds: &[GenericBound<'_>], msg: &'static str, ) -> Vec<(ComparableTraitRef, Span)> { - let mut map = FxHashMap::default(); + let mut map = FxIndexMap::default(); let mut repeated_res = false; let only_comparable_trait_refs = |bound: &GenericBound<'_>| { @@ -442,8 +441,8 @@ fn rollup_traits( for bound in bounds.iter().filter_map(only_comparable_trait_refs) { let (comparable_bound, span_direct) = bound; match map.entry(comparable_bound) { - Entry::Occupied(_) => repeated_res = true, - Entry::Vacant(e) => { + IndexEntry::Occupied(_) => repeated_res = true, + IndexEntry::Vacant(e) => { e.insert((span_direct, i)); i += 1; }, diff --git a/src/tools/clippy/clippy_lints/src/use_self.rs b/src/tools/clippy/clippy_lints/src/use_self.rs index 08449de79b3..f5cf4a586fd 100644 --- a/src/tools/clippy/clippy_lints/src/use_self.rs +++ b/src/tools/clippy/clippy_lints/src/use_self.rs @@ -85,10 +85,6 @@ const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element"; impl<'tcx> LateLintPass<'tcx> for UseSelf { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { - if matches!(item.kind, ItemKind::OpaqueTy(_)) { - // skip over `ItemKind::OpaqueTy` in order to lint `foo() -> impl <..>` - return; - } // We push the self types of `impl`s on a stack here. Only the top type on the stack is // relevant for linting, since this is the self type of the `impl` we're currently in. To // avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that @@ -130,10 +126,8 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { self.stack.push(stack_item); } - fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) { - if !matches!(item.kind, ItemKind::OpaqueTy(_)) { - self.stack.pop(); - } + fn check_item_post(&mut self, _: &LateContext<'_>, _: &Item<'_>) { + self.stack.pop(); } fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs index 9143d292f67..bfb3a76ad25 100644 --- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs +++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs @@ -141,62 +141,89 @@ fn path_search_pat(path: &Path<'_>) -> (Pat, Pat) { /// Get the search patterns to use for the given expression fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) { - match e.kind { - ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")), - // Parenthesis are trimmed from the text before the search patterns are matched. - // See: `span_matches_pat` - ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")), - ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat(tcx, e).1), - ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat(tcx, e).1), - ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat(tcx, e).1), - ExprKind::Lit(lit) => lit_search_pat(&lit.node), - ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")), - ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => (expr_search_pat(tcx, e).0, Pat::Str("(")), - ExprKind::Call(first, [.., last]) - | ExprKind::MethodCall(_, first, [.., last], _) - | ExprKind::Binary(_, first, last) - | ExprKind::Tup([first, .., last]) - | ExprKind::Assign(first, last, _) - | ExprKind::AssignOp(_, first, last) => (expr_search_pat(tcx, first).0, expr_search_pat(tcx, last).1), - ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat(tcx, e), - ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("")), - ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat(tcx, let_expr.init).1), - ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")), - ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")), - ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")), - ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")), - ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => { - (Pat::Str("for"), Pat::Str("}")) - }, - ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")), - ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => (expr_search_pat(tcx, e).0, Pat::Str("?")), - ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => { - (expr_search_pat(tcx, e).0, Pat::Str("await")) - }, - ExprKind::Closure(&Closure { body, .. }) => (Pat::Str(""), expr_search_pat(tcx, tcx.hir().body(body).value).1), - ExprKind::Block( - Block { - rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), - .. + fn expr_search_pat_inner(tcx: TyCtxt<'_>, e: &Expr<'_>, outer_span: Span) -> (Pat, Pat) { + // The expression can have subexpressions in different contexts, in which case + // building up a search pattern from the macro expansion would lead to false positives; + // e.g. `return format!(..)` would be considered to be from a proc macro + // if we build up a pattern for the macro expansion and compare it to the invocation `format!()`. + // So instead we return an empty pattern such that `span_matches_pat` always returns true. + if !e.span.eq_ctxt(outer_span) { + return (Pat::Str(""), Pat::Str("")); + } + + match e.kind { + ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")), + // Parenthesis are trimmed from the text before the search patterns are matched. + // See: `span_matches_pat` + ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")), + ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Lit(lit) => lit_search_pat(&lit.node), + ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")), + ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => { + (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("(")) }, - None, - ) => (Pat::Str("unsafe"), Pat::Str("}")), - ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")), - ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)), - ExprKind::Index(e, _, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")), - ExprKind::Path(ref path) => qpath_search_pat(path), - ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1), - ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")), - ExprKind::Break(Destination { label: Some(name), .. }, None) => (Pat::Str("break"), Pat::Sym(name.ident.name)), - ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat(tcx, e).1), - ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")), - ExprKind::Continue(Destination { label: Some(name), .. }) => (Pat::Str("continue"), Pat::Sym(name.ident.name)), - ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")), - ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat(tcx, e).1), - ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")), - ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat(tcx, e).1), - _ => (Pat::Str(""), Pat::Str("")), + ExprKind::Call(first, [.., last]) + | ExprKind::MethodCall(_, first, [.., last], _) + | ExprKind::Binary(_, first, last) + | ExprKind::Tup([first, .., last]) + | ExprKind::Assign(first, last, _) + | ExprKind::AssignOp(_, first, last) => ( + expr_search_pat_inner(tcx, first, outer_span).0, + expr_search_pat_inner(tcx, last, outer_span).1, + ), + ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat_inner(tcx, e, outer_span), + ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("")), + ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat_inner(tcx, let_expr.init, outer_span).1), + ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")), + ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")), + ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")), + ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")), + ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => { + (Pat::Str("for"), Pat::Str("}")) + }, + ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")), + ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => { + (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("?")) + }, + ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => { + (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("await")) + }, + ExprKind::Closure(&Closure { body, .. }) => ( + Pat::Str(""), + expr_search_pat_inner(tcx, tcx.hir().body(body).value, outer_span).1, + ), + ExprKind::Block( + Block { + rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), + .. + }, + None, + ) => (Pat::Str("unsafe"), Pat::Str("}")), + ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")), + ExprKind::Field(e, name) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Sym(name.name)), + ExprKind::Index(e, _, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("]")), + ExprKind::Path(ref path) => qpath_search_pat(path), + ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")), + ExprKind::Break(Destination { label: Some(name), .. }, None) => { + (Pat::Str("break"), Pat::Sym(name.ident.name)) + }, + ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")), + ExprKind::Continue(Destination { label: Some(name), .. }) => { + (Pat::Str("continue"), Pat::Sym(name.ident.name)) + }, + ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")), + ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")), + ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat_inner(tcx, e, outer_span).1), + _ => (Pat::Str(""), Pat::Str("")), + } } + + expr_search_pat_inner(tcx, e, e.span) } fn fn_header_search_pat(header: FnHeader) -> Pat { @@ -220,7 +247,7 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) { ItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")), ItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")), ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")), - ItemKind::TyAlias(..) | ItemKind::OpaqueTy(_) => (Pat::Str("type"), Pat::Str(";")), + ItemKind::TyAlias(..) => (Pat::Str("type"), Pat::Str(";")), ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")), ItemKind::Struct(VariantData::Struct { .. }, _) => (Pat::Str("struct"), Pat::Str("}")), ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")), diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 514ec70a40b..10e258444a6 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -1,6 +1,5 @@ #![feature(array_chunks)] #![feature(box_patterns)] -#![feature(control_flow_enum)] #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] diff --git a/src/tools/clippy/clippy_utils/src/source.rs b/src/tools/clippy/clippy_utils/src/source.rs index 4ad7575e720..eecbfb3936a 100644 --- a/src/tools/clippy/clippy_utils/src/source.rs +++ b/src/tools/clippy/clippy_utils/src/source.rs @@ -287,6 +287,7 @@ impl SourceFileRange { self.sf .src .as_ref() + .map(|src| src.as_str()) .or_else(|| self.sf.external_src.get().and_then(|src| src.get_source())) .and_then(|x| x.get(self.range.clone())) } diff --git a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.fixed b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.fixed index baf939af24e..cdb8fa0454c 100644 --- a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.fixed +++ b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.fixed @@ -86,6 +86,7 @@ mod issue9612 { util(); } + #[allow(unconditional_panic)] fn util() { let _a: u8 = 4.try_into().unwrap(); let _a: u8 = 5.try_into().expect(""); diff --git a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.rs b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.rs index e300ba18c33..e53d53db5f7 100644 --- a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.rs +++ b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.rs @@ -86,6 +86,7 @@ mod issue9612 { util(); } + #[allow(unconditional_panic)] fn util() { let _a: u8 = 4.try_into().unwrap(); let _a: u8 = 5.try_into().expect(""); diff --git a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.stderr b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.stderr index 320578bfabc..b58ce9b8af3 100644 --- a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.stderr +++ b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.stderr @@ -274,7 +274,7 @@ LL | let _ = &boxed_slice[1]; | ~~~~~~~~~~~~~~~ error: called `.get().unwrap()` on a slice - --> tests/ui-toml/unwrap_used/unwrap_used.rs:93:17 + --> tests/ui-toml/unwrap_used/unwrap_used.rs:94:17 | LL | let _ = Box::new([0]).get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/crashes/ice-12284.rs b/src/tools/clippy/tests/ui/crashes/ice-12284.rs deleted file mode 100644 index 8d1dbface8e..00000000000 --- a/src/tools/clippy/tests/ui/crashes/ice-12284.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![allow(incomplete_features)] -#![feature(unnamed_fields)] - -#[repr(C)] -struct Foo { - _: struct { - }, -} - -fn main() {} diff --git a/src/tools/clippy/tests/ui/dbg_macro/dbg_macro.fixed b/src/tools/clippy/tests/ui/dbg_macro/dbg_macro.fixed index e3525191423..bda9221a5e1 100644 --- a/src/tools/clippy/tests/ui/dbg_macro/dbg_macro.fixed +++ b/src/tools/clippy/tests/ui/dbg_macro/dbg_macro.fixed @@ -1,5 +1,5 @@ #![warn(clippy::dbg_macro)] -#![allow(clippy::unnecessary_operation, clippy::no_effect)] +#![allow(clippy::unnecessary_operation, clippy::no_effect, clippy::unit_arg)] fn foo(n: u32) -> u32 { if let Some(n) = n.checked_sub(4) { n } else { n } diff --git a/src/tools/clippy/tests/ui/dbg_macro/dbg_macro.rs b/src/tools/clippy/tests/ui/dbg_macro/dbg_macro.rs index 80606c2db05..8244254026b 100644 --- a/src/tools/clippy/tests/ui/dbg_macro/dbg_macro.rs +++ b/src/tools/clippy/tests/ui/dbg_macro/dbg_macro.rs @@ -1,5 +1,5 @@ #![warn(clippy::dbg_macro)] -#![allow(clippy::unnecessary_operation, clippy::no_effect)] +#![allow(clippy::unnecessary_operation, clippy::no_effect, clippy::unit_arg)] fn foo(n: u32) -> u32 { if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n } diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.fixed b/src/tools/clippy/tests/ui/doc/doc-fixable.fixed index edfffe8fcfe..355f2bc7736 100644 --- a/src/tools/clippy/tests/ui/doc/doc-fixable.fixed +++ b/src/tools/clippy/tests/ui/doc/doc-fixable.fixed @@ -3,7 +3,7 @@ #![allow(dead_code, incomplete_features)] #![warn(clippy::doc_markdown)] -#![feature(custom_inner_attributes, generic_const_exprs, const_option)] +#![feature(custom_inner_attributes, generic_const_exprs)] #![rustfmt::skip] /// The `foo_bar` function does _nothing_. See also `foo::bar`. (note the dot there) diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.rs b/src/tools/clippy/tests/ui/doc/doc-fixable.rs index 3c0f6913e32..9ced2677622 100644 --- a/src/tools/clippy/tests/ui/doc/doc-fixable.rs +++ b/src/tools/clippy/tests/ui/doc/doc-fixable.rs @@ -3,7 +3,7 @@ #![allow(dead_code, incomplete_features)] #![warn(clippy::doc_markdown)] -#![feature(custom_inner_attributes, generic_const_exprs, const_option)] +#![feature(custom_inner_attributes, generic_const_exprs)] #![rustfmt::skip] /// The foo_bar function does _nothing_. See also foo::bar. (note the dot there) diff --git a/src/tools/clippy/tests/ui/get_unwrap.fixed b/src/tools/clippy/tests/ui/get_unwrap.fixed index 62beb195939..2dd3c30a4e2 100644 --- a/src/tools/clippy/tests/ui/get_unwrap.fixed +++ b/src/tools/clippy/tests/ui/get_unwrap.fixed @@ -70,6 +70,7 @@ fn main() { mod issue9909 { #![allow(clippy::identity_op, clippy::unwrap_used, dead_code)] + #[allow(unconditional_panic)] fn reduced() { let f = &[1, 2, 3]; diff --git a/src/tools/clippy/tests/ui/get_unwrap.rs b/src/tools/clippy/tests/ui/get_unwrap.rs index 1e09ff5c67e..94226564cac 100644 --- a/src/tools/clippy/tests/ui/get_unwrap.rs +++ b/src/tools/clippy/tests/ui/get_unwrap.rs @@ -70,6 +70,7 @@ fn main() { mod issue9909 { #![allow(clippy::identity_op, clippy::unwrap_used, dead_code)] + #[allow(unconditional_panic)] fn reduced() { let f = &[1, 2, 3]; diff --git a/src/tools/clippy/tests/ui/get_unwrap.stderr b/src/tools/clippy/tests/ui/get_unwrap.stderr index 0f8b279da1e..8eacb249c60 100644 --- a/src/tools/clippy/tests/ui/get_unwrap.stderr +++ b/src/tools/clippy/tests/ui/get_unwrap.stderr @@ -266,7 +266,7 @@ LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice - --> tests/ui/get_unwrap.rs:77:24 + --> tests/ui/get_unwrap.rs:78:24 | LL | let _x: &i32 = f.get(1 + 2).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^ @@ -277,7 +277,7 @@ LL | let _x: &i32 = &f[1 + 2]; | ~~~~~~~~~ error: called `.get().unwrap()` on a slice - --> tests/ui/get_unwrap.rs:80:18 + --> tests/ui/get_unwrap.rs:81:18 | LL | let _x = f.get(1 + 2).unwrap().to_string(); | ^^^^^^^^^^^^^^^^^^^^^ @@ -288,7 +288,7 @@ LL | let _x = f[1 + 2].to_string(); | ~~~~~~~~ error: called `.get().unwrap()` on a slice - --> tests/ui/get_unwrap.rs:83:18 + --> tests/ui/get_unwrap.rs:84:18 | LL | let _x = f.get(1 + 2).unwrap().abs(); | ^^^^^^^^^^^^^^^^^^^^^ @@ -299,7 +299,7 @@ LL | let _x = f[1 + 2].abs(); | ~~~~~~~~ error: called `.get_mut().unwrap()` on a slice - --> tests/ui/get_unwrap.rs:100:33 + --> tests/ui/get_unwrap.rs:101:33 | LL | let b = rest.get_mut(linidx(j, k) - linidx(i, k) - 1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/needless_return.fixed b/src/tools/clippy/tests/ui/needless_return.fixed index ca422e605d6..aa2a274525b 100644 --- a/src/tools/clippy/tests/ui/needless_return.fixed +++ b/src/tools/clippy/tests/ui/needless_return.fixed @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![feature(yeet_expr)] #![allow(unused)] #![allow( @@ -9,6 +10,9 @@ )] #![warn(clippy::needless_return)] +extern crate proc_macros; +use proc_macros::with_span; + use std::cell::RefCell; macro_rules! the_answer { @@ -359,6 +363,10 @@ fn issue12907() -> String { "".split("").next().unwrap().to_string() } +fn issue13458() { + with_span!(span return); +} + fn main() {} fn a(x: Option<u8>) -> Option<u8> { diff --git a/src/tools/clippy/tests/ui/needless_return.rs b/src/tools/clippy/tests/ui/needless_return.rs index aad6e13136f..bf67cfd3698 100644 --- a/src/tools/clippy/tests/ui/needless_return.rs +++ b/src/tools/clippy/tests/ui/needless_return.rs @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![feature(yeet_expr)] #![allow(unused)] #![allow( @@ -9,6 +10,9 @@ )] #![warn(clippy::needless_return)] +extern crate proc_macros; +use proc_macros::with_span; + use std::cell::RefCell; macro_rules! the_answer { @@ -369,6 +373,10 @@ fn issue12907() -> String { return "".split("").next().unwrap().to_string(); } +fn issue13458() { + with_span!(span return); +} + fn main() {} fn a(x: Option<u8>) -> Option<u8> { diff --git a/src/tools/clippy/tests/ui/needless_return.stderr b/src/tools/clippy/tests/ui/needless_return.stderr index da0fa220d8c..d3c2a6badc0 100644 --- a/src/tools/clippy/tests/ui/needless_return.stderr +++ b/src/tools/clippy/tests/ui/needless_return.stderr @@ -1,5 +1,5 @@ error: unneeded `return` statement - --> tests/ui/needless_return.rs:25:5 + --> tests/ui/needless_return.rs:29:5 | LL | return true; | ^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:29:5 + --> tests/ui/needless_return.rs:33:5 | LL | return true; | ^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:34:5 + --> tests/ui/needless_return.rs:38:5 | LL | return true;;; | ^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:39:5 + --> tests/ui/needless_return.rs:43:5 | LL | return true;; ; ; | ^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:44:9 + --> tests/ui/needless_return.rs:48:9 | LL | return true; | ^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:46:9 + --> tests/ui/needless_return.rs:50:9 | LL | return false; | ^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + false | error: unneeded `return` statement - --> tests/ui/needless_return.rs:52:17 + --> tests/ui/needless_return.rs:56:17 | LL | true => return false, | ^^^^^^^^^^^^ @@ -84,7 +84,7 @@ LL | true => false, | ~~~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:54:13 + --> tests/ui/needless_return.rs:58:13 | LL | return true; | ^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:61:9 + --> tests/ui/needless_return.rs:65:9 | LL | return true; | ^^^^^^^^^^^ @@ -108,7 +108,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:63:16 + --> tests/ui/needless_return.rs:67:16 | LL | let _ = || return true; | ^^^^^^^^^^^ @@ -119,7 +119,7 @@ LL | let _ = || true; | ~~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:67:5 + --> tests/ui/needless_return.rs:71:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^ @@ -131,7 +131,7 @@ LL + the_answer!() | error: unneeded `return` statement - --> tests/ui/needless_return.rs:70:21 + --> tests/ui/needless_return.rs:74:21 | LL | fn test_void_fun() { | _____________________^ @@ -146,7 +146,7 @@ LL + fn test_void_fun() { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:75:11 + --> tests/ui/needless_return.rs:79:11 | LL | if b { | ___________^ @@ -161,7 +161,7 @@ LL + if b { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:77:13 + --> tests/ui/needless_return.rs:81:13 | LL | } else { | _____________^ @@ -176,7 +176,7 @@ LL + } else { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:85:14 + --> tests/ui/needless_return.rs:89:14 | LL | _ => return, | ^^^^^^ @@ -187,7 +187,7 @@ LL | _ => (), | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:93:24 + --> tests/ui/needless_return.rs:97:24 | LL | let _ = 42; | ________________________^ @@ -202,7 +202,7 @@ LL + let _ = 42; | error: unneeded `return` statement - --> tests/ui/needless_return.rs:96:14 + --> tests/ui/needless_return.rs:100:14 | LL | _ => return, | ^^^^^^ @@ -213,7 +213,7 @@ LL | _ => (), | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:109:9 + --> tests/ui/needless_return.rs:113:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -225,7 +225,7 @@ LL + String::from("test") | error: unneeded `return` statement - --> tests/ui/needless_return.rs:111:9 + --> tests/ui/needless_return.rs:115:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^ @@ -237,7 +237,7 @@ LL + String::new() | error: unneeded `return` statement - --> tests/ui/needless_return.rs:133:32 + --> tests/ui/needless_return.rs:137:32 | LL | bar.unwrap_or_else(|_| return) | ^^^^^^ @@ -248,7 +248,7 @@ LL | bar.unwrap_or_else(|_| {}) | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:137:21 + --> tests/ui/needless_return.rs:141:21 | LL | let _ = || { | _____________________^ @@ -263,7 +263,7 @@ LL + let _ = || { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:140:20 + --> tests/ui/needless_return.rs:144:20 | LL | let _ = || return; | ^^^^^^ @@ -274,7 +274,7 @@ LL | let _ = || {}; | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:146:32 + --> tests/ui/needless_return.rs:150:32 | LL | res.unwrap_or_else(|_| return Foo) | ^^^^^^^^^^ @@ -285,7 +285,7 @@ LL | res.unwrap_or_else(|_| Foo) | ~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:155:5 + --> tests/ui/needless_return.rs:159:5 | LL | return true; | ^^^^^^^^^^^ @@ -297,7 +297,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:159:5 + --> tests/ui/needless_return.rs:163:5 | LL | return true; | ^^^^^^^^^^^ @@ -309,7 +309,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:164:9 + --> tests/ui/needless_return.rs:168:9 | LL | return true; | ^^^^^^^^^^^ @@ -321,7 +321,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:166:9 + --> tests/ui/needless_return.rs:170:9 | LL | return false; | ^^^^^^^^^^^^ @@ -333,7 +333,7 @@ LL + false | error: unneeded `return` statement - --> tests/ui/needless_return.rs:172:17 + --> tests/ui/needless_return.rs:176:17 | LL | true => return false, | ^^^^^^^^^^^^ @@ -344,7 +344,7 @@ LL | true => false, | ~~~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:174:13 + --> tests/ui/needless_return.rs:178:13 | LL | return true; | ^^^^^^^^^^^ @@ -356,7 +356,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:181:9 + --> tests/ui/needless_return.rs:185:9 | LL | return true; | ^^^^^^^^^^^ @@ -368,7 +368,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:183:16 + --> tests/ui/needless_return.rs:187:16 | LL | let _ = || return true; | ^^^^^^^^^^^ @@ -379,7 +379,7 @@ LL | let _ = || true; | ~~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:187:5 + --> tests/ui/needless_return.rs:191:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^ @@ -391,7 +391,7 @@ LL + the_answer!() | error: unneeded `return` statement - --> tests/ui/needless_return.rs:190:33 + --> tests/ui/needless_return.rs:194:33 | LL | async fn async_test_void_fun() { | _________________________________^ @@ -406,7 +406,7 @@ LL + async fn async_test_void_fun() { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:195:11 + --> tests/ui/needless_return.rs:199:11 | LL | if b { | ___________^ @@ -421,7 +421,7 @@ LL + if b { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:197:13 + --> tests/ui/needless_return.rs:201:13 | LL | } else { | _____________^ @@ -436,7 +436,7 @@ LL + } else { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:205:14 + --> tests/ui/needless_return.rs:209:14 | LL | _ => return, | ^^^^^^ @@ -447,7 +447,7 @@ LL | _ => (), | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:218:9 + --> tests/ui/needless_return.rs:222:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -459,7 +459,7 @@ LL + String::from("test") | error: unneeded `return` statement - --> tests/ui/needless_return.rs:220:9 + --> tests/ui/needless_return.rs:224:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^ @@ -471,7 +471,7 @@ LL + String::new() | error: unneeded `return` statement - --> tests/ui/needless_return.rs:236:5 + --> tests/ui/needless_return.rs:240:5 | LL | return format!("Hello {}", "world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -483,7 +483,7 @@ LL + format!("Hello {}", "world!") | error: unneeded `return` statement - --> tests/ui/needless_return.rs:277:9 + --> tests/ui/needless_return.rs:281:9 | LL | return true; | ^^^^^^^^^^^ @@ -497,7 +497,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:279:9 + --> tests/ui/needless_return.rs:283:9 | LL | return false; | ^^^^^^^^^^^^ @@ -509,7 +509,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:286:13 + --> tests/ui/needless_return.rs:290:13 | LL | return 10; | ^^^^^^^^^ @@ -524,7 +524,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:289:13 + --> tests/ui/needless_return.rs:293:13 | LL | return 100; | ^^^^^^^^^^ @@ -537,7 +537,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:297:9 + --> tests/ui/needless_return.rs:301:9 | LL | return 0; | ^^^^^^^^ @@ -549,7 +549,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:304:13 + --> tests/ui/needless_return.rs:308:13 | LL | return *(x as *const isize); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -564,7 +564,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:306:13 + --> tests/ui/needless_return.rs:310:13 | LL | return !*(x as *const isize); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -577,7 +577,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:313:20 + --> tests/ui/needless_return.rs:317:20 | LL | let _ = 42; | ____________________^ @@ -594,7 +594,7 @@ LL + let _ = 42; | error: unneeded `return` statement - --> tests/ui/needless_return.rs:320:20 + --> tests/ui/needless_return.rs:324:20 | LL | let _ = 42; return; | ^^^^^^^ @@ -606,7 +606,7 @@ LL + let _ = 42; | error: unneeded `return` statement - --> tests/ui/needless_return.rs:332:9 + --> tests/ui/needless_return.rs:336:9 | LL | return Ok(format!("ok!")); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -618,7 +618,7 @@ LL + Ok(format!("ok!")) | error: unneeded `return` statement - --> tests/ui/needless_return.rs:334:9 + --> tests/ui/needless_return.rs:338:9 | LL | return Err(format!("err!")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -630,7 +630,7 @@ LL + Err(format!("err!")) | error: unneeded `return` statement - --> tests/ui/needless_return.rs:340:9 + --> tests/ui/needless_return.rs:344:9 | LL | return if true { 1 } else { 2 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -642,7 +642,7 @@ LL + if true { 1 } else { 2 } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:344:9 + --> tests/ui/needless_return.rs:348:9 | LL | return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -654,7 +654,7 @@ LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else | error: unneeded `return` statement - --> tests/ui/needless_return.rs:365:5 + --> tests/ui/needless_return.rs:369:5 | LL | return { "a".to_string() } + "b" + { "c" }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -666,7 +666,7 @@ LL + ({ "a".to_string() } + "b" + { "c" }) | error: unneeded `return` statement - --> tests/ui/needless_return.rs:369:5 + --> tests/ui/needless_return.rs:373:5 | LL | return "".split("").next().unwrap().to_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/transmute_float_to_int.fixed b/src/tools/clippy/tests/ui/transmute_float_to_int.fixed index 83814ca43b9..075a198918a 100644 --- a/src/tools/clippy/tests/ui/transmute_float_to_int.fixed +++ b/src/tools/clippy/tests/ui/transmute_float_to_int.fixed @@ -1,7 +1,7 @@ #![warn(clippy::transmute_float_to_int)] #![allow(clippy::missing_transmute_annotations)] -#![feature(f128, f128_const)] -#![feature(f16, f16_const)] +#![feature(f128)] +#![feature(f16)] fn float_to_int() { let _: u32 = unsafe { 1f32.to_bits() }; diff --git a/src/tools/clippy/tests/ui/transmute_float_to_int.rs b/src/tools/clippy/tests/ui/transmute_float_to_int.rs index 64d6e917203..12541b2f7cf 100644 --- a/src/tools/clippy/tests/ui/transmute_float_to_int.rs +++ b/src/tools/clippy/tests/ui/transmute_float_to_int.rs @@ -1,7 +1,7 @@ #![warn(clippy::transmute_float_to_int)] #![allow(clippy::missing_transmute_annotations)] -#![feature(f128, f128_const)] -#![feature(f16, f16_const)] +#![feature(f128)] +#![feature(f16)] fn float_to_int() { let _: u32 = unsafe { std::mem::transmute(1f32) }; diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed index e95054a7ccb..617d32d1fa7 100644 --- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed +++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed @@ -84,8 +84,11 @@ fn issue_10449() { } // Pointers cannot be cast to integers in const contexts +#[allow(ptr_to_integer_transmute_in_consts, reason = "This is tested in the compiler test suite")] const fn issue_12402<P>(ptr: *const P) { - unsafe { transmute::<*const i32, usize>(&42i32) }; - unsafe { transmute::<fn(*const P), usize>(issue_12402) }; - let _ = unsafe { transmute::<_, usize>(ptr) }; + // This test exists even though the compiler lints against it + // to test that clippy's transmute lints do not trigger on this. + unsafe { std::mem::transmute::<*const i32, usize>(&42i32) }; + unsafe { std::mem::transmute::<fn(*const P), usize>(issue_12402) }; + let _ = unsafe { std::mem::transmute::<_, usize>(ptr) }; } diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs index e5fcdef7a1c..d68db3c2deb 100644 --- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs +++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs @@ -84,8 +84,11 @@ fn issue_10449() { } // Pointers cannot be cast to integers in const contexts +#[allow(ptr_to_integer_transmute_in_consts, reason = "This is tested in the compiler test suite")] const fn issue_12402<P>(ptr: *const P) { - unsafe { transmute::<*const i32, usize>(&42i32) }; - unsafe { transmute::<fn(*const P), usize>(issue_12402) }; - let _ = unsafe { transmute::<_, usize>(ptr) }; + // This test exists even though the compiler lints against it + // to test that clippy's transmute lints do not trigger on this. + unsafe { std::mem::transmute::<*const i32, usize>(&42i32) }; + unsafe { std::mem::transmute::<fn(*const P), usize>(issue_12402) }; + let _ = unsafe { std::mem::transmute::<_, usize>(ptr) }; } diff --git a/src/tools/compiletest/src/command-list.rs b/src/tools/compiletest/src/command-list.rs index a4cedbf66e2..4d57907f26f 100644 --- a/src/tools/compiletest/src/command-list.rs +++ b/src/tools/compiletest/src/command-list.rs @@ -42,11 +42,14 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-cdb", "ignore-compare-mode-next-solver", "ignore-compare-mode-polonius", + "ignore-coverage-map", + "ignore-coverage-run", "ignore-cross-compile", "ignore-debug", "ignore-eabi", "ignore-emscripten", "ignore-endian-big", + "ignore-enzyme", "ignore-freebsd", "ignore-fuchsia", "ignore-gdb", @@ -64,23 +67,6 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-loongarch64", "ignore-macabi", "ignore-macos", - "ignore-mode-assembly", - "ignore-mode-codegen", - "ignore-mode-codegen-units", - "ignore-mode-coverage-map", - "ignore-mode-coverage-run", - "ignore-mode-crashes", - "ignore-mode-debuginfo", - "ignore-mode-incremental", - "ignore-mode-js-doc-test", - "ignore-mode-mir-opt", - "ignore-mode-pretty", - "ignore-mode-run-make", - "ignore-mode-run-pass-valgrind", - "ignore-mode-rustdoc", - "ignore-mode-rustdoc-json", - "ignore-mode-ui", - "ignore-mode-ui-fulldeps", "ignore-msp430", "ignore-msvc", "ignore-musl", @@ -144,7 +130,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "needs-git-hash", "needs-llvm-components", "needs-llvm-zstd", - "needs-profiler-support", + "needs-profiler-runtime", "needs-relocation-model-pic", "needs-run-enabled", "needs-rust-lld", @@ -223,6 +209,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "pretty-compare-only", "pretty-expanded", "pretty-mode", + "reference", "regex-error-pattern", "remap-src-base", "revisions", diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index adc89cad72f..a5418ad8384 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -53,7 +53,6 @@ macro_rules! string_enum { string_enum! { #[derive(Clone, Copy, PartialEq, Debug)] pub enum Mode { - RunPassValgrind => "run-pass-valgrind", Pretty => "pretty", DebugInfo => "debuginfo", Codegen => "codegen", @@ -207,13 +206,6 @@ pub struct Config { /// Path to LLVM's bin directory. pub llvm_bin_dir: Option<PathBuf>, - /// The valgrind path. - pub valgrind_path: Option<String>, - - /// Whether to fail if we can't run run-pass-valgrind tests under valgrind - /// (or, alternatively, to silently run them like regular run-pass tests). - pub force_valgrind: bool, - /// The path to the Clang executable to run Clang-based tests with. If /// `None` then these tests will be ignored. pub run_clang_based_tests_with: Option<String>, @@ -393,8 +385,8 @@ pub struct Config { pub git_merge_commit_email: String, /// True if the profiler runtime is enabled for this target. - /// Used by the "needs-profiler-support" header in test files. - pub profiler_support: bool, + /// Used by the "needs-profiler-runtime" directive in test files. + pub profiler_runtime: bool, } impl Config { diff --git a/src/tools/compiletest/src/debuggers.rs b/src/tools/compiletest/src/debuggers.rs new file mode 100644 index 00000000000..b605bc813f1 --- /dev/null +++ b/src/tools/compiletest/src/debuggers.rs @@ -0,0 +1,272 @@ +use std::env; +use std::ffi::OsString; +use std::path::PathBuf; +use std::process::Command; +use std::sync::Arc; + +use crate::common::{Config, Debugger}; + +pub(crate) fn configure_cdb(config: &Config) -> Option<Arc<Config>> { + config.cdb.as_ref()?; + + Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() })) +} + +pub(crate) fn configure_gdb(config: &Config) -> Option<Arc<Config>> { + config.gdb_version?; + + if config.matches_env("msvc") { + return None; + } + + if config.remote_test_client.is_some() && !config.target.contains("android") { + println!( + "WARNING: debuginfo tests are not available when \ + testing with remote" + ); + return None; + } + + if config.target.contains("android") { + println!( + "{} debug-info test uses tcp 5039 port.\ + please reserve it", + config.target + ); + + // android debug-info test uses remote debugger so, we test 1 thread + // at once as they're all sharing the same TCP port to communicate + // over. + // + // we should figure out how to lift this restriction! (run them all + // on different ports allocated dynamically). + env::set_var("RUST_TEST_THREADS", "1"); + } + + Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() })) +} + +pub(crate) fn configure_lldb(config: &Config) -> Option<Arc<Config>> { + config.lldb_python_dir.as_ref()?; + + if let Some(350) = config.lldb_version { + println!( + "WARNING: The used version of LLDB (350) has a \ + known issue that breaks debuginfo tests. See \ + issue #32520 for more information. Skipping all \ + LLDB-based tests!", + ); + return None; + } + + Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() })) +} + +/// Returns `true` if the given target is an Android target for the +/// purposes of GDB testing. +pub(crate) fn is_android_gdb_target(target: &str) -> bool { + matches!( + &target[..], + "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" + ) +} + +/// Returns `true` if the given target is a MSVC target for the purposes of CDB testing. +fn is_pc_windows_msvc_target(target: &str) -> bool { + target.ends_with("-pc-windows-msvc") +} + +fn find_cdb(target: &str) -> Option<OsString> { + if !(cfg!(windows) && is_pc_windows_msvc_target(target)) { + return None; + } + + let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?; + let cdb_arch = if cfg!(target_arch = "x86") { + "x86" + } else if cfg!(target_arch = "x86_64") { + "x64" + } else if cfg!(target_arch = "aarch64") { + "arm64" + } else if cfg!(target_arch = "arm") { + "arm" + } else { + return None; // No compatible CDB.exe in the Windows 10 SDK + }; + + let mut path = PathBuf::new(); + path.push(pf86); + path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too? + path.push(cdb_arch); + path.push(r"cdb.exe"); + + if !path.exists() { + return None; + } + + Some(path.into_os_string()) +} + +/// Returns Path to CDB +pub(crate) fn analyze_cdb( + cdb: Option<String>, + target: &str, +) -> (Option<OsString>, Option<[u16; 4]>) { + let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target)); + + let mut version = None; + if let Some(cdb) = cdb.as_ref() { + if let Ok(output) = Command::new(cdb).arg("/version").output() { + if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { + version = extract_cdb_version(&first_line); + } + } + } + + (cdb, version) +} + +pub(crate) fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> { + // Example full_version_line: "cdb version 10.0.18362.1" + let version = full_version_line.rsplit(' ').next()?; + let mut components = version.split('.'); + let major: u16 = components.next().unwrap().parse().unwrap(); + let minor: u16 = components.next().unwrap().parse().unwrap(); + let patch: u16 = components.next().unwrap_or("0").parse().unwrap(); + let build: u16 = components.next().unwrap_or("0").parse().unwrap(); + Some([major, minor, patch, build]) +} + +/// Returns (Path to GDB, GDB Version) +pub(crate) fn analyze_gdb( + gdb: Option<String>, + target: &str, + android_cross_path: &PathBuf, +) -> (Option<String>, Option<u32>) { + #[cfg(not(windows))] + const GDB_FALLBACK: &str = "gdb"; + #[cfg(windows)] + const GDB_FALLBACK: &str = "gdb.exe"; + + let fallback_gdb = || { + if is_android_gdb_target(target) { + let mut gdb_path = match android_cross_path.to_str() { + Some(x) => x.to_owned(), + None => panic!("cannot find android cross path"), + }; + gdb_path.push_str("/bin/gdb"); + gdb_path + } else { + GDB_FALLBACK.to_owned() + } + }; + + let gdb = match gdb { + None => fallback_gdb(), + Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb + Some(ref s) => s.to_owned(), + }; + + let mut version_line = None; + if let Ok(output) = Command::new(&gdb).arg("--version").output() { + if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { + version_line = Some(first_line.to_string()); + } + } + + let version = match version_line { + Some(line) => extract_gdb_version(&line), + None => return (None, None), + }; + + (Some(gdb), version) +} + +pub(crate) fn extract_gdb_version(full_version_line: &str) -> Option<u32> { + let full_version_line = full_version_line.trim(); + + // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both + // of the ? sections being optional + + // We will parse up to 3 digits for each component, ignoring the date + + // We skip text in parentheses. This avoids accidentally parsing + // the openSUSE version, which looks like: + // GNU gdb (GDB; openSUSE Leap 15.0) 8.1 + // This particular form is documented in the GNU coding standards: + // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion + + let unbracketed_part = full_version_line.split('[').next().unwrap(); + let mut splits = unbracketed_part.trim_end().rsplit(' '); + let version_string = splits.next().unwrap(); + + let mut splits = version_string.split('.'); + let major = splits.next().unwrap(); + let minor = splits.next().unwrap(); + let patch = splits.next(); + + let major: u32 = major.parse().unwrap(); + let (minor, patch): (u32, u32) = match minor.find(not_a_digit) { + None => { + let minor = minor.parse().unwrap(); + let patch: u32 = match patch { + Some(patch) => match patch.find(not_a_digit) { + None => patch.parse().unwrap(), + Some(idx) if idx > 3 => 0, + Some(idx) => patch[..idx].parse().unwrap(), + }, + None => 0, + }; + (minor, patch) + } + // There is no patch version after minor-date (e.g. "4-2012"). + Some(idx) => { + let minor = minor[..idx].parse().unwrap(); + (minor, 0) + } + }; + + Some(((major * 1000) + minor) * 1000 + patch) +} + +/// Returns LLDB version +pub(crate) fn extract_lldb_version(full_version_line: &str) -> Option<u32> { + // Extract the major LLDB version from the given version string. + // LLDB version strings are different for Apple and non-Apple platforms. + // The Apple variant looks like this: + // + // LLDB-179.5 (older versions) + // lldb-300.2.51 (new versions) + // + // We are only interested in the major version number, so this function + // will return `Some(179)` and `Some(300)` respectively. + // + // Upstream versions look like: + // lldb version 6.0.1 + // + // There doesn't seem to be a way to correlate the Apple version + // with the upstream version, and since the tests were originally + // written against Apple versions, we make a fake Apple version by + // multiplying the first number by 100. This is a hack. + + let full_version_line = full_version_line.trim(); + + if let Some(apple_ver) = + full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-")) + { + if let Some(idx) = apple_ver.find(not_a_digit) { + let version: u32 = apple_ver[..idx].parse().unwrap(); + return Some(version); + } + } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") { + if let Some(idx) = lldb_ver.find(not_a_digit) { + let version: u32 = lldb_ver[..idx].parse().ok()?; + return Some(version * 100); + } + } + None +} + +fn not_a_digit(c: char) -> bool { + !c.is_ascii_digit() +} diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 83a10c56208..099e620ffe0 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -5,17 +5,17 @@ use std::io::BufReader; use std::io::prelude::*; use std::path::{Path, PathBuf}; use std::process::Command; -use std::sync::OnceLock; -use regex::Regex; use tracing::*; use crate::common::{Config, Debugger, FailMode, Mode, PassMode}; +use crate::debuggers::{extract_cdb_version, extract_gdb_version}; +use crate::header::auxiliary::{AuxProps, parse_and_update_aux}; use crate::header::cfg::{MatchOutcome, parse_cfg_name_directive}; use crate::header::needs::CachedNeedsConditions; use crate::util::static_regex; -use crate::{extract_cdb_version, extract_gdb_version}; +pub(crate) mod auxiliary; mod cfg; mod needs; #[cfg(test)] @@ -35,9 +35,10 @@ impl HeadersCache { /// the test. #[derive(Default)] pub struct EarlyProps { - pub aux: Vec<String>, - pub aux_bin: Vec<String>, - pub aux_crate: Vec<(String, String)>, + /// Auxiliary crates that should be built and made available to this test. + /// Included in [`EarlyProps`] so that the indicated files can participate + /// in up-to-date checking. Building happens via [`TestProps::aux`] instead. + pub(crate) aux: AuxProps, pub revisions: Vec<String>, } @@ -56,23 +57,9 @@ impl EarlyProps { &mut poisoned, testfile, rdr, - &mut |HeaderLine { directive: ln, .. }| { - config.push_name_value_directive(ln, directives::AUX_BUILD, &mut props.aux, |r| { - r.trim().to_string() - }); - config.push_name_value_directive( - ln, - directives::AUX_BIN, - &mut props.aux_bin, - |r| r.trim().to_string(), - ); - config.push_name_value_directive( - ln, - directives::AUX_CRATE, - &mut props.aux_crate, - Config::parse_aux_crate, - ); - config.parse_and_update_revisions(ln, &mut props.revisions); + &mut |DirectiveLine { directive: ln, .. }| { + parse_and_update_aux(config, ln, &mut props.aux); + config.parse_and_update_revisions(testfile, ln, &mut props.revisions); }, ); @@ -100,18 +87,8 @@ pub struct TestProps { // If present, the name of a file that this test should match when // pretty-printed pub pp_exact: Option<PathBuf>, - // Other crates that should be compiled (typically from the same - // directory as the test, but for backwards compatibility reasons - // we also check the auxiliary directory) - pub aux_builds: Vec<String>, - // Auxiliary crates that should be compiled as `#![crate_type = "bin"]`. - pub aux_bins: Vec<String>, - // Similar to `aux_builds`, but a list of NAME=somelib.rs of dependencies - // to build and pass with the `--extern` flag. - pub aux_crates: Vec<(String, String)>, - /// Similar to `aux_builds`, but also passes the resulting dylib path to - /// `-Zcodegen-backend`. - pub aux_codegen_backend: Option<String>, + /// Auxiliary crates that should be built and made available to this test. + pub(crate) aux: AuxProps, // Environment settings to use for compiling pub rustc_env: Vec<(String, String)>, // Environment variables to unset prior to compiling. @@ -278,10 +255,7 @@ impl TestProps { run_flags: vec![], doc_flags: vec![], pp_exact: None, - aux_builds: vec![], - aux_bins: vec![], - aux_crates: vec![], - aux_codegen_backend: None, + aux: Default::default(), revisions: vec![], rustc_env: vec![ ("RUSTC_ICE".to_string(), "0".to_string()), @@ -370,7 +344,7 @@ impl TestProps { &mut poisoned, testfile, file, - &mut |HeaderLine { header_revision, directive: ln, .. }| { + &mut |DirectiveLine { header_revision, directive: ln, .. }| { if header_revision.is_some() && header_revision != test_revision { return; } @@ -417,7 +391,7 @@ impl TestProps { has_edition = true; } - config.parse_and_update_revisions(ln, &mut self.revisions); + config.parse_and_update_revisions(testfile, ln, &mut self.revisions); if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) { self.run_flags.extend(split_flags(&flags)); @@ -456,21 +430,10 @@ impl TestProps { PRETTY_COMPARE_ONLY, &mut self.pretty_compare_only, ); - config.push_name_value_directive(ln, AUX_BUILD, &mut self.aux_builds, |r| { - r.trim().to_string() - }); - config.push_name_value_directive(ln, AUX_BIN, &mut self.aux_bins, |r| { - r.trim().to_string() - }); - config.push_name_value_directive( - ln, - AUX_CRATE, - &mut self.aux_crates, - Config::parse_aux_crate, - ); - if let Some(r) = config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND) { - self.aux_codegen_backend = Some(r.trim().to_owned()); - } + + // Call a helper method to deal with aux-related directives. + parse_and_update_aux(config, ln, &mut self.aux); + config.push_name_value_directive( ln, EXEC_ENV, @@ -717,7 +680,7 @@ impl TestProps { /// Extract an `(Option<line_revision>, directive)` directive from a line if comment is present. /// -/// See [`HeaderLine`] for a diagram. +/// See [`DirectiveLine`] for a diagram. pub fn line_directive<'line>( comment: &str, original_line: &'line str, @@ -775,17 +738,13 @@ const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] = /// ```text /// //@ compile-flags: -O /// ^^^^^^^^^^^^^^^^^ directive -/// ^^^^^^^^^^^^^^^^^^^^^ original_line /// /// //@ [foo] compile-flags: -O /// ^^^ header_revision /// ^^^^^^^^^^^^^^^^^ directive -/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ original_line /// ``` -struct HeaderLine<'ln> { +struct DirectiveLine<'ln> { line_number: usize, - /// Raw line from the test file, including comment prefix and any revision. - original_line: &'ln str, /// Some header directives start with a revision name in square brackets /// (e.g. `[foo]`), and only apply to that revision of the test. /// If present, this field contains the revision name (e.g. `foo`). @@ -797,7 +756,6 @@ struct HeaderLine<'ln> { pub(crate) struct CheckDirectiveResult<'ln> { is_known_directive: bool, - directive_name: &'ln str, trailing_directive: Option<&'ln str>, } @@ -832,11 +790,7 @@ pub(crate) fn check_directive<'a>( } .then_some(trailing); - CheckDirectiveResult { - is_known_directive: is_known(&directive_name), - directive_name: directive_ln, - trailing_directive, - } + CheckDirectiveResult { is_known_directive: is_known(&directive_name), trailing_directive } } fn iter_header( @@ -845,150 +799,119 @@ fn iter_header( poisoned: &mut bool, testfile: &Path, rdr: impl Read, - it: &mut dyn FnMut(HeaderLine<'_>), + it: &mut dyn FnMut(DirectiveLine<'_>), ) { if testfile.is_dir() { return; } - // Coverage tests in coverage-run mode always have these extra directives, - // without needing to specify them manually in every test file. - // (Some of the comments below have been copied over from the old - // `tests/run-make/coverage-reports/Makefile`, which no longer exists.) + // Coverage tests in coverage-run mode always have these extra directives, without needing to + // specify them manually in every test file. (Some of the comments below have been copied over + // from the old `tests/run-make/coverage-reports/Makefile`, which no longer exists.) + // + // FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later. if mode == Mode::CoverageRun { let extra_directives: &[&str] = &[ - "needs-profiler-support", - // FIXME(pietroalbini): this test currently does not work on cross-compiled - // targets because remote-test is not capable of sending back the *.profraw - // files generated by the LLVM instrumentation. + "needs-profiler-runtime", + // FIXME(pietroalbini): this test currently does not work on cross-compiled targets + // because remote-test is not capable of sending back the *.profraw files generated by + // the LLVM instrumentation. "ignore-cross-compile", ]; // Process the extra implied directives, with a dummy line number of 0. for directive in extra_directives { - it(HeaderLine { line_number: 0, original_line: "", header_revision: None, directive }); + it(DirectiveLine { line_number: 0, header_revision: None, directive }); } } + // NOTE(jieyouxu): once we get rid of `Makefile`s we can unconditionally check for `//@`. let comment = if testfile.extension().is_some_and(|e| e == "rs") { "//@" } else { "#" }; let mut rdr = BufReader::with_capacity(1024, rdr); let mut ln = String::new(); let mut line_number = 0; - // Match on error annotations like `//~ERROR`. - static REVISION_MAGIC_COMMENT_RE: OnceLock<Regex> = OnceLock::new(); - let revision_magic_comment_re = - REVISION_MAGIC_COMMENT_RE.get_or_init(|| Regex::new("//(\\[.*\\])?~.*").unwrap()); - loop { line_number += 1; ln.clear(); if rdr.read_line(&mut ln).unwrap() == 0 { break; } - - // Assume that any directives will be found before the first - // module or function. This doesn't seem to be an optimization - // with a warm page cache. Maybe with a cold one. - let original_line = &ln; let ln = ln.trim(); + + // Assume that any directives will be found before the first module or function. This + // doesn't seem to be an optimization with a warm page cache. Maybe with a cold one. + // FIXME(jieyouxu): this will cause `//@` directives in the rest of the test file to + // not be checked. if ln.starts_with("fn") || ln.starts_with("mod") { return; + } - // First try to accept `ui_test` style comments (`//@`) - } else if let Some((header_revision, non_revisioned_directive_line)) = - line_directive(comment, ln) - { - // Perform unknown directive check on Rust files. - if testfile.extension().map(|e| e == "rs").unwrap_or(false) { - let directive_ln = non_revisioned_directive_line.trim(); - - let CheckDirectiveResult { is_known_directive, trailing_directive, .. } = - check_directive(directive_ln, mode, ln); - - if !is_known_directive { - *poisoned = true; - - eprintln!( - "error: detected unknown compiletest test directive `{}` in {}:{}", - directive_ln, - testfile.display(), - line_number, - ); - - return; - } + let Some((header_revision, non_revisioned_directive_line)) = line_directive(comment, ln) + else { + continue; + }; - if let Some(trailing_directive) = &trailing_directive { - *poisoned = true; + // Perform unknown directive check on Rust files. + if testfile.extension().map(|e| e == "rs").unwrap_or(false) { + let directive_ln = non_revisioned_directive_line.trim(); - eprintln!( - "error: detected trailing compiletest test directive `{}` in {}:{}\n \ - help: put the trailing directive in it's own line: `//@ {}`", - trailing_directive, - testfile.display(), - line_number, - trailing_directive, - ); + let CheckDirectiveResult { is_known_directive, trailing_directive } = + check_directive(directive_ln, mode, ln); - return; - } - } + if !is_known_directive { + *poisoned = true; - it(HeaderLine { - line_number, - original_line, - header_revision, - directive: non_revisioned_directive_line, - }); - // Then we try to check for legacy-style candidates, which are not the magic ~ERROR family - // error annotations. - } else if !revision_magic_comment_re.is_match(ln) { - let Some((_, rest)) = line_directive("//", ln) else { - continue; - }; + eprintln!( + "error: detected unknown compiletest test directive `{}` in {}:{}", + directive_ln, + testfile.display(), + line_number, + ); - if rest.trim_start().starts_with(':') { - // This is likely a markdown link: - // `[link_name]: https://example.org` - continue; + return; } - let rest = rest.trim_start(); - - let CheckDirectiveResult { is_known_directive, directive_name, .. } = - check_directive(rest, mode, ln); - - if is_known_directive { + if let Some(trailing_directive) = &trailing_directive { *poisoned = true; + eprintln!( - "error: detected legacy-style directive {} in compiletest test: {}:{}, please use `ui_test`-style directives `//@` instead: {:#?}", - directive_name, + "error: detected trailing compiletest test directive `{}` in {}:{}\n \ + help: put the trailing directive in it's own line: `//@ {}`", + trailing_directive, testfile.display(), line_number, - line_directive("//", ln), + trailing_directive, ); + return; } } + + it(DirectiveLine { + line_number, + header_revision, + directive: non_revisioned_directive_line, + }); } } impl Config { - fn parse_aux_crate(r: String) -> (String, String) { - let mut parts = r.trim().splitn(2, '='); - ( - parts.next().expect("missing aux-crate name (e.g. log=log.rs)").to_string(), - parts.next().expect("missing aux-crate value (e.g. log=log.rs)").to_string(), - ) - } - - fn parse_and_update_revisions(&self, line: &str, existing: &mut Vec<String>) { + fn parse_and_update_revisions(&self, testfile: &Path, line: &str, existing: &mut Vec<String>) { if let Some(raw) = self.parse_name_value_directive(line, "revisions") { + if self.mode == Mode::RunMake { + panic!("`run-make` tests do not support revisions: {}", testfile.display()); + } + let mut duplicates: HashSet<_> = existing.iter().cloned().collect(); for revision in raw.split_whitespace().map(|r| r.to_string()) { if !duplicates.insert(revision.clone()) { - panic!("Duplicate revision: `{}` in line `{}`", revision, raw); + panic!( + "duplicate revision: `{}` in line `{}`: {}", + revision, + raw, + testfile.display() + ); } existing.push(revision); } @@ -1362,13 +1285,14 @@ pub fn make_test_description<R: Read>( let mut local_poisoned = false; + // Scan through the test file to handle `ignore-*`, `only-*`, and `needs-*` directives. iter_header( config.mode, &config.suite, &mut local_poisoned, path, src, - &mut |HeaderLine { header_revision, original_line, directive: ln, line_number }| { + &mut |DirectiveLine { header_revision, directive: ln, line_number }| { if header_revision.is_some() && header_revision != test_revision { return; } @@ -1393,17 +1317,7 @@ pub fn make_test_description<R: Read>( }; } - if let Some((_, post)) = original_line.trim_start().split_once("//") { - let post = post.trim_start(); - if post.starts_with("ignore-tidy") { - // Not handled by compiletest. - } else { - decision!(cfg::handle_ignore(config, ln)); - } - } else { - decision!(cfg::handle_ignore(config, ln)); - } - + decision!(cfg::handle_ignore(config, ln)); decision!(cfg::handle_only(config, ln)); decision!(needs::handle_needs(&cache.needs, config, ln)); decision!(ignore_llvm(config, ln)); diff --git a/src/tools/compiletest/src/header/auxiliary.rs b/src/tools/compiletest/src/header/auxiliary.rs new file mode 100644 index 00000000000..6f6538ce196 --- /dev/null +++ b/src/tools/compiletest/src/header/auxiliary.rs @@ -0,0 +1,60 @@ +//! Code for dealing with test directives that request an "auxiliary" crate to +//! be built and made available to the test in some way. + +use std::iter; + +use crate::common::Config; +use crate::header::directives::{AUX_BIN, AUX_BUILD, AUX_CODEGEN_BACKEND, AUX_CRATE}; + +/// Properties parsed from `aux-*` test directives. +#[derive(Clone, Debug, Default)] +pub(crate) struct AuxProps { + /// Other crates that should be built and made available to this test. + /// These are filenames relative to `./auxiliary/` in the test's directory. + pub(crate) builds: Vec<String>, + /// Auxiliary crates that should be compiled as `#![crate_type = "bin"]`. + pub(crate) bins: Vec<String>, + /// Similar to `builds`, but a list of NAME=somelib.rs of dependencies + /// to build and pass with the `--extern` flag. + pub(crate) crates: Vec<(String, String)>, + /// Similar to `builds`, but also uses the resulting dylib as a + /// `-Zcodegen-backend` when compiling the test file. + pub(crate) codegen_backend: Option<String>, +} + +impl AuxProps { + /// Yields all of the paths (relative to `./auxiliary/`) that have been + /// specified in `aux-*` directives for this test. + pub(crate) fn all_aux_path_strings(&self) -> impl Iterator<Item = &str> { + let Self { builds, bins, crates, codegen_backend } = self; + + iter::empty() + .chain(builds.iter().map(String::as_str)) + .chain(bins.iter().map(String::as_str)) + .chain(crates.iter().map(|(_, path)| path.as_str())) + .chain(codegen_backend.iter().map(String::as_str)) + } +} + +/// If the given test directive line contains an `aux-*` directive, parse it +/// and update [`AuxProps`] accordingly. +pub(super) fn parse_and_update_aux(config: &Config, ln: &str, aux: &mut AuxProps) { + if !ln.starts_with("aux-") { + return; + } + + config.push_name_value_directive(ln, AUX_BUILD, &mut aux.builds, |r| r.trim().to_string()); + config.push_name_value_directive(ln, AUX_BIN, &mut aux.bins, |r| r.trim().to_string()); + config.push_name_value_directive(ln, AUX_CRATE, &mut aux.crates, parse_aux_crate); + if let Some(r) = config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND) { + aux.codegen_backend = Some(r.trim().to_owned()); + } +} + +fn parse_aux_crate(r: String) -> (String, String) { + let mut parts = r.trim().splitn(2, '='); + ( + parts.next().expect("missing aux-crate name (e.g. log=log.rs)").to_string(), + parts.next().expect("missing aux-crate value (e.g. log=log.rs)").to_string(), + ) +} diff --git a/src/tools/compiletest/src/header/cfg.rs b/src/tools/compiletest/src/header/cfg.rs index 948568e63c2..b9314f0abbb 100644 --- a/src/tools/compiletest/src/header/cfg.rs +++ b/src/tools/compiletest/src/header/cfg.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use crate::common::{CompareMode, Config, Debugger, Mode}; +use crate::common::{CompareMode, Config, Debugger}; use crate::header::IgnoreDecision; const EXTRA_ARCHS: &[&str] = &["spirv"]; @@ -166,6 +166,12 @@ pub(super) fn parse_cfg_name_directive<'a>( message: "when the target vendor is Apple" } + condition! { + name: "enzyme", + condition: config.has_enzyme, + message: "when rustc is built with LLVM Enzyme" + } + // Technically the locally built compiler uses the "dev" channel rather than the "nightly" // channel, even though most people don't know or won't care about it. To avoid confusion, we // treat the "dev" channel as the "nightly" channel when processing the directive. @@ -217,13 +223,10 @@ pub(super) fn parse_cfg_name_directive<'a>( } // Coverage tests run the same test file in multiple modes. // If a particular test should not be run in one of the modes, ignore it - // with "ignore-mode-coverage-map" or "ignore-mode-coverage-run". + // with "ignore-coverage-map" or "ignore-coverage-run". condition! { - name: format!("mode-{}", config.mode.to_str()), - allowed_names: ContainsPrefixed { - prefix: "mode-", - inner: Mode::STR_VARIANTS, - }, + name: config.mode.to_str(), + allowed_names: ["coverage-map", "coverage-run"], message: "when the test mode is {name}", } diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs index f5dd722ed37..a744fb61b9c 100644 --- a/src/tools/compiletest/src/header/needs.rs +++ b/src/tools/compiletest/src/header/needs.rs @@ -100,9 +100,9 @@ pub(super) fn handle_needs( ignore_reason: "ignored on targets without unwinding support", }, Need { - name: "needs-profiler-support", - condition: cache.profiler_support, - ignore_reason: "ignored when profiler support is disabled", + name: "needs-profiler-runtime", + condition: config.profiler_runtime, + ignore_reason: "ignored when the profiler runtime is not available", }, Need { name: "needs-force-clang-based-tests", @@ -220,7 +220,6 @@ pub(super) struct CachedNeedsConditions { sanitizer_memtag: bool, sanitizer_shadow_call_stack: bool, sanitizer_safestack: bool, - profiler_support: bool, xray: bool, rust_lld: bool, dlltool: bool, @@ -247,7 +246,6 @@ impl CachedNeedsConditions { sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag), sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack), sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack), - profiler_support: config.profiler_support, xray: config.target_cfg().xray, // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`), diff --git a/src/tools/compiletest/src/header/test-auxillary/known_legacy_directive.rs b/src/tools/compiletest/src/header/test-auxillary/known_legacy_directive.rs deleted file mode 100644 index 108ca432e13..00000000000 --- a/src/tools/compiletest/src/header/test-auxillary/known_legacy_directive.rs +++ /dev/null @@ -1 +0,0 @@ -// ignore-wasm diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index ae661200c6c..c3c9496c4d2 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -1,6 +1,5 @@ use std::io::Read; use std::path::Path; -use std::str::FromStr; use super::iter_header; use crate::common::{Config, Debugger, Mode}; @@ -70,7 +69,7 @@ struct ConfigBuilder { llvm_version: Option<String>, git_hash: bool, system_llvm: bool, - profiler_support: bool, + profiler_runtime: bool, } impl ConfigBuilder { @@ -114,8 +113,8 @@ impl ConfigBuilder { self } - fn profiler_support(&mut self, s: bool) -> &mut Self { - self.profiler_support = s; + fn profiler_runtime(&mut self, is_available: bool) -> &mut Self { + self.profiler_runtime = is_available; self } @@ -163,8 +162,8 @@ impl ConfigBuilder { if self.system_llvm { args.push("--system-llvm".to_owned()); } - if self.profiler_support { - args.push("--profiler-support".to_owned()); + if self.profiler_runtime { + args.push("--profiler-runtime".to_owned()); } args.push("--rustc-path".to_string()); @@ -243,7 +242,8 @@ fn aux_build() { //@ aux-build: b.rs " ) - .aux, + .aux + .builds, vec!["a.rs", "b.rs"], ); } @@ -369,12 +369,12 @@ fn sanitizers() { } #[test] -fn profiler_support() { - let config: Config = cfg().profiler_support(false).build(); - assert!(check_ignore(&config, "//@ needs-profiler-support")); +fn profiler_runtime() { + let config: Config = cfg().profiler_runtime(false).build(); + assert!(check_ignore(&config, "//@ needs-profiler-runtime")); - let config: Config = cfg().profiler_support(true).build(); - assert!(!check_ignore(&config, "//@ needs-profiler-support")); + let config: Config = cfg().profiler_runtime(true).build(); + assert!(!check_ignore(&config, "//@ needs-profiler-runtime")); } #[test] @@ -423,7 +423,7 @@ fn test_extract_version_range() { } #[test] -#[should_panic(expected = "Duplicate revision: `rpass1` in line ` rpass1 rpass1`")] +#[should_panic(expected = "duplicate revision: `rpass1` in line ` rpass1 rpass1`")] fn test_duplicate_revisions() { let config: Config = cfg().build(); parse_rs(&config, "//@ revisions: rpass1 rpass1"); @@ -573,19 +573,15 @@ fn families() { } #[test] -fn ignore_mode() { - for &mode in Mode::STR_VARIANTS { - // Indicate profiler support so that "coverage-run" tests aren't skipped. - let config: Config = cfg().mode(mode).profiler_support(true).build(); - let other = if mode == "coverage-run" { "coverage-map" } else { "coverage-run" }; +fn ignore_coverage() { + // Indicate profiler runtime availability so that "coverage-run" tests aren't skipped. + let config = cfg().mode("coverage-map").profiler_runtime(true).build(); + assert!(check_ignore(&config, "//@ ignore-coverage-map")); + assert!(!check_ignore(&config, "//@ ignore-coverage-run")); - assert_ne!(mode, other); - assert_eq!(config.mode, Mode::from_str(mode).unwrap()); - assert_ne!(config.mode, Mode::from_str(other).unwrap()); - - assert!(check_ignore(&config, &format!("//@ ignore-mode-{mode}"))); - assert!(!check_ignore(&config, &format!("//@ ignore-mode-{other}"))); - } + let config = cfg().mode("coverage-run").profiler_runtime(true).build(); + assert!(!check_ignore(&config, "//@ ignore-coverage-map")); + assert!(check_ignore(&config, "//@ ignore-coverage-run")); } #[test] @@ -622,17 +618,6 @@ fn test_unknown_directive_check() { } #[test] -fn test_known_legacy_directive_check() { - let mut poisoned = false; - run_path( - &mut poisoned, - Path::new("a.rs"), - include_bytes!("./test-auxillary/known_legacy_directive.rs"), - ); - assert!(poisoned); -} - -#[test] fn test_known_directive_check_no_error() { let mut poisoned = false; run_path( diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index a8355ee9590..18000e5602c 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -10,6 +10,7 @@ mod tests; pub mod common; pub mod compute_diff; +mod debuggers; pub mod errors; pub mod header; mod json; @@ -36,8 +37,8 @@ use walkdir::WalkDir; use self::header::{EarlyProps, make_test_description}; use crate::common::{ - Config, Debugger, Mode, PassMode, TestPaths, UI_EXTENSIONS, expected_output_path, - output_base_dir, output_relative_path, + Config, Mode, PassMode, TestPaths, UI_EXTENSIONS, expected_output_path, output_base_dir, + output_relative_path, }; use crate::header::HeadersCache; use crate::util::logv; @@ -53,8 +54,6 @@ pub fn parse_config(args: Vec<String>) -> Config { .reqopt("", "python", "path to python to use for doc tests", "PATH") .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH") .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH") - .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM") - .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind") .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH") .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR") .reqopt("", "src-base", "directory to scan for test files", "PATH") @@ -65,7 +64,7 @@ pub fn parse_config(args: Vec<String>) -> Config { "", "mode", "which sort of compile tests to run", - "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \ + "pretty | debug-info | codegen | rustdoc \ | rustdoc-json | codegen-units | incremental | run-make | ui \ | js-doc-test | mir-opt | assembly | crashes", ) @@ -155,7 +154,7 @@ pub fn parse_config(args: Vec<String>) -> Config { .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged") .optflag("", "only-modified", "only run tests that result been modified") .optflag("", "nocapture", "") - .optflag("", "profiler-support", "is the profiler runtime enabled for this target") + .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target") .optflag("h", "help", "show this message") .reqopt("", "channel", "current Rust channel", "CHANNEL") .optflag( @@ -206,9 +205,11 @@ pub fn parse_config(args: Vec<String>) -> Config { let target = opt_str2(matches.opt_str("target")); let android_cross_path = opt_path(matches, "android-cross-path"); - let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target); - let (gdb, gdb_version) = analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path); - let lldb_version = matches.opt_str("lldb-version").as_deref().and_then(extract_lldb_version); + let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target); + let (gdb, gdb_version) = + debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path); + let lldb_version = + matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version); let color = match matches.opt_str("color").as_deref() { Some("auto") | None => ColorConfig::AutoColor, Some("always") => ColorConfig::AlwaysColor, @@ -269,8 +270,6 @@ pub fn parse_config(args: Vec<String>) -> Config { python: matches.opt_str("python").unwrap(), jsondocck_path: matches.opt_str("jsondocck-path"), jsondoclint_path: matches.opt_str("jsondoclint-path"), - valgrind_path: matches.opt_str("valgrind-path"), - force_valgrind: matches.opt_present("force-valgrind"), run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"), llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from), llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from), @@ -359,7 +358,7 @@ pub fn parse_config(args: Vec<String>) -> Config { nightly_branch: matches.opt_str("nightly-branch").unwrap(), git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(), - profiler_support: matches.opt_present("profiler-support"), + profiler_runtime: matches.opt_present("profiler-runtime"), } } @@ -447,9 +446,9 @@ pub fn run_tests(config: Arc<Config>) { if let Mode::DebugInfo = config.mode { // Debugging emscripten code doesn't make sense today if !config.target.contains("emscripten") { - configs.extend(configure_cdb(&config)); - configs.extend(configure_gdb(&config)); - configs.extend(configure_lldb(&config)); + configs.extend(debuggers::configure_cdb(&config)); + configs.extend(debuggers::configure_gdb(&config)); + configs.extend(debuggers::configure_lldb(&config)); } } else { configs.push(config.clone()); @@ -502,62 +501,6 @@ pub fn run_tests(config: Arc<Config>) { } } -fn configure_cdb(config: &Config) -> Option<Arc<Config>> { - config.cdb.as_ref()?; - - Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() })) -} - -fn configure_gdb(config: &Config) -> Option<Arc<Config>> { - config.gdb_version?; - - if config.matches_env("msvc") { - return None; - } - - if config.remote_test_client.is_some() && !config.target.contains("android") { - println!( - "WARNING: debuginfo tests are not available when \ - testing with remote" - ); - return None; - } - - if config.target.contains("android") { - println!( - "{} debug-info test uses tcp 5039 port.\ - please reserve it", - config.target - ); - - // android debug-info test uses remote debugger so, we test 1 thread - // at once as they're all sharing the same TCP port to communicate - // over. - // - // we should figure out how to lift this restriction! (run them all - // on different ports allocated dynamically). - env::set_var("RUST_TEST_THREADS", "1"); - } - - Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() })) -} - -fn configure_lldb(config: &Config) -> Option<Arc<Config>> { - config.lldb_python_dir.as_ref()?; - - if let Some(350) = config.lldb_version { - println!( - "WARNING: The used version of LLDB (350) has a \ - known issue that breaks debuginfo tests. See \ - issue #32520 for more information. Skipping all \ - LLDB-based tests!", - ); - return None; - } - - Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() })) -} - pub fn test_opts(config: &Config) -> test::TestOpts { if env::var("RUST_TEST_NOCAPTURE").is_ok() { eprintln!( @@ -866,7 +809,8 @@ fn files_related_to_test( related.push(testpaths.file.clone()); } - for aux in &props.aux { + for aux in props.aux.all_aux_path_strings() { + // FIXME(Zalathar): Perform all `auxiliary` path resolution in one place. let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux); related.push(path); } @@ -984,212 +928,6 @@ fn make_test_closure( })) } -/// Returns `true` if the given target is an Android target for the -/// purposes of GDB testing. -fn is_android_gdb_target(target: &str) -> bool { - matches!( - &target[..], - "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" - ) -} - -/// Returns `true` if the given target is a MSVC target for the purposes of CDB testing. -fn is_pc_windows_msvc_target(target: &str) -> bool { - target.ends_with("-pc-windows-msvc") -} - -fn find_cdb(target: &str) -> Option<OsString> { - if !(cfg!(windows) && is_pc_windows_msvc_target(target)) { - return None; - } - - let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?; - let cdb_arch = if cfg!(target_arch = "x86") { - "x86" - } else if cfg!(target_arch = "x86_64") { - "x64" - } else if cfg!(target_arch = "aarch64") { - "arm64" - } else if cfg!(target_arch = "arm") { - "arm" - } else { - return None; // No compatible CDB.exe in the Windows 10 SDK - }; - - let mut path = PathBuf::new(); - path.push(pf86); - path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too? - path.push(cdb_arch); - path.push(r"cdb.exe"); - - if !path.exists() { - return None; - } - - Some(path.into_os_string()) -} - -/// Returns Path to CDB -fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) { - let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target)); - - let mut version = None; - if let Some(cdb) = cdb.as_ref() { - if let Ok(output) = Command::new(cdb).arg("/version").output() { - if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { - version = extract_cdb_version(&first_line); - } - } - } - - (cdb, version) -} - -fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> { - // Example full_version_line: "cdb version 10.0.18362.1" - let version = full_version_line.rsplit(' ').next()?; - let mut components = version.split('.'); - let major: u16 = components.next().unwrap().parse().unwrap(); - let minor: u16 = components.next().unwrap().parse().unwrap(); - let patch: u16 = components.next().unwrap_or("0").parse().unwrap(); - let build: u16 = components.next().unwrap_or("0").parse().unwrap(); - Some([major, minor, patch, build]) -} - -/// Returns (Path to GDB, GDB Version) -fn analyze_gdb( - gdb: Option<String>, - target: &str, - android_cross_path: &PathBuf, -) -> (Option<String>, Option<u32>) { - #[cfg(not(windows))] - const GDB_FALLBACK: &str = "gdb"; - #[cfg(windows)] - const GDB_FALLBACK: &str = "gdb.exe"; - - let fallback_gdb = || { - if is_android_gdb_target(target) { - let mut gdb_path = match android_cross_path.to_str() { - Some(x) => x.to_owned(), - None => panic!("cannot find android cross path"), - }; - gdb_path.push_str("/bin/gdb"); - gdb_path - } else { - GDB_FALLBACK.to_owned() - } - }; - - let gdb = match gdb { - None => fallback_gdb(), - Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb - Some(ref s) => s.to_owned(), - }; - - let mut version_line = None; - if let Ok(output) = Command::new(&gdb).arg("--version").output() { - if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { - version_line = Some(first_line.to_string()); - } - } - - let version = match version_line { - Some(line) => extract_gdb_version(&line), - None => return (None, None), - }; - - (Some(gdb), version) -} - -fn extract_gdb_version(full_version_line: &str) -> Option<u32> { - let full_version_line = full_version_line.trim(); - - // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both - // of the ? sections being optional - - // We will parse up to 3 digits for each component, ignoring the date - - // We skip text in parentheses. This avoids accidentally parsing - // the openSUSE version, which looks like: - // GNU gdb (GDB; openSUSE Leap 15.0) 8.1 - // This particular form is documented in the GNU coding standards: - // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion - - let unbracketed_part = full_version_line.split('[').next().unwrap(); - let mut splits = unbracketed_part.trim_end().rsplit(' '); - let version_string = splits.next().unwrap(); - - let mut splits = version_string.split('.'); - let major = splits.next().unwrap(); - let minor = splits.next().unwrap(); - let patch = splits.next(); - - let major: u32 = major.parse().unwrap(); - let (minor, patch): (u32, u32) = match minor.find(not_a_digit) { - None => { - let minor = minor.parse().unwrap(); - let patch: u32 = match patch { - Some(patch) => match patch.find(not_a_digit) { - None => patch.parse().unwrap(), - Some(idx) if idx > 3 => 0, - Some(idx) => patch[..idx].parse().unwrap(), - }, - None => 0, - }; - (minor, patch) - } - // There is no patch version after minor-date (e.g. "4-2012"). - Some(idx) => { - let minor = minor[..idx].parse().unwrap(); - (minor, 0) - } - }; - - Some(((major * 1000) + minor) * 1000 + patch) -} - -/// Returns LLDB version -fn extract_lldb_version(full_version_line: &str) -> Option<u32> { - // Extract the major LLDB version from the given version string. - // LLDB version strings are different for Apple and non-Apple platforms. - // The Apple variant looks like this: - // - // LLDB-179.5 (older versions) - // lldb-300.2.51 (new versions) - // - // We are only interested in the major version number, so this function - // will return `Some(179)` and `Some(300)` respectively. - // - // Upstream versions look like: - // lldb version 6.0.1 - // - // There doesn't seem to be a way to correlate the Apple version - // with the upstream version, and since the tests were originally - // written against Apple versions, we make a fake Apple version by - // multiplying the first number by 100. This is a hack. - - let full_version_line = full_version_line.trim(); - - if let Some(apple_ver) = - full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-")) - { - if let Some(idx) = apple_ver.find(not_a_digit) { - let version: u32 = apple_ver[..idx].parse().unwrap(); - return Some(version); - } - } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") { - if let Some(idx) = lldb_ver.find(not_a_digit) { - let version: u32 = lldb_ver[..idx].parse().ok()?; - return Some(version * 100); - } - } - None -} - -fn not_a_digit(c: char) -> bool { - !c.is_ascii_digit() -} - fn check_overlapping_tests(found_paths: &HashSet<PathBuf>) { let mut collisions = Vec::new(); for path in found_paths { diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 7b85e6f80b3..b0f87593f95 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -18,15 +18,11 @@ fn main() { let config = Arc::new(parse_config(env::args().collect())); - if config.valgrind_path.is_none() && config.force_valgrind { - panic!("Can't find Valgrind to run Valgrind tests"); - } - if !config.has_tidy && config.mode == Mode::Rustdoc { eprintln!("warning: `tidy` is not installed; diffs will not be generated"); } - if !config.profiler_support && config.mode == Mode::CoverageRun { + if !config.profiler_runtime && config.mode == Mode::CoverageRun { let actioned = if config.bless { "blessed" } else { "checked" }; eprintln!( r#" diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 74d86d2b521..29f9925de16 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -20,9 +20,9 @@ use tracing::*; use crate::common::{ Assembly, Codegen, CodegenUnits, CompareMode, Config, CoverageMap, CoverageRun, Crashes, DebugInfo, Debugger, FailMode, Incremental, JsDocTest, MirOpt, PassMode, Pretty, RunMake, - RunPassValgrind, Rustdoc, RustdocJson, TestPaths, UI_EXTENSIONS, UI_FIXED, UI_RUN_STDERR, - UI_RUN_STDOUT, UI_STDERR, UI_STDOUT, UI_SVG, UI_WINDOWS_SVG, Ui, expected_output_path, - incremental_dir, output_base_dir, output_base_name, output_testname_unique, + Rustdoc, RustdocJson, TestPaths, UI_EXTENSIONS, UI_FIXED, UI_RUN_STDERR, UI_RUN_STDOUT, + UI_STDERR, UI_STDOUT, UI_SVG, UI_WINDOWS_SVG, Ui, expected_output_path, incremental_dir, + output_base_dir, output_base_name, output_testname_unique, }; use crate::compute_diff::{write_diff, write_filtered_diff}; use crate::errors::{self, Error, ErrorKind}; @@ -49,7 +49,6 @@ mod run_make; mod rustdoc; mod rustdoc_json; mod ui; -mod valgrind; // tidy-alphabet-end #[cfg(test)] @@ -253,7 +252,6 @@ impl<'test> TestCx<'test> { self.fatal("cannot use should-ice in a test that is not cfail"); } match self.config.mode { - RunPassValgrind => self.run_valgrind_test(), Pretty => self.run_pretty_test(), DebugInfo => self.run_debuginfo_test(), Codegen => self.run_codegen_test(), @@ -320,10 +318,29 @@ impl<'test> TestCx<'test> { } } - fn check_if_test_should_compile(&self, proc_res: &ProcRes, pm: Option<PassMode>) { - if self.should_compile_successfully(pm) { + fn check_if_test_should_compile( + &self, + fail_mode: Option<FailMode>, + pass_mode: Option<PassMode>, + proc_res: &ProcRes, + ) { + if self.should_compile_successfully(pass_mode) { if !proc_res.status.success() { - self.fatal_proc_rec("test compilation failed although it shouldn't!", proc_res); + match (fail_mode, pass_mode) { + (Some(FailMode::Build), Some(PassMode::Check)) => { + // A `build-fail` test needs to `check-pass`. + self.fatal_proc_rec( + "`build-fail` test is required to pass check build, but check build failed", + proc_res, + ); + } + _ => { + self.fatal_proc_rec( + "test compilation failed although it shouldn't!", + proc_res, + ); + } + } } } else { if proc_res.status.success() { @@ -843,13 +860,13 @@ impl<'test> TestCx<'test> { /// Auxiliaries, no matter how deep, have the same root_out_dir and root_testpaths. fn document(&self, root_out_dir: &Path, root_testpaths: &TestPaths) -> ProcRes { if self.props.build_aux_docs { - for rel_ab in &self.props.aux_builds { + for rel_ab in &self.props.aux.builds { let aux_testpaths = self.compute_aux_test_paths(root_testpaths, rel_ab); - let aux_props = + let props_for_aux = self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config); let aux_cx = TestCx { config: self.config, - props: &aux_props, + props: &props_for_aux, testpaths: &aux_testpaths, revision: self.revision, }; @@ -1061,11 +1078,11 @@ impl<'test> TestCx<'test> { fn aux_output_dir(&self) -> PathBuf { let aux_dir = self.aux_output_dir_name(); - if !self.props.aux_builds.is_empty() { + if !self.props.aux.builds.is_empty() { remove_and_create_dir_all(&aux_dir); } - if !self.props.aux_bins.is_empty() { + if !self.props.aux.bins.is_empty() { let aux_bin_dir = self.aux_bin_output_dir_name(); remove_and_create_dir_all(&aux_dir); remove_and_create_dir_all(&aux_bin_dir); @@ -1075,15 +1092,15 @@ impl<'test> TestCx<'test> { } fn build_all_auxiliary(&self, of: &TestPaths, aux_dir: &Path, rustc: &mut Command) { - for rel_ab in &self.props.aux_builds { + for rel_ab in &self.props.aux.builds { self.build_auxiliary(of, rel_ab, &aux_dir, false /* is_bin */); } - for rel_ab in &self.props.aux_bins { + for rel_ab in &self.props.aux.bins { self.build_auxiliary(of, rel_ab, &aux_dir, true /* is_bin */); } - for (aux_name, aux_path) in &self.props.aux_crates { + for (aux_name, aux_path) in &self.props.aux.crates { let aux_type = self.build_auxiliary(of, &aux_path, &aux_dir, false /* is_bin */); let lib_name = get_lib_name(&aux_path.trim_end_matches(".rs").replace('-', "_"), aux_type); @@ -1099,7 +1116,7 @@ impl<'test> TestCx<'test> { // Build any `//@ aux-codegen-backend`, and pass the resulting library // to `-Zcodegen-backend` when compiling the test file. - if let Some(aux_file) = &self.props.aux_codegen_backend { + if let Some(aux_file) = &self.props.aux.codegen_backend { let aux_type = self.build_auxiliary(of, aux_file, aux_dir, false); if let Some(lib_name) = get_lib_name(aux_file.trim_end_matches(".rs"), aux_type) { let lib_path = aux_dir.join(&lib_name); @@ -1500,8 +1517,7 @@ impl<'test> TestCx<'test> { Crashes => { set_mir_dump_dir(&mut rustc); } - RunPassValgrind | Pretty | DebugInfo | Rustdoc | RustdocJson | RunMake - | CodegenUnits | JsDocTest => { + Pretty | DebugInfo | Rustdoc | RustdocJson | RunMake | CodegenUnits | JsDocTest => { // do not use JSON output } } @@ -1783,58 +1799,14 @@ impl<'test> TestCx<'test> { proc_res.fatal(None, || on_failure(*self)); } - fn get_output_file(&self, extension: &str) -> TargetLocation { - let thin_lto = self.props.compile_flags.iter().any(|s| s.ends_with("lto=thin")); - if thin_lto { - TargetLocation::ThisDirectory(self.output_base_dir()) - } else { - // This works with both `--emit asm` (as default output name for the assembly) - // and `ptx-linker` because the latter can write output at requested location. - let output_path = self.output_base_name().with_extension(extension); - - TargetLocation::ThisFile(output_path.clone()) - } - } - - fn get_filecheck_file(&self, extension: &str) -> PathBuf { - let thin_lto = self.props.compile_flags.iter().any(|s| s.ends_with("lto=thin")); - if thin_lto { - let name = self.testpaths.file.file_stem().unwrap().to_str().unwrap(); - let canonical_name = name.replace('-', "_"); - let mut output_file = None; - for entry in self.output_base_dir().read_dir().unwrap() { - if let Ok(entry) = entry { - let entry_path = entry.path(); - let entry_file = entry_path.file_name().unwrap().to_str().unwrap(); - if entry_file.starts_with(&format!("{}.{}", name, canonical_name)) - && entry_file.ends_with(extension) - { - assert!( - output_file.is_none(), - "thinlto doesn't support multiple cgu tests" - ); - output_file = Some(entry_file.to_string()); - } - } - } - if let Some(output_file) = output_file { - self.output_base_dir().join(output_file) - } else { - self.output_base_name().with_extension(extension) - } - } else { - self.output_base_name().with_extension(extension) - } - } - // codegen tests (using FileCheck) fn compile_test_and_save_ir(&self) -> (ProcRes, PathBuf) { - let output_file = self.get_output_file("ll"); + let output_path = self.output_base_name().with_extension("ll"); let input_file = &self.testpaths.file; let rustc = self.make_compile_args( input_file, - output_file, + TargetLocation::ThisFile(output_path.clone()), Emit::LlvmIr, AllowUnused::No, LinkToAux::Yes, @@ -1842,35 +1814,27 @@ impl<'test> TestCx<'test> { ); let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths); - let output_path = self.get_filecheck_file("ll"); (proc_res, output_path) } fn compile_test_and_save_assembly(&self) -> (ProcRes, PathBuf) { - let output_file = self.get_output_file("s"); + // This works with both `--emit asm` (as default output name for the assembly) + // and `ptx-linker` because the latter can write output at requested location. + let output_path = self.output_base_name().with_extension("s"); let input_file = &self.testpaths.file; - let mut emit = Emit::None; - match self.props.assembly_output.as_ref().map(AsRef::as_ref) { - Some("emit-asm") => { - emit = Emit::Asm; - } - - Some("bpf-linker") => { - emit = Emit::LinkArgsAsm; - } - - Some("ptx-linker") => { - // No extra flags needed. - } - - Some(header) => self.fatal(&format!("unknown 'assembly-output' header: {header}")), - None => self.fatal("missing 'assembly-output' header"), - } + // Use the `//@ assembly-output:` directive to determine how to emit assembly. + let emit = match self.props.assembly_output.as_deref() { + Some("emit-asm") => Emit::Asm, + Some("bpf-linker") => Emit::LinkArgsAsm, + Some("ptx-linker") => Emit::None, // No extra flags needed. + Some(other) => self.fatal(&format!("unknown 'assembly-output' directive: {other}")), + None => self.fatal("missing 'assembly-output' directive"), + }; let rustc = self.make_compile_args( input_file, - output_file, + TargetLocation::ThisFile(output_path.clone()), emit, AllowUnused::No, LinkToAux::Yes, @@ -1878,7 +1842,6 @@ impl<'test> TestCx<'test> { ); let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths); - let output_path = self.get_filecheck_file("s"); (proc_res, output_path) } @@ -2109,6 +2072,10 @@ impl<'test> TestCx<'test> { .collect() } + /// This method is used for `//@ check-test-line-numbers-match`. + /// + /// It checks that doctests line in the displayed doctest "name" matches where they are + /// defined in source code. fn check_rustdoc_test_option(&self, res: ProcRes) { let mut other_files = Vec::new(); let mut files: HashMap<String, Vec<usize>> = HashMap::new(); @@ -2651,33 +2618,6 @@ impl<'test> TestCx<'test> { } } - // FIXME(jieyouxu): `run_rpass_test` is hoisted out here and not in incremental because - // apparently valgrind test falls back to `run_rpass_test` if valgrind isn't available, which - // seems highly questionable to me. - fn run_rpass_test(&self) { - let emit_metadata = self.should_emit_metadata(self.pass_mode()); - let should_run = self.run_if_enabled(); - let proc_res = self.compile_test(should_run, emit_metadata); - - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - // FIXME(#41968): Move this check to tidy? - if !errors::load_errors(&self.testpaths.file, self.revision).is_empty() { - self.fatal("run-pass tests with expected warnings should be moved to ui/"); - } - - if let WillExecute::Disabled = should_run { - return; - } - - let proc_res = self.exec_compiled_test(); - if !proc_res.status.success() { - self.fatal_proc_rec("test run failed!", &proc_res); - } - } - fn aggressive_rm_rf(&self, path: &Path) -> io::Result<()> { for e in path.read_dir()? { let entry = e?; diff --git a/src/tools/compiletest/src/runtest/debuginfo.rs b/src/tools/compiletest/src/runtest/debuginfo.rs index 36127414ab1..bd0845b4524 100644 --- a/src/tools/compiletest/src/runtest/debuginfo.rs +++ b/src/tools/compiletest/src/runtest/debuginfo.rs @@ -9,8 +9,8 @@ use tracing::debug; use super::debugger::DebuggerCommands; use super::{Debugger, Emit, ProcRes, TestCx, Truncated, WillExecute}; use crate::common::Config; +use crate::debuggers::{extract_gdb_version, is_android_gdb_target}; use crate::util::logv; -use crate::{extract_gdb_version, is_android_gdb_target}; impl TestCx<'_> { pub(super) fn run_debuginfo_test(&self) { diff --git a/src/tools/compiletest/src/runtest/incremental.rs b/src/tools/compiletest/src/runtest/incremental.rs index 81b006292e4..591aff0defe 100644 --- a/src/tools/compiletest/src/runtest/incremental.rs +++ b/src/tools/compiletest/src/runtest/incremental.rs @@ -1,10 +1,6 @@ -use super::{TestCx, WillExecute}; +use super::{FailMode, TestCx, WillExecute}; use crate::errors; -// FIXME(jieyouxu): `run_rpass_test` got hoisted out of this because apparently valgrind falls back -// to `run_rpass_test` if valgrind isn't available, which is questionable, but keeping it for -// refactoring changes to preserve current behavior. - impl TestCx<'_> { pub(super) fn run_incremental_test(&self) { // Basic plan for a test incremental/foo/bar.rs: @@ -73,10 +69,34 @@ impl TestCx<'_> { } } + fn run_rpass_test(&self) { + let emit_metadata = self.should_emit_metadata(self.pass_mode()); + let should_run = self.run_if_enabled(); + let proc_res = self.compile_test(should_run, emit_metadata); + + if !proc_res.status.success() { + self.fatal_proc_rec("compilation failed!", &proc_res); + } + + // FIXME(#41968): Move this check to tidy? + if !errors::load_errors(&self.testpaths.file, self.revision).is_empty() { + self.fatal("run-pass tests with expected warnings should be moved to ui/"); + } + + if let WillExecute::Disabled = should_run { + return; + } + + let proc_res = self.exec_compiled_test(); + if !proc_res.status.success() { + self.fatal_proc_rec("test run failed!", &proc_res); + } + } + fn run_cfail_test(&self) { let pm = self.pass_mode(); let proc_res = self.compile_test(WillExecute::No, self.should_emit_metadata(pm)); - self.check_if_test_should_compile(&proc_res, pm); + self.check_if_test_should_compile(Some(FailMode::Build), pm, &proc_res); self.check_no_compiler_crash(&proc_res, self.props.should_ice); let output_to_check = self.get_output(&proc_res); @@ -115,12 +135,6 @@ impl TestCx<'_> { let proc_res = self.exec_compiled_test(); - // The value our Makefile configures valgrind to return on failure - const VALGRIND_ERR: i32 = 100; - if proc_res.status.code() == Some(VALGRIND_ERR) { - self.fatal_proc_rec("run-fail test isn't valgrind-clean!", &proc_res); - } - let output_to_check = self.get_output(&proc_res); self.check_correct_failure_status(&proc_res); self.check_all_error_patterns(&output_to_check, &proc_res, pm); diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs index bd8ef952a86..bb747c68029 100644 --- a/src/tools/compiletest/src/runtest/ui.rs +++ b/src/tools/compiletest/src/runtest/ui.rs @@ -18,14 +18,14 @@ impl TestCx<'_> { let pm = Some(PassMode::Check); let proc_res = self.compile_test_general(WillExecute::No, Emit::Metadata, pm, Vec::new()); - self.check_if_test_should_compile(&proc_res, pm); + self.check_if_test_should_compile(self.props.fail_mode, pm, &proc_res); } let pm = self.pass_mode(); let should_run = self.should_run(pm); let emit_metadata = self.should_emit_metadata(pm); let proc_res = self.compile_test(should_run, emit_metadata); - self.check_if_test_should_compile(&proc_res, pm); + self.check_if_test_should_compile(self.props.fail_mode, pm, &proc_res); if matches!(proc_res.truncated, Truncated::Yes) && !self.props.dont_check_compiler_stdout && !self.props.dont_check_compiler_stderr diff --git a/src/tools/compiletest/src/runtest/valgrind.rs b/src/tools/compiletest/src/runtest/valgrind.rs deleted file mode 100644 index 8d72c4be9ff..00000000000 --- a/src/tools/compiletest/src/runtest/valgrind.rs +++ /dev/null @@ -1,34 +0,0 @@ -use super::{Emit, TestCx, WillExecute}; - -impl TestCx<'_> { - pub(super) fn run_valgrind_test(&self) { - assert!(self.revision.is_none(), "revisions not relevant here"); - - // FIXME(jieyouxu): does this really make any sense? If a valgrind test isn't testing - // valgrind, what is it even testing? - if self.config.valgrind_path.is_none() { - assert!(!self.config.force_valgrind); - return self.run_rpass_test(); - } - - let should_run = self.run_if_enabled(); - let mut proc_res = self.compile_test(should_run, Emit::None); - - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - if let WillExecute::Disabled = should_run { - return; - } - - let mut new_config = self.config.clone(); - new_config.runner = new_config.valgrind_path.clone(); - let new_cx = TestCx { config: &new_config, ..*self }; - proc_res = new_cx.exec_compiled_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("test run failed!", &proc_res); - } - } -} diff --git a/src/tools/compiletest/src/tests.rs b/src/tools/compiletest/src/tests.rs index 7c2e7b0f023..680579c59ae 100644 --- a/src/tools/compiletest/src/tests.rs +++ b/src/tools/compiletest/src/tests.rs @@ -1,5 +1,8 @@ -use super::header::extract_llvm_version; -use super::*; +use std::ffi::OsString; + +use crate::debuggers::{extract_gdb_version, extract_lldb_version}; +use crate::header::extract_llvm_version; +use crate::is_test; #[test] fn test_extract_gdb_version() { diff --git a/src/tools/coverage-dump/src/covfun.rs b/src/tools/coverage-dump/src/covfun.rs index c779dd0583c..33fac3edccd 100644 --- a/src/tools/coverage-dump/src/covfun.rs +++ b/src/tools/coverage-dump/src/covfun.rs @@ -56,6 +56,7 @@ pub(crate) fn dump_covfun_mappings( expression_resolver.push_operands(lhs, rhs); } + let mut max_counter = None; for i in 0..num_files { let num_mappings = parser.read_uleb128_u32()?; println!("Number of file {i} mappings: {num_mappings}"); @@ -63,6 +64,11 @@ pub(crate) fn dump_covfun_mappings( for _ in 0..num_mappings { let (kind, region) = parser.read_mapping_kind_and_region()?; println!("- {kind:?} at {region:?}"); + kind.for_each_term(|term| { + if let CovTerm::Counter(n) = term { + max_counter = max_counter.max(Some(n)); + } + }); match kind { // Also print expression mappings in resolved form. @@ -83,6 +89,16 @@ pub(crate) fn dump_covfun_mappings( } parser.ensure_empty()?; + + // Printing the highest counter ID seen in the functions mappings makes + // it easier to determine whether a change to coverage instrumentation + // has increased or decreased the number of physical counters needed. + // (It's possible for the generated code to have more counters that + // aren't used by any mappings, but that should hopefully be rare.) + println!("Highest counter ID seen: {}", match max_counter { + Some(id) => format!("c{id}"), + None => "(none)".to_owned(), + }); println!(); } Ok(()) @@ -271,6 +287,32 @@ enum MappingKind { }, } +impl MappingKind { + fn for_each_term(&self, mut callback: impl FnMut(CovTerm)) { + match *self { + Self::Code(term) => callback(term), + Self::Gap(term) => callback(term), + Self::Expansion(_id) => {} + Self::Skip => {} + Self::Branch { r#true, r#false } => { + callback(r#true); + callback(r#false); + } + Self::MCDCBranch { + r#true, + r#false, + condition_id: _, + true_next_id: _, + false_next_id: _, + } => { + callback(r#true); + callback(r#false); + } + Self::MCDCDecision { bitmap_idx: _, conditions_num: _ } => {} + } + } +} + struct MappingRegion { /// Offset of this region's start line, relative to the *start line* of /// the *previous mapping* (or 0). Line numbers are 1-based. diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs index b04919bdd3e..f7c752033c5 100644 --- a/src/tools/jsondoclint/src/validator.rs +++ b/src/tools/jsondoclint/src/validator.rs @@ -418,7 +418,7 @@ impl<'a> Validator<'a> { } else if !self.missing_ids.contains(id) { self.missing_ids.insert(id); - let sels = json_find::find_selector(&self.krate_json, &Value::String(id.0.clone())); + let sels = json_find::find_selector(&self.krate_json, &Value::Number(id.0.into())); assert_ne!(sels.len(), 0); self.fail(id, ErrorKind::NotFound(sels)) diff --git a/src/tools/jsondoclint/src/validator/tests.rs b/src/tools/jsondoclint/src/validator/tests.rs index d15aa7db315..e842e1318db 100644 --- a/src/tools/jsondoclint/src/validator/tests.rs +++ b/src/tools/jsondoclint/src/validator/tests.rs @@ -15,24 +15,20 @@ fn check(krate: &Crate, errs: &[Error]) { assert_eq!(errs, &validator.errs[..]); } -fn id(s: &str) -> Id { - Id(s.to_owned()) -} - #[test] fn errors_on_missing_links() { let k = Crate { - root: id("0"), + root: Id(0), crate_version: None, includes_private: false, - index: FxHashMap::from_iter([(id("0"), Item { + index: FxHashMap::from_iter([(Id(0), Item { name: Some("root".to_owned()), - id: id(""), + id: Id(0), crate_id: 0, span: None, visibility: Visibility::Public, docs: None, - links: FxHashMap::from_iter([("Not Found".to_owned(), id("1"))]), + links: FxHashMap::from_iter([("Not Found".to_owned(), Id(1))]), attrs: vec![], deprecation: None, inner: ItemEnum::Module(Module { is_crate: true, items: vec![], is_stripped: false }), @@ -49,7 +45,7 @@ fn errors_on_missing_links() { SelectorPart::Field("links".to_owned()), SelectorPart::Field("Not Found".to_owned()), ]]), - id: id("1"), + id: Id(1), }]); } @@ -58,28 +54,28 @@ fn errors_on_missing_links() { #[test] fn errors_on_local_in_paths_and_not_index() { let krate = Crate { - root: id("0:0:1572"), + root: Id(0), crate_version: None, includes_private: false, index: FxHashMap::from_iter([ - (id("0:0:1572"), Item { - id: id("0:0:1572"), + (Id(0), Item { + id: Id(0), crate_id: 0, name: Some("microcore".to_owned()), span: None, visibility: Visibility::Public, docs: None, - links: FxHashMap::from_iter([(("prim@i32".to_owned(), id("0:1:1571")))]), + links: FxHashMap::from_iter([(("prim@i32".to_owned(), Id(2)))]), attrs: Vec::new(), deprecation: None, inner: ItemEnum::Module(Module { is_crate: true, - items: vec![id("0:1:717")], + items: vec![Id(1)], is_stripped: false, }), }), - (id("0:1:717"), Item { - id: id("0:1:717"), + (Id(1), Item { + id: Id(1), crate_id: 0, name: Some("i32".to_owned()), span: None, @@ -91,7 +87,7 @@ fn errors_on_local_in_paths_and_not_index() { inner: ItemEnum::Primitive(Primitive { name: "i32".to_owned(), impls: vec![] }), }), ]), - paths: FxHashMap::from_iter([(id("0:1:1571"), ItemSummary { + paths: FxHashMap::from_iter([(Id(2), ItemSummary { crate_id: 0, path: vec!["microcore".to_owned(), "i32".to_owned()], kind: ItemKind::Primitive, @@ -101,7 +97,7 @@ fn errors_on_local_in_paths_and_not_index() { }; check(&krate, &[Error { - id: id("0:1:1571"), + id: Id(2), kind: ErrorKind::Custom("Id for local item in `paths` but not in `index`".to_owned()), }]); } @@ -110,11 +106,11 @@ fn errors_on_local_in_paths_and_not_index() { #[should_panic = "LOCAL_CRATE_ID is wrong"] fn checks_local_crate_id_is_correct() { let krate = Crate { - root: id("root"), + root: Id(0), crate_version: None, includes_private: false, - index: FxHashMap::from_iter([(id("root"), Item { - id: id("root"), + index: FxHashMap::from_iter([(Id(0), Item { + id: Id(0), crate_id: LOCAL_CRATE_ID.wrapping_add(1), name: Some("irrelavent".to_owned()), span: None, diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 5b1bad28c07..475139a3b51 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -473,14 +473,14 @@ pub fn report_leaks<'tcx>( leaks: Vec<(AllocId, MemoryKind, Allocation<Provenance, AllocExtra<'tcx>, MiriAllocBytes>)>, ) { let mut any_pruned = false; - for (id, kind, mut alloc) in leaks { + for (id, kind, alloc) in leaks { let mut title = format!( "memory leaked: {id:?} ({}, size: {:?}, align: {:?})", kind, alloc.size().bytes(), alloc.align.bytes() ); - let Some(backtrace) = alloc.extra.backtrace.take() else { + let Some(backtrace) = alloc.extra.backtrace else { ecx.tcx.dcx().err(title); continue; }; diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 75dce211dd4..9f93f151668 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -473,7 +473,7 @@ pub fn eval_entry<'tcx>( } // Check for memory leaks. info!("Additional static roots: {:?}", ecx.machine.static_roots); - let leaks = ecx.find_leaked_allocations(&ecx.machine.static_roots); + let leaks = ecx.take_leaked_allocations(|ecx| &ecx.machine.static_roots); if !leaks.is_empty() { report_leaks(&ecx, leaks); tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check"); diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 665dd7c441a..9f772cfa982 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -295,6 +295,37 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } + "fmuladdf32" => { + let [a, b, c] = check_arg_count(args)?; + let a = this.read_scalar(a)?.to_f32()?; + let b = this.read_scalar(b)?.to_f32()?; + let c = this.read_scalar(c)?.to_f32()?; + let fuse: bool = this.machine.rng.get_mut().gen(); + let res = if fuse { + // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11 + a.to_host().mul_add(b.to_host(), c.to_host()).to_soft() + } else { + ((a * b).value + c).value + }; + let res = this.adjust_nan(res, &[a, b, c]); + this.write_scalar(res, dest)?; + } + "fmuladdf64" => { + let [a, b, c] = check_arg_count(args)?; + let a = this.read_scalar(a)?.to_f64()?; + let b = this.read_scalar(b)?.to_f64()?; + let c = this.read_scalar(c)?.to_f64()?; + let fuse: bool = this.machine.rng.get_mut().gen(); + let res = if fuse { + // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11 + a.to_host().mul_add(b.to_host(), c.to_host()).to_soft() + } else { + ((a * b).value + c).value + }; + let res = this.adjust_nan(res, &[a, b, c]); + this.write_scalar(res, dest)?; + } + "powf32" => { let [f1, f2] = check_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f32()?; diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 330147c8f1c..f089d1e1bcc 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -1,6 +1,5 @@ #![feature(rustc_private)] #![feature(cell_update)] -#![feature(const_option)] #![feature(float_gamma)] #![feature(map_try_insert)] #![feature(never_type)] diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 0d28a4ed3e4..af0ca00a0c0 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -321,7 +321,7 @@ impl ProvenanceExtra { } /// Extra per-allocation data -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct AllocExtra<'tcx> { /// Global state of the borrow tracker, if enabled. pub borrow_tracker: Option<borrow_tracker::AllocState>, @@ -338,6 +338,14 @@ pub struct AllocExtra<'tcx> { pub backtrace: Option<Vec<FrameInfo<'tcx>>>, } +// We need a `Clone` impl because the machine passes `Allocation` through `Cow`... +// but that should never end up actually cloning our `AllocExtra`. +impl<'tcx> Clone for AllocExtra<'tcx> { + fn clone(&self) -> Self { + panic!("our allocations should never be cloned"); + } +} + impl VisitProvenance for AllocExtra<'_> { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { let AllocExtra { borrow_tracker, data_race, weak_memory, backtrace: _ } = self; diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 6ab18a5345e..853d3e80517 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -30,6 +30,7 @@ fn main() { libm(); test_fast(); test_algebraic(); + test_fmuladd(); } trait Float: Copy + PartialEq + Debug { @@ -1041,3 +1042,20 @@ fn test_algebraic() { test_operations_f32(11., 2.); test_operations_f32(10., 15.); } + +fn test_fmuladd() { + use std::intrinsics::{fmuladdf32, fmuladdf64}; + + #[inline(never)] + pub fn test_operations_f32(a: f32, b: f32, c: f32) { + assert_approx_eq!(unsafe { fmuladdf32(a, b, c) }, a * b + c); + } + + #[inline(never)] + pub fn test_operations_f64(a: f64, b: f64, c: f64) { + assert_approx_eq!(unsafe { fmuladdf64(a, b, c) }, a * b + c); + } + + test_operations_f32(0.1, 0.2, 0.3); + test_operations_f64(1.1, 1.2, 1.3); +} diff --git a/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs b/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs new file mode 100644 index 00000000000..b46cf1ddf65 --- /dev/null +++ b/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs @@ -0,0 +1,44 @@ +#![feature(core_intrinsics)] +use std::intrinsics::{fmuladdf32, fmuladdf64}; + +fn main() { + let mut saw_zero = false; + let mut saw_nonzero = false; + for _ in 0..50 { + let a = std::hint::black_box(0.1_f64); + let b = std::hint::black_box(0.2); + let c = std::hint::black_box(-a * b); + // It is unspecified whether the following operation is fused or not. The + // following evaluates to 0.0 if unfused, and nonzero (-1.66e-18) if fused. + let x = unsafe { fmuladdf64(a, b, c) }; + if x == 0.0 { + saw_zero = true; + } else { + saw_nonzero = true; + } + } + assert!( + saw_zero && saw_nonzero, + "`fmuladdf64` failed to be evaluated as both fused and unfused" + ); + + let mut saw_zero = false; + let mut saw_nonzero = false; + for _ in 0..50 { + let a = std::hint::black_box(0.1_f32); + let b = std::hint::black_box(0.2); + let c = std::hint::black_box(-a * b); + // It is unspecified whether the following operation is fused or not. The + // following evaluates to 0.0 if unfused, and nonzero (-8.1956386e-10) if fused. + let x = unsafe { fmuladdf32(a, b, c) }; + if x == 0.0 { + saw_zero = true; + } else { + saw_nonzero = true; + } + } + assert!( + saw_zero && saw_nonzero, + "`fmuladdf32` failed to be evaluated as both fused and unfused" + ); +} diff --git a/src/tools/miri/tests/pass/underscore_pattern.rs b/src/tools/miri/tests/pass/underscore_pattern.rs index f0afe558954..f59bb9f5c82 100644 --- a/src/tools/miri/tests/pass/underscore_pattern.rs +++ b/src/tools/miri/tests/pass/underscore_pattern.rs @@ -1,5 +1,7 @@ // Various tests ensuring that underscore patterns really just construct the place, but don't check its contents. #![feature(strict_provenance)] +#![feature(never_type)] + use std::ptr; fn main() { @@ -9,6 +11,7 @@ fn main() { invalid_let(); dangling_let_type_annotation(); invalid_let_type_annotation(); + never(); } fn dangling_match() { @@ -34,6 +37,13 @@ fn invalid_match() { _ => {} } } + + unsafe { + let x: Uninit<!> = Uninit { uninit: () }; + match x.value { + _ => {} + } + } } fn dangling_let() { @@ -41,6 +51,11 @@ fn dangling_let() { let ptr = ptr::without_provenance::<bool>(0x40); let _ = *ptr; } + + unsafe { + let ptr = ptr::without_provenance::<!>(0x40); + let _ = *ptr; + } } fn invalid_let() { @@ -49,6 +64,12 @@ fn invalid_let() { let ptr = ptr::addr_of!(val).cast::<bool>(); let _ = *ptr; } + + unsafe { + let val = 3u8; + let ptr = ptr::addr_of!(val).cast::<!>(); + let _ = *ptr; + } } // Adding a type annotation used to change how MIR is generated, make sure we cover both cases. @@ -57,6 +78,11 @@ fn dangling_let_type_annotation() { let ptr = ptr::without_provenance::<bool>(0x40); let _: bool = *ptr; } + + unsafe { + let ptr = ptr::without_provenance::<!>(0x40); + let _: ! = *ptr; + } } fn invalid_let_type_annotation() { @@ -65,7 +91,28 @@ fn invalid_let_type_annotation() { let ptr = ptr::addr_of!(val).cast::<bool>(); let _: bool = *ptr; } + + unsafe { + let val = 3u8; + let ptr = ptr::addr_of!(val).cast::<!>(); + let _: ! = *ptr; + } } -// FIXME: we should also test `!`, not just `bool` -- but that s currently buggy: -// https://github.com/rust-lang/rust/issues/117288 +// Regression test from <https://github.com/rust-lang/rust/issues/117288>. +fn never() { + unsafe { + let x = 3u8; + let x: *const ! = &x as *const u8 as *const _; + let _: ! = *x; + } + + // Without a type annotation, make sure we don't implicitly coerce `!` to `()` + // when we do the noop `*x` (as that would require a `!` *value*, creating + // which is UB). + unsafe { + let x = 3u8; + let x: *const ! = &x as *const u8 as *const _; + let _ = *x; + } +} diff --git a/src/tools/opt-dist/src/tests.rs b/src/tools/opt-dist/src/tests.rs index 82c393d34a6..e401554640c 100644 --- a/src/tools/opt-dist/src/tests.rs +++ b/src/tools/opt-dist/src/tests.rs @@ -96,7 +96,6 @@ llvm-config = "{llvm_config}" "tests/incremental", "tests/mir-opt", "tests/pretty", - "tests/run-pass-valgrind", "tests/ui", "tests/crashes", ]; diff --git a/src/tools/run-make-support/src/diff/tests.rs b/src/tools/run-make-support/src/diff/tests.rs index 286548bef61..6096560ca52 100644 --- a/src/tools/run-make-support/src/diff/tests.rs +++ b/src/tools/run-make-support/src/diff/tests.rs @@ -1,28 +1,23 @@ -#[cfg(test)] -mod tests { - use crate::*; - - #[test] - fn test_diff() { - let expected = "foo\nbar\nbaz\n"; - let actual = "foo\nbar\nbaz\n"; +use crate::diff; + +#[test] +fn test_diff() { + let expected = "foo\nbar\nbaz\n"; + let actual = "foo\nbar\nbaz\n"; + diff().expected_text("EXPECTED_TEXT", expected).actual_text("ACTUAL_TEXT", actual).run(); +} + +#[test] +fn test_should_panic() { + let expected = "foo\nbar\nbaz\n"; + let actual = "foo\nbaz\nbar\n"; + + let output = std::panic::catch_unwind(|| { diff().expected_text("EXPECTED_TEXT", expected).actual_text("ACTUAL_TEXT", actual).run(); - } - - #[test] - fn test_should_panic() { - let expected = "foo\nbar\nbaz\n"; - let actual = "foo\nbaz\nbar\n"; - - let output = std::panic::catch_unwind(|| { - diff() - .expected_text("EXPECTED_TEXT", expected) - .actual_text("ACTUAL_TEXT", actual) - .run(); - }) - .unwrap_err(); - - let expected_output = "\ + }) + .unwrap_err(); + + let expected_output = "\ test failed: `EXPECTED_TEXT` is different from `ACTUAL_TEXT` --- EXPECTED_TEXT @@ -34,28 +29,27 @@ test failed: `EXPECTED_TEXT` is different from `ACTUAL_TEXT` -baz "; - assert_eq!(output.downcast_ref::<String>().unwrap(), expected_output); - } + assert_eq!(output.downcast_ref::<String>().unwrap(), expected_output); +} - #[test] - fn test_normalize() { - let expected = " +#[test] +fn test_normalize() { + let expected = " running 2 tests .. test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME "; - let actual = " + let actual = " running 2 tests .. test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s "; - diff() - .expected_text("EXPECTED_TEXT", expected) - .actual_text("ACTUAL_TEXT", actual) - .normalize(r#"finished in \d+\.\d+s"#, "finished in $$TIME") - .run(); - } + diff() + .expected_text("EXPECTED_TEXT", expected) + .actual_text("ACTUAL_TEXT", actual) + .normalize(r#"finished in \d+\.\d+s"#, "finished in $$TIME") + .run(); } diff --git a/src/tools/run-make-support/src/macros.rs b/src/tools/run-make-support/src/macros.rs index f7fe4f54223..cc3d1281d0a 100644 --- a/src/tools/run-make-support/src/macros.rs +++ b/src/tools/run-make-support/src/macros.rs @@ -70,6 +70,30 @@ macro_rules! impl_common_helpers { self } + /// Configuration for the child process’s standard input (stdin) handle. + /// + /// See [`std::process::Command::stdin`]. + pub fn stdin<T: Into<::std::process::Stdio>>(&mut self, cfg: T) -> &mut Self { + self.cmd.stdin(cfg); + self + } + + /// Configuration for the child process’s standard output (stdout) handle. + /// + /// See [`std::process::Command::stdout`]. + pub fn stdout<T: Into<::std::process::Stdio>>(&mut self, cfg: T) -> &mut Self { + self.cmd.stdout(cfg); + self + } + + /// Configuration for the child process’s standard error (stderr) handle. + /// + /// See [`std::process::Command::stderr`]. + pub fn stderr<T: Into<::std::process::Stdio>>(&mut self, cfg: T) -> &mut Self { + self.cmd.stderr(cfg); + self + } + /// Inspect what the underlying [`Command`] is up to the /// current construction. pub fn inspect<I>(&mut self, inspector: I) -> &mut Self diff --git a/src/tools/rust-analyzer/.github/workflows/release.yaml b/src/tools/rust-analyzer/.github/workflows/release.yaml index e11d6e15d10..39ac652de0f 100644 --- a/src/tools/rust-analyzer/.github/workflows/release.yaml +++ b/src/tools/rust-analyzer/.github/workflows/release.yaml @@ -16,7 +16,7 @@ env: RUSTFLAGS: "-D warnings -W unreachable-pub" RUSTUP_MAX_RETRIES: 10 FETCH_DEPTH: 0 # pull in the tags for the version string - MACOSX_DEPLOYMENT_TARGET: 10.15 + MACOSX_DEPLOYMENT_TARGET: 13.0 CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc @@ -43,10 +43,10 @@ jobs: - os: ubuntu-20.04 target: arm-unknown-linux-gnueabihf code-target: linux-armhf - - os: macos-12 + - os: macos-13 target: x86_64-apple-darwin code-target: darwin-x64 - - os: macos-12 + - os: macos-13 target: aarch64-apple-darwin code-target: darwin-arm64 diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index dc820fcb28d..7891edc2447 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -145,9 +145,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.10" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +dependencies = [ + "shlex", +] [[package]] name = "cfg" @@ -1853,6 +1856,12 @@ dependencies = [ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index b4587a37961..0b3d6e2a1ef 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -4,7 +4,7 @@ exclude = ["crates/proc-macro-srv/proc-macro-test/imp"] resolver = "2" [workspace.package] -rust-version = "1.80" +rust-version = "1.81" edition = "2021" license = "MIT OR Apache-2.0" authors = ["rust-analyzer team"] diff --git a/src/tools/rust-analyzer/crates/cfg/src/lib.rs b/src/tools/rust-analyzer/crates/cfg/src/lib.rs index e9daaf7de3c..c2d40086056 100644 --- a/src/tools/rust-analyzer/crates/cfg/src/lib.rs +++ b/src/tools/rust-analyzer/crates/cfg/src/lib.rs @@ -49,6 +49,10 @@ impl CfgOptions { cfg.fold(&|atom| self.enabled.contains(atom)) } + pub fn check_atom(&self, cfg: &CfgAtom) -> bool { + self.enabled.contains(cfg) + } + pub fn insert_atom(&mut self, key: Symbol) { self.enabled.insert(CfgAtom::Flag(key)); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs index 85fb90fdfb6..d568f6faa72 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -36,7 +36,7 @@ macro_rules! f { } struct#0:1@58..64#1# MyTraitMap2#0:2@31..42#0# {#0:1@72..73#1# - map#0:1@86..89#1#:#0:1@89..90#1# #0:1@89..90#1#::#0:1@91..92#1#std#0:1@93..96#1#::#0:1@96..97#1#collections#0:1@98..109#1#::#0:1@109..110#1#HashSet#0:1@111..118#1#<#0:1@118..119#1#(#0:1@119..120#1#)#0:1@120..121#1#>#0:1@121..122#1#,#0:1@122..123#1# + map#0:1@86..89#1#:#0:1@89..90#1# #0:1@89..90#1#::#0:1@91..93#1#std#0:1@93..96#1#::#0:1@96..98#1#collections#0:1@98..109#1#::#0:1@109..111#1#HashSet#0:1@111..118#1#<#0:1@118..119#1#(#0:1@119..120#1#)#0:1@120..121#1#>#0:1@121..122#1#,#0:1@122..123#1# }#0:1@132..133#1# "#]], ); 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 e09ef4f205d..7f1d19719da 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 @@ -6,7 +6,7 @@ use std::{cmp::Ordering, iter, mem, ops::Not}; use base_db::{CrateId, CrateOrigin, Dependency, LangCrateOrigin}; -use cfg::{CfgExpr, CfgOptions}; +use cfg::{CfgAtom, CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ attrs::{Attr, AttrId}, @@ -1324,13 +1324,21 @@ impl DefCollector<'_> { }; // Skip #[test]/#[bench] expansion, which would merely result in more memory usage - // due to duplicating functions into macro expansions + // 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). + // Ideally we will just expand them to nothing here. But we are only collecting macro calls, + // not expanding them, so we have no way to do that. if matches!( def.kind, MacroDefKind::BuiltInAttr(_, expander) if expander.is_test() || expander.is_bench() ) { - return recollect_without(self); + let test_is_active = + self.cfg_options.check_atom(&CfgAtom::Flag(sym::test.clone())); + if test_is_active { + return recollect_without(self); + } } let call_id = || { 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 b9afc666f75..2a8691b461c 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 @@ -4,6 +4,8 @@ use span::{MacroCallId, Span}; use crate::{db::ExpandDatabase, name, tt, ExpandResult, MacroCallKind}; +use super::quote; + macro_rules! register_builtin { ($(($name:ident, $variant:ident) => $expand:ident),* ) => { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -52,15 +54,15 @@ impl BuiltinAttrExpander { } register_builtin! { - (bench, Bench) => dummy_attr_expand, + (bench, Bench) => dummy_gate_test_expand, (cfg_accessible, CfgAccessible) => dummy_attr_expand, (cfg_eval, CfgEval) => dummy_attr_expand, (derive, Derive) => derive_expand, // derive const is equivalent to derive for our proposes. (derive_const, DeriveConst) => derive_expand, (global_allocator, GlobalAllocator) => dummy_attr_expand, - (test, Test) => dummy_attr_expand, - (test_case, TestCase) => dummy_attr_expand + (test, Test) => dummy_gate_test_expand, + (test_case, TestCase) => dummy_gate_test_expand } pub fn find_builtin_attr(ident: &name::Name) -> Option<BuiltinAttrExpander> { @@ -76,6 +78,19 @@ fn dummy_attr_expand( ExpandResult::ok(tt.clone()) } +fn dummy_gate_test_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult<tt::Subtree> { + let result = quote::quote! { span=> + #[cfg(test)] + #tt + }; + ExpandResult::ok(result) +} + /// We generate a very specific expansion here, as we do not actually expand the `#[derive]` attribute /// itself in name res, but we do want to expand it to something for the IDE layer, so that the input /// derive attributes can be downmapped, and resolved as proper paths. 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 584f9631e34..484a8662eb1 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -16,7 +16,10 @@ use crate::{ cfg_process, declarative::DeclarativeMacroExpander, fixup::{self, SyntaxFixupUndoInfo}, - hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt}, + hygiene::{ + span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt, + SyntaxContextExt as _, + }, proc_macro::ProcMacros, span_map::{RealSpanMap, SpanMap, SpanMapRef}, tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, @@ -300,14 +303,16 @@ pub fn expand_speculative( token_tree_to_syntax_node(&speculative_expansion.value, expand_to, loc.def.edition); let syntax_node = node.syntax_node(); - let token = rev_tmap + let (token, _) = rev_tmap .ranges_with_span(span_map.span_for_range(token_to_map.text_range())) - .filter_map(|range| syntax_node.covering_element(range).into_token()) - .min_by_key(|t| { - // prefer tokens of the same kind and text + .filter_map(|(range, ctx)| syntax_node.covering_element(range).into_token().zip(Some(ctx))) + .min_by_key(|(t, ctx)| { + // prefer tokens of the same kind and text, as well as non opaque marked ones // Note the inversion of the score here, as we want to prefer the first token in case // of all tokens having the same score - (t.kind() != token_to_map.kind()) as u8 + 2 * ((t.text() != token_to_map.text()) as u8) + ctx.is_opaque(db) as u8 + + 2 * (t.kind() != token_to_map.kind()) as u8 + + 4 * ((t.text() != token_to_map.text()) as u8) })?; Some((node.syntax_node(), token)) } 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 cc02332207d..5e1448f7950 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs @@ -151,6 +151,7 @@ pub trait SyntaxContextExt { fn remove_mark(&mut self, db: &dyn ExpandDatabase) -> (Option<MacroCallId>, Transparency); fn outer_mark(self, db: &dyn ExpandDatabase) -> (Option<MacroCallId>, Transparency); fn marks(self, db: &dyn ExpandDatabase) -> Vec<(MacroCallId, Transparency)>; + fn is_opaque(self, db: &dyn ExpandDatabase) -> bool; } impl SyntaxContextExt for SyntaxContextId { @@ -177,6 +178,9 @@ impl SyntaxContextExt for SyntaxContextId { marks.reverse(); marks } + fn is_opaque(self, db: &dyn ExpandDatabase) -> bool { + !self.is_root() && db.lookup_intern_syntax_context(self).outer_transparency.is_opaque() + } } // FIXME: Make this a SyntaxContextExt method once we have RPIT 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 95380979492..56cb5fd375c 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -25,6 +25,7 @@ mod prettify_macro_expansion_; use attrs::collect_attrs; use rustc_hash::FxHashMap; +use stdx::TupleExt; use triomphe::Arc; use std::hash::Hash; @@ -772,14 +773,15 @@ impl ExpansionInfo { /// Maps the passed in file range down into a macro expansion if it is the input to a macro call. /// /// Note this does a linear search through the entire backing vector of the spanmap. + // FIXME: Consider adding a reverse map to ExpansionInfo to get rid of the linear search which + // potentially results in quadratic look ups (notably this might improve semantic highlighting perf) pub fn map_range_down_exact( &self, span: Span, - ) -> Option<InMacroFile<impl Iterator<Item = SyntaxToken> + '_>> { - let tokens = self - .exp_map - .ranges_with_span_exact(span) - .flat_map(move |range| self.expanded.value.covering_element(range).into_token()); + ) -> Option<InMacroFile<impl Iterator<Item = (SyntaxToken, SyntaxContextId)> + '_>> { + let tokens = self.exp_map.ranges_with_span_exact(span).flat_map(move |(range, ctx)| { + self.expanded.value.covering_element(range).into_token().zip(Some(ctx)) + }); Some(InMacroFile::new(self.expanded.file_id, tokens)) } @@ -791,11 +793,10 @@ impl ExpansionInfo { pub fn map_range_down( &self, span: Span, - ) -> Option<InMacroFile<impl Iterator<Item = SyntaxToken> + '_>> { - let tokens = self - .exp_map - .ranges_with_span(span) - .flat_map(move |range| self.expanded.value.covering_element(range).into_token()); + ) -> Option<InMacroFile<impl Iterator<Item = (SyntaxToken, SyntaxContextId)> + '_>> { + let tokens = self.exp_map.ranges_with_span(span).flat_map(move |(range, ctx)| { + self.expanded.value.covering_element(range).into_token().zip(Some(ctx)) + }); Some(InMacroFile::new(self.expanded.file_id, tokens)) } @@ -845,7 +846,8 @@ impl ExpansionInfo { self.arg.file_id, arg_map .ranges_with_span_exact(span) - .filter(|range| range.intersect(arg_range).is_some()) + .filter(|(range, _)| range.intersect(arg_range).is_some()) + .map(TupleExt::head) .collect(), ) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs index e74e3d78988..f7bacbd49b3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs @@ -382,8 +382,9 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> { } fn is_object_safe(&self, trait_id: chalk_ir::TraitId<Interner>) -> bool { + // FIXME: When cargo is updated, change to dyn_compatibility let trait_ = from_chalk_trait_id(trait_id); - crate::object_safety::object_safety(self.db, trait_).is_none() + crate::dyn_compatibility::dyn_compatibility(self.db, trait_).is_none() } fn closure_kind( 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 ce5a821ea2b..5620d80adb5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -20,11 +20,11 @@ use triomphe::Arc; use crate::{ chalk_db, consteval::ConstEvalError, + dyn_compatibility::DynCompatibilityViolation, layout::{Layout, LayoutError}, lower::{GenericDefaults, GenericPredicates}, method_resolution::{InherentImpls, TraitImpls, TyFingerprint}, mir::{BorrowckResult, MirBody, MirLowerError}, - object_safety::ObjectSafetyViolation, Binders, ClosureId, Const, FnDefId, ImplTraitId, ImplTraits, InferenceResult, Interner, PolyFnSig, Substitution, TraitEnvironment, TraitRef, Ty, TyDefId, ValueTyDefId, }; @@ -108,8 +108,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { #[salsa::invoke(crate::layout::target_data_layout_query)] fn target_data_layout(&self, krate: CrateId) -> Result<Arc<TargetDataLayout>, Arc<str>>; - #[salsa::invoke(crate::object_safety::object_safety_of_trait_query)] - fn object_safety_of_trait(&self, trait_: TraitId) -> Option<ObjectSafetyViolation>; + #[salsa::invoke(crate::dyn_compatibility::dyn_compatibility_of_trait_query)] + fn dyn_compatibility_of_trait(&self, trait_: TraitId) -> Option<DynCompatibilityViolation>; #[salsa::invoke(crate::lower::ty_query)] #[salsa::cycle(crate::lower::ty_recover)] @@ -280,8 +280,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { } #[test] -fn hir_database_is_object_safe() { - fn _assert_object_safe(_: &dyn HirDatabase) {} +fn hir_database_is_dyn_compatible() { + fn _assert_dyn_compatible(_: &dyn HirDatabase) {} } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs index 82517e69917..7f6b7e392b3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs @@ -58,7 +58,7 @@ impl fmt::Display for CaseType { let repr = match self { CaseType::LowerSnakeCase => "snake_case", CaseType::UpperSnakeCase => "UPPER_SNAKE_CASE", - CaseType::UpperCamelCase => "CamelCase", + CaseType::UpperCamelCase => "UpperCamelCase", }; repr.fmt(f) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs index cbe1af15703..aa0c9e30be1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs @@ -111,7 +111,7 @@ mod tests { check(to_lower_snake_case, "lower_snake_case", expect![[""]]); check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]); check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]); - check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]); + check(to_lower_snake_case, "UpperCamelCase", expect![["upper_camel_case"]]); check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]); check(to_lower_snake_case, "a", expect![[""]]); check(to_lower_snake_case, "abc", expect![[""]]); @@ -121,8 +121,8 @@ mod tests { #[test] fn test_to_camel_case() { - check(to_camel_case, "CamelCase", expect![[""]]); - check(to_camel_case, "CamelCase_", expect![[""]]); + check(to_camel_case, "UpperCamelCase", expect![[""]]); + check(to_camel_case, "UpperCamelCase_", expect![[""]]); check(to_camel_case, "_CamelCase", expect![[""]]); check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]); check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]); @@ -143,7 +143,7 @@ mod tests { check(to_upper_snake_case, "UPPER_SNAKE_CASE", expect![[""]]); check(to_upper_snake_case, "lower_snake_case", expect![["LOWER_SNAKE_CASE"]]); check(to_upper_snake_case, "Weird_Case", expect![["WEIRD_CASE"]]); - check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]); + check(to_upper_snake_case, "UpperCamelCase", expect![["UPPER_CAMEL_CASE"]]); check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]); check(to_upper_snake_case, "A", expect![[""]]); check(to_upper_snake_case, "ABC", expect![[""]]); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs index a4c66268555..e0d1758210e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs @@ -1,4 +1,4 @@ -//! Compute the object-safety of a trait +//! Compute the dyn-compatibility of a trait use std::ops::ControlFlow; @@ -28,14 +28,14 @@ use crate::{ }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ObjectSafetyViolation { +pub enum DynCompatibilityViolation { SizedSelf, SelfReferential, Method(FunctionId, MethodViolationCode), AssocConst(ConstId), GAT(TypeAliasId), // This doesn't exist in rustc, but added for better visualization - HasNonSafeSuperTrait(TraitId), + HasNonCompatibleSuperTrait(TraitId), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -50,70 +50,73 @@ pub enum MethodViolationCode { UndispatchableReceiver, } -pub fn object_safety(db: &dyn HirDatabase, trait_: TraitId) -> Option<ObjectSafetyViolation> { +pub fn dyn_compatibility( + db: &dyn HirDatabase, + trait_: TraitId, +) -> Option<DynCompatibilityViolation> { for super_trait in all_super_traits(db.upcast(), trait_).into_iter().skip(1).rev() { - if db.object_safety_of_trait(super_trait).is_some() { - return Some(ObjectSafetyViolation::HasNonSafeSuperTrait(super_trait)); + if db.dyn_compatibility_of_trait(super_trait).is_some() { + return Some(DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait)); } } - db.object_safety_of_trait(trait_) + db.dyn_compatibility_of_trait(trait_) } -pub fn object_safety_with_callback<F>( +pub fn dyn_compatibility_with_callback<F>( db: &dyn HirDatabase, trait_: TraitId, cb: &mut F, ) -> ControlFlow<()> where - F: FnMut(ObjectSafetyViolation) -> ControlFlow<()>, + F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>, { for super_trait in all_super_traits(db.upcast(), trait_).into_iter().skip(1).rev() { - if db.object_safety_of_trait(super_trait).is_some() { - cb(ObjectSafetyViolation::HasNonSafeSuperTrait(trait_))?; + if db.dyn_compatibility_of_trait(super_trait).is_some() { + cb(DynCompatibilityViolation::HasNonCompatibleSuperTrait(trait_))?; } } - object_safety_of_trait_with_callback(db, trait_, cb) + dyn_compatibility_of_trait_with_callback(db, trait_, cb) } -pub fn object_safety_of_trait_with_callback<F>( +pub fn dyn_compatibility_of_trait_with_callback<F>( db: &dyn HirDatabase, trait_: TraitId, cb: &mut F, ) -> ControlFlow<()> where - F: FnMut(ObjectSafetyViolation) -> ControlFlow<()>, + F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>, { // Check whether this has a `Sized` bound if generics_require_sized_self(db, trait_.into()) { - cb(ObjectSafetyViolation::SizedSelf)?; + cb(DynCompatibilityViolation::SizedSelf)?; } // Check if there exist bounds that referencing self if predicates_reference_self(db, trait_) { - cb(ObjectSafetyViolation::SelfReferential)?; + cb(DynCompatibilityViolation::SelfReferential)?; } if bounds_reference_self(db, trait_) { - cb(ObjectSafetyViolation::SelfReferential)?; + cb(DynCompatibilityViolation::SelfReferential)?; } // rustc checks for non-lifetime binders here, but we don't support HRTB yet let trait_data = db.trait_data(trait_); for (_, assoc_item) in &trait_data.items { - object_safety_violation_for_assoc_item(db, trait_, *assoc_item, cb)?; + dyn_compatibility_violation_for_assoc_item(db, trait_, *assoc_item, cb)?; } ControlFlow::Continue(()) } -pub fn object_safety_of_trait_query( +pub fn dyn_compatibility_of_trait_query( db: &dyn HirDatabase, trait_: TraitId, -) -> Option<ObjectSafetyViolation> { +) -> Option<DynCompatibilityViolation> { let mut res = None; - object_safety_of_trait_with_callback(db, trait_, &mut |osv| { + dyn_compatibility_of_trait_with_callback(db, trait_, &mut |osv| { res = Some(osv); ControlFlow::Break(()) }); @@ -321,14 +324,14 @@ fn contains_illegal_self_type_reference<T: TypeVisitable<Interner>>( t.visit_with(visitor.as_dyn(), outer_binder).is_break() } -fn object_safety_violation_for_assoc_item<F>( +fn dyn_compatibility_violation_for_assoc_item<F>( db: &dyn HirDatabase, trait_: TraitId, item: AssocItemId, cb: &mut F, ) -> ControlFlow<()> where - F: FnMut(ObjectSafetyViolation) -> ControlFlow<()>, + F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>, { // Any item that has a `Self : Sized` requisite is otherwise // exempt from the regulations. @@ -337,10 +340,10 @@ where } match item { - AssocItemId::ConstId(it) => cb(ObjectSafetyViolation::AssocConst(it)), + AssocItemId::ConstId(it) => cb(DynCompatibilityViolation::AssocConst(it)), AssocItemId::FunctionId(it) => { virtual_call_violations_for_method(db, trait_, it, &mut |mvc| { - cb(ObjectSafetyViolation::Method(it, mvc)) + cb(DynCompatibilityViolation::Method(it, mvc)) }) } AssocItemId::TypeAliasId(it) => { @@ -350,7 +353,7 @@ where } else { let generic_params = db.generic_params(item.into()); if !generic_params.is_empty() { - cb(ObjectSafetyViolation::GAT(it)) + cb(DynCompatibilityViolation::GAT(it)) } else { ControlFlow::Continue(()) } @@ -469,7 +472,7 @@ fn receiver_is_dispatchable( return false; }; - // `self: Self` can't be dispatched on, but this is already considered object safe. + // `self: Self` can't be dispatched on, but this is already considered dyn compatible // See rustc's comment on https://github.com/rust-lang/rust/blob/3f121b9461cce02a703a0e7e450568849dfaa074/compiler/rustc_trait_selection/src/traits/object_safety.rs#L433-L437 if sig .skip_binders() diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility/tests.rs index c2a9117c5be..3f3e68eeb1c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility/tests.rs @@ -5,29 +5,29 @@ use rustc_hash::{FxHashMap, FxHashSet}; use syntax::ToSmolStr; use test_fixture::WithFixture; -use crate::{object_safety::object_safety_with_callback, test_db::TestDB}; +use crate::{dyn_compatibility::dyn_compatibility_with_callback, test_db::TestDB}; use super::{ + DynCompatibilityViolation, MethodViolationCode::{self, *}, - ObjectSafetyViolation, }; -use ObjectSafetyViolationKind::*; +use DynCompatibilityViolationKind::*; #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum ObjectSafetyViolationKind { +enum DynCompatibilityViolationKind { SizedSelf, SelfReferential, Method(MethodViolationCode), AssocConst, GAT, - HasNonSafeSuperTrait, + HasNonCompatibleSuperTrait, } -fn check_object_safety<'a>( +fn check_dyn_compatibility<'a>( ra_fixture: &str, - expected: impl IntoIterator<Item = (&'a str, Vec<ObjectSafetyViolationKind>)>, + expected: impl IntoIterator<Item = (&'a str, Vec<DynCompatibilityViolationKind>)>, ) { let mut expected: FxHashMap<_, _> = expected.into_iter().map(|(id, osvs)| (id, FxHashSet::from_iter(osvs))).collect(); @@ -53,18 +53,20 @@ fn check_object_safety<'a>( continue; }; let mut osvs = FxHashSet::default(); - object_safety_with_callback(&db, trait_id, &mut |osv| { + dyn_compatibility_with_callback(&db, trait_id, &mut |osv| { osvs.insert(match osv { - ObjectSafetyViolation::SizedSelf => SizedSelf, - ObjectSafetyViolation::SelfReferential => SelfReferential, - ObjectSafetyViolation::Method(_, mvc) => Method(mvc), - ObjectSafetyViolation::AssocConst(_) => AssocConst, - ObjectSafetyViolation::GAT(_) => GAT, - ObjectSafetyViolation::HasNonSafeSuperTrait(_) => HasNonSafeSuperTrait, + DynCompatibilityViolation::SizedSelf => SizedSelf, + DynCompatibilityViolation::SelfReferential => SelfReferential, + DynCompatibilityViolation::Method(_, mvc) => Method(mvc), + DynCompatibilityViolation::AssocConst(_) => AssocConst, + DynCompatibilityViolation::GAT(_) => GAT, + DynCompatibilityViolation::HasNonCompatibleSuperTrait(_) => { + HasNonCompatibleSuperTrait + } }); ControlFlow::Continue(()) }); - assert_eq!(osvs, expected, "Object safety violations for `{name}` do not match;"); + assert_eq!(osvs, expected, "Dyn Compatibility violations for `{name}` do not match;"); } let remains: Vec<_> = expected.keys().collect(); @@ -73,7 +75,7 @@ fn check_object_safety<'a>( #[test] fn item_bounds_can_reference_self() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: eq pub trait Foo { @@ -88,7 +90,7 @@ pub trait Foo { #[test] fn associated_consts() { - check_object_safety( + check_dyn_compatibility( r#" trait Bar { const X: usize; @@ -100,7 +102,7 @@ trait Bar { #[test] fn bounds_reference_self() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: eq trait X { @@ -113,7 +115,7 @@ trait X { #[test] fn by_value_self() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar { @@ -135,7 +137,7 @@ trait Quux { #[test] fn generic_methods() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar { @@ -157,7 +159,7 @@ trait Qax { #[test] fn mentions_self() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar { @@ -182,7 +184,7 @@ trait Quux { #[test] fn no_static() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Foo { @@ -195,7 +197,7 @@ trait Foo { #[test] fn sized_self() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar: Sized { @@ -205,7 +207,7 @@ trait Bar: Sized { [("Bar", vec![SizedSelf])], ); - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar @@ -220,7 +222,7 @@ trait Bar #[test] fn supertrait_gat() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait GatTrait { @@ -229,13 +231,13 @@ trait GatTrait { trait SuperTrait<T>: GatTrait {} "#, - [("GatTrait", vec![GAT]), ("SuperTrait", vec![HasNonSafeSuperTrait])], + [("GatTrait", vec![GAT]), ("SuperTrait", vec![HasNonCompatibleSuperTrait])], ); } #[test] fn supertrait_mentions_self() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar<T> { @@ -251,7 +253,7 @@ trait Baz : Bar<Self> { #[test] fn rustc_issue_19538() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Foo { @@ -260,13 +262,13 @@ trait Foo { trait Bar: Foo {} "#, - [("Foo", vec![Method(Generic)]), ("Bar", vec![HasNonSafeSuperTrait])], + [("Foo", vec![Method(Generic)]), ("Bar", vec![HasNonCompatibleSuperTrait])], ); } #[test] fn rustc_issue_22040() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: fmt, eq, dispatch_from_dyn use core::fmt::Debug; @@ -281,7 +283,7 @@ trait Expr: Debug + PartialEq { #[test] fn rustc_issue_102762() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: future, send, sync, dispatch_from_dyn, deref use core::pin::Pin; @@ -313,7 +315,7 @@ pub trait Fetcher: Send + Sync { #[test] fn rustc_issue_102933() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: future, dispatch_from_dyn, deref use core::future::Future; @@ -351,7 +353,7 @@ pub trait B2: Service<Response = i32> + B1 { #[test] fn rustc_issue_106247() { - check_object_safety( + check_dyn_compatibility( r#" //- minicore: sync, dispatch_from_dyn pub trait Trait { @@ -363,8 +365,8 @@ pub trait Trait { } #[test] -fn std_error_is_object_safe() { - check_object_safety( +fn std_error_is_dyn_compatible() { + check_dyn_compatibility( r#" //- minicore: fmt, dispatch_from_dyn trait Erased<'a>: 'a {} @@ -380,14 +382,14 @@ pub trait Error: core::fmt::Debug + core::fmt::Display { } #[test] -fn lifetime_gat_is_object_unsafe() { - check_object_safety( +fn lifetime_gat_is_dyn_incompatible() { + check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Foo { type Bar<'a>; } "#, - [("Foo", vec![ObjectSafetyViolationKind::GAT])], + [("Foo", vec![DynCompatibilityViolationKind::GAT])], ); } 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 5ed41b99ba3..ef570a20556 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -38,11 +38,11 @@ pub mod consteval; pub mod db; pub mod diagnostics; pub mod display; +pub mod dyn_compatibility; pub mod lang_items; pub mod layout; pub mod method_resolution; pub mod mir; -pub mod object_safety; pub mod primitive; pub mod traits; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs index 878d584a4ef..9830fa1ca7b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs @@ -386,82 +386,91 @@ fn ever_initialized_map( fn dfs( db: &dyn HirDatabase, body: &MirBody, - b: BasicBlockId, l: LocalId, + stack: &mut Vec<BasicBlockId>, result: &mut ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>>, ) { - let mut is_ever_initialized = result[b][l]; // It must be filled, as we use it as mark for dfs - let block = &body.basic_blocks[b]; - for statement in &block.statements { - match &statement.kind { - StatementKind::Assign(p, _) => { - if p.projection.lookup(&body.projection_store).is_empty() && p.local == l { - is_ever_initialized = true; + while let Some(b) = stack.pop() { + let mut is_ever_initialized = result[b][l]; // It must be filled, as we use it as mark for dfs + let block = &body.basic_blocks[b]; + for statement in &block.statements { + match &statement.kind { + StatementKind::Assign(p, _) => { + if p.projection.lookup(&body.projection_store).is_empty() && p.local == l { + is_ever_initialized = true; + } } - } - StatementKind::StorageDead(p) => { - if *p == l { - is_ever_initialized = false; + StatementKind::StorageDead(p) => { + if *p == l { + is_ever_initialized = false; + } } + StatementKind::Deinit(_) + | StatementKind::FakeRead(_) + | StatementKind::Nop + | StatementKind::StorageLive(_) => (), } - StatementKind::Deinit(_) - | StatementKind::FakeRead(_) - | StatementKind::Nop - | StatementKind::StorageLive(_) => (), - } - } - let Some(terminator) = &block.terminator else { - never!( - "Terminator should be none only in construction.\nThe body:\n{}", - body.pretty_print(db) - ); - return; - }; - let mut process = |target, is_ever_initialized| { - if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized { - result[target].insert(l, is_ever_initialized); - dfs(db, body, target, l, result); - } - }; - match &terminator.kind { - TerminatorKind::Goto { target } => process(*target, is_ever_initialized), - TerminatorKind::SwitchInt { targets, .. } => { - targets.all_targets().iter().for_each(|&it| process(it, is_ever_initialized)); } - TerminatorKind::UnwindResume - | TerminatorKind::Abort - | TerminatorKind::Return - | TerminatorKind::Unreachable => (), - TerminatorKind::Call { target, cleanup, destination, .. } => { - if destination.projection.lookup(&body.projection_store).is_empty() - && destination.local == l - { - is_ever_initialized = true; + let Some(terminator) = &block.terminator else { + never!( + "Terminator should be none only in construction.\nThe body:\n{}", + body.pretty_print(db) + ); + return; + }; + let mut process = |target, is_ever_initialized| { + if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized { + result[target].insert(l, is_ever_initialized); + stack.push(target); + } + }; + match &terminator.kind { + TerminatorKind::Goto { target } => process(*target, is_ever_initialized), + TerminatorKind::SwitchInt { targets, .. } => { + targets.all_targets().iter().for_each(|&it| process(it, is_ever_initialized)); + } + TerminatorKind::UnwindResume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable => (), + TerminatorKind::Call { target, cleanup, destination, .. } => { + if destination.projection.lookup(&body.projection_store).is_empty() + && destination.local == l + { + is_ever_initialized = true; + } + target.iter().chain(cleanup).for_each(|&it| process(it, is_ever_initialized)); + } + TerminatorKind::Drop { target, unwind, place: _ } => { + iter::once(target) + .chain(unwind) + .for_each(|&it| process(it, is_ever_initialized)); + } + TerminatorKind::DropAndReplace { .. } + | TerminatorKind::Assert { .. } + | TerminatorKind::Yield { .. } + | TerminatorKind::CoroutineDrop + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } => { + never!("We don't emit these MIR terminators yet"); } - target.iter().chain(cleanup).for_each(|&it| process(it, is_ever_initialized)); - } - TerminatorKind::Drop { target, unwind, place: _ } => { - iter::once(target).chain(unwind).for_each(|&it| process(it, is_ever_initialized)); - } - TerminatorKind::DropAndReplace { .. } - | TerminatorKind::Assert { .. } - | TerminatorKind::Yield { .. } - | TerminatorKind::CoroutineDrop - | TerminatorKind::FalseEdge { .. } - | TerminatorKind::FalseUnwind { .. } => { - never!("We don't emit these MIR terminators yet"); } } } + let mut stack = Vec::new(); for &l in &body.param_locals { result[body.start_block].insert(l, true); - dfs(db, body, body.start_block, l, &mut result); + stack.clear(); + stack.push(body.start_block); + dfs(db, body, l, &mut stack, &mut result); } for l in body.locals.iter().map(|it| it.0) { db.unwind_if_cancelled(); if !result[body.start_block].contains_idx(l) { result[body.start_block].insert(l, false); - dfs(db, body, body.start_block, l, &mut result); + stack.clear(); + stack.push(body.start_block); + dfs(db, body, l, &mut stack, &mut result); } } result diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 8f5db32f957..30e023e1a47 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -144,9 +144,9 @@ pub use { hir_ty::{ consteval::ConstEvalError, display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, + dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode}, layout::LayoutError, mir::{MirEvalError, MirLowerError}, - object_safety::{MethodViolationCode, ObjectSafetyViolation}, CastError, FnAbi, PointerCast, Safety, }, // FIXME: Properly encapsulate mir @@ -497,10 +497,9 @@ impl Module { /// Finds a parent module. pub fn parent(self, db: &dyn HirDatabase) -> Option<Module> { - // FIXME: handle block expressions as modules (their parent is in a different DefMap) let def_map = self.id.def_map(db.upcast()); - let parent_id = def_map[self.id.local_id].parent?; - Some(Module { id: def_map.module_id(parent_id) }) + let parent_id = def_map.containing_module(self.id.local_id)?; + Some(Module { id: parent_id }) } /// Finds nearest non-block ancestor `Module` (`self` included). @@ -557,7 +556,7 @@ impl Module { acc: &mut Vec<AnyDiagnostic>, style_lints: bool, ) { - let _p = tracing::info_span!("Module::diagnostics", name = ?self.name(db)).entered(); + let _p = tracing::info_span!("diagnostics", name = ?self.name(db)).entered(); let edition = db.crate_graph()[self.id.krate()].edition; let def_map = self.id.def_map(db.upcast()); for diag in def_map.diagnostics() { @@ -2690,8 +2689,8 @@ impl Trait { .count() } - pub fn object_safety(&self, db: &dyn HirDatabase) -> Option<ObjectSafetyViolation> { - hir_ty::object_safety::object_safety(db, self.id) + pub fn dyn_compatibility(&self, db: &dyn HirDatabase) -> Option<DynCompatibilityViolation> { + hir_ty::dyn_compatibility::dyn_compatibility(db, self.id) } fn all_macro_calls(&self, db: &dyn HirDatabase) -> Box<[(AstId<ast::Item>, MacroCallId)]> { diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index fa14b53dbc3..b27f1fbb5db 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -24,6 +24,7 @@ use hir_expand::{ builtin::{BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, files::InRealFile, + hygiene::SyntaxContextExt as _, inert_attr_macro::find_builtin_attr_idx, name::AsName, FileRange, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt, @@ -32,13 +33,13 @@ use intern::Symbol; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; -use span::{EditionedFileId, FileId, HirFileIdRepr}; +use span::{EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId}; use stdx::TupleExt; use syntax::{ algo::skip_trivia_token, - ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody, IsString as _}, - match_ast, AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, - TextRange, TextSize, + ast::{self, HasAttrs as _, HasGenericParams, IsString as _}, + AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, + TextSize, }; use crate::{ @@ -608,7 +609,7 @@ impl<'db> SemanticsImpl<'db> { let quote = string.open_quote_text_range()?; let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?; - self.descend_into_macros_breakable(token, |token| { + self.descend_into_macros_breakable(token, |token, _| { (|| { let token = token.value; let string = ast::String::cast(token)?; @@ -655,7 +656,7 @@ impl<'db> SemanticsImpl<'db> { let original_string = ast::String::cast(original_token.clone())?; let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?; let quote = original_string.open_quote_text_range()?; - self.descend_into_macros_breakable(original_token, |token| { + self.descend_into_macros_breakable(original_token, |token, _| { (|| { let token = token.value; self.resolve_offset_in_format_args( @@ -718,7 +719,7 @@ impl<'db> SemanticsImpl<'db> { // node is just the token, so descend the token self.descend_into_macros_impl( InRealFile::new(file_id, first), - &mut |InFile { value, .. }| { + &mut |InFile { value, .. }, _ctx| { if let Some(node) = value .parent_ancestors() .take_while(|it| it.text_range() == value.text_range()) @@ -732,7 +733,7 @@ impl<'db> SemanticsImpl<'db> { } else { // Descend first and last token, then zip them to look for the node they belong to let mut scratch: SmallVec<[_; 1]> = smallvec![]; - self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token| { + self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token, _ctx| { scratch.push(token); CONTINUE_NO_BREAKS }); @@ -740,7 +741,7 @@ impl<'db> SemanticsImpl<'db> { let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( InRealFile::new(file_id, last), - &mut |InFile { value: last, file_id: last_fid }| { + &mut |InFile { value: last, file_id: last_fid }, _ctx| { if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() { if first_fid == last_fid { if let Some(p) = first.parent() { @@ -763,7 +764,9 @@ impl<'db> SemanticsImpl<'db> { res } - fn is_inside_macro_call(token: &SyntaxToken) -> bool { + // FIXME: This isn't quite right wrt to inner attributes + /// Does a syntactic traversal to check whether this token might be inside a macro call + pub fn might_be_inside_macro_call(&self, token: &SyntaxToken) -> bool { token.parent_ancestors().any(|ancestor| { if ast::MacroCall::can_cast(ancestor.kind()) { return true; @@ -781,25 +784,14 @@ impl<'db> SemanticsImpl<'db> { }) } - pub fn descend_into_macros_exact_if_in_macro( - &self, - token: SyntaxToken, - ) -> SmallVec<[SyntaxToken; 1]> { - if Self::is_inside_macro_call(&token) { - self.descend_into_macros_exact(token) - } else { - smallvec![token] - } - } - pub fn descend_into_macros_cb( &self, token: SyntaxToken, - mut cb: impl FnMut(InFile<SyntaxToken>), + mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContextId), ) { if let Ok(token) = self.wrap_token_infile(token).into_real_file() { - self.descend_into_macros_impl(token, &mut |t| { - cb(t); + self.descend_into_macros_impl(token, &mut |t, ctx| { + cb(t, ctx); CONTINUE_NO_BREAKS }); } @@ -808,7 +800,7 @@ impl<'db> SemanticsImpl<'db> { pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { let mut res = smallvec![]; if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { - self.descend_into_macros_impl(token, &mut |t| { + self.descend_into_macros_impl(token, &mut |t, _ctx| { res.push(t.value); CONTINUE_NO_BREAKS }); @@ -819,10 +811,27 @@ impl<'db> SemanticsImpl<'db> { res } + pub fn descend_into_macros_no_opaque(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { + let mut res = smallvec![]; + if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { + self.descend_into_macros_impl(token, &mut |t, ctx| { + if !ctx.is_opaque(self.db.upcast()) { + // Don't descend into opaque contexts + res.push(t.value); + } + CONTINUE_NO_BREAKS + }); + } + if res.is_empty() { + res.push(token); + } + res + } + pub fn descend_into_macros_breakable<T>( &self, token: InRealFile<SyntaxToken>, - mut cb: impl FnMut(InFile<SyntaxToken>) -> ControlFlow<T>, + mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContextId) -> ControlFlow<T>, ) -> Option<T> { self.descend_into_macros_impl(token.clone(), &mut cb) } @@ -834,10 +843,12 @@ impl<'db> SemanticsImpl<'db> { let text = token.text(); let kind = token.kind(); - self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }| { + self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }, ctx| { let mapped_kind = value.kind(); let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); - let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); + let matches = (kind == mapped_kind || any_ident_match()) + && text == value.text() + && !ctx.is_opaque(self.db.upcast()); if matches { r.push(value); } @@ -854,17 +865,21 @@ impl<'db> SemanticsImpl<'db> { let text = token.text(); let kind = token.kind(); if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { - self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| { - let mapped_kind = value.kind(); - let any_ident_match = - || kind.is_any_identifier() && value.kind().is_any_identifier(); - let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); - if matches { - ControlFlow::Break(value) - } else { - ControlFlow::Continue(()) - } - }) + self.descend_into_macros_breakable( + token.clone(), + |InFile { value, file_id: _ }, _ctx| { + let mapped_kind = value.kind(); + let any_ident_match = + || kind.is_any_identifier() && value.kind().is_any_identifier(); + let matches = + (kind == mapped_kind || any_ident_match()) && text == value.text(); + if matches { + ControlFlow::Break(value) + } else { + ControlFlow::Continue(()) + } + }, + ) } else { None } @@ -874,7 +889,7 @@ impl<'db> SemanticsImpl<'db> { fn descend_into_macros_impl<T>( &self, InRealFile { value: token, file_id }: InRealFile<SyntaxToken>, - f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<T>, + f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContextId) -> ControlFlow<T>, ) -> Option<T> { let _p = tracing::info_span!("descend_into_macros_impl").entered(); let (sa, span, file_id) = token @@ -898,7 +913,8 @@ impl<'db> SemanticsImpl<'db> { // These are tracked to know which macro calls we still have to look into // the tokens themselves aren't that interesting as the span that is being used to map // things down never changes. - let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])]; + let mut stack: Vec<(_, SmallVec<[_; 2]>)> = + vec![(file_id, smallvec![(token, SyntaxContextId::ROOT)])]; // Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| { @@ -921,11 +937,11 @@ impl<'db> SemanticsImpl<'db> { // Filters out all tokens that contain the given range (usually the macro call), any such // token is redundant as the corresponding macro call has already been processed let filter_duplicates = |tokens: &mut SmallVec<_>, range: TextRange| { - tokens.retain(|t: &mut SyntaxToken| !range.contains_range(t.text_range())) + tokens.retain(|(t, _): &mut (SyntaxToken, _)| !range.contains_range(t.text_range())) }; while let Some((expansion, ref mut tokens)) = stack.pop() { - while let Some(token) = tokens.pop() { + while let Some((token, ctx)) = tokens.pop() { let was_not_remapped = (|| { // First expand into attribute invocations let containing_attribute_macro_call = self.with_ctx(|ctx| { @@ -1036,7 +1052,7 @@ impl<'db> SemanticsImpl<'db> { let text_range = attr.syntax().text_range(); // remove any other token in this macro input, all their mappings are the // same as this - tokens.retain(|t| { + tokens.retain(|(t, _)| { !text_range.contains_range(t.text_range()) }); return process_expansion_for_token( @@ -1093,7 +1109,7 @@ impl<'db> SemanticsImpl<'db> { .is_none(); if was_not_remapped { - if let ControlFlow::Break(b) = f(InFile::new(expansion, token)) { + if let ControlFlow::Break(b) = f(InFile::new(expansion, token), ctx) { return Some(b); } } @@ -1221,26 +1237,10 @@ impl<'db> SemanticsImpl<'db> { ToDef::to_def(self, src.as_ref()) } - pub fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> { - let text = lifetime.text(); - let label = lifetime.syntax().ancestors().find_map(|syn| { - let label = match_ast! { - match syn { - ast::ForExpr(it) => it.label(), - ast::WhileExpr(it) => it.label(), - ast::LoopExpr(it) => it.label(), - ast::BlockExpr(it) => it.label(), - _ => None, - } - }; - label.filter(|l| { - l.lifetime() - .and_then(|lt| lt.lifetime_ident_token()) - .map_or(false, |lt| lt.text() == text) - }) - })?; - let src = self.wrap_node_infile(label); - ToDef::to_def(self, src.as_ref()) + pub fn resolve_label(&self, label: &ast::Lifetime) -> Option<Label> { + let (parent, label_id) = self + .with_ctx(|ctx| ctx.label_ref_to_def(self.wrap_node_infile(label.clone()).as_ref()))?; + Some(Label { parent, label_id }) } pub fn resolve_type(&self, ty: &ast::Type) -> Option<Type> { diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs index c1e4e1d1e27..fd6d52d6c9d 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs @@ -92,7 +92,7 @@ use hir_def::{ keys::{self, Key}, DynMap, }, - hir::{BindingId, LabelId}, + hir::{BindingId, Expr, LabelId}, AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, Lookup, MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, @@ -343,6 +343,20 @@ impl SourceToDefCtx<'_, '_> { Some((container, label_id)) } + pub(super) fn label_ref_to_def( + &mut self, + src: InFile<&ast::Lifetime>, + ) -> Option<(DefWithBodyId, LabelId)> { + let break_or_continue = ast::Expr::cast(src.value.syntax().parent()?)?; + let container = self.find_pat_or_label_container(src.syntax_ref())?; + let (body, source_map) = self.db.body_with_source_map(container); + let break_or_continue = source_map.node_expr(src.with_value(&break_or_continue))?; + let (Expr::Break { label, .. } | Expr::Continue { label }) = body[break_or_continue] else { + return None; + }; + Some((container, label?)) + } + pub(super) fn item_to_macro_call(&mut self, src: InFile<&ast::Item>) -> Option<MacroCallId> { let map = self.dyn_map(src)?; map[keys::ATTR_MACRO_CALL].get(&AstPtr::new(src.value)).copied() diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs index a43a4b5e1a0..61dc72e0b33 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs @@ -1,8 +1,12 @@ use hir::TypeInfo; use ide_db::syntax_helpers::suggest_name; use syntax::{ - ast::{self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, HasName}, - ted, NodeOrToken, + ast::{ + self, edit::IndentLevel, edit_in_place::Indent, make, syntax_factory::SyntaxFactory, + AstNode, + }, + syntax_editor::Position, + NodeOrToken, SyntaxKind::{BLOCK_EXPR, BREAK_EXPR, COMMENT, LOOP_EXPR, MATCH_GUARD, PATH_EXPR, RETURN_EXPR}, SyntaxNode, T, }; @@ -105,39 +109,46 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op ), }; + let make = SyntaxFactory::new(); + let mut editor = edit.make_editor(&expr_replace); + + let pat_name = make.name(&var_name); + let name_expr = make.expr_path(make::ext::ident_path(&var_name)); + + if let Some(cap) = ctx.config.snippet_cap { + let tabstop = edit.make_tabstop_before(cap); + editor.add_annotation(pat_name.syntax().clone(), tabstop); + } + let ident_pat = match parent { Some(ast::Expr::RefExpr(expr)) if expr.mut_token().is_some() => { - make::ident_pat(false, true, make::name(&var_name)) + make.ident_pat(false, true, pat_name) } _ if needs_adjust && !needs_ref && ty.as_ref().is_some_and(|ty| ty.is_mutable_reference()) => { - make::ident_pat(false, true, make::name(&var_name)) + make.ident_pat(false, true, pat_name) } - _ => make::ident_pat(false, false, make::name(&var_name)), + _ => make.ident_pat(false, false, pat_name), }; let to_extract_no_ref = match ty.as_ref().filter(|_| needs_ref) { Some(receiver_type) if receiver_type.is_mutable_reference() => { - make::expr_ref(to_extract_no_ref, true) + make.expr_ref(to_extract_no_ref, true) } Some(receiver_type) if receiver_type.is_reference() => { - make::expr_ref(to_extract_no_ref, false) + make.expr_ref(to_extract_no_ref, false) } _ => to_extract_no_ref, }; - let expr_replace = edit.make_syntax_mut(expr_replace); - let let_stmt = - make::let_stmt(ident_pat.into(), None, Some(to_extract_no_ref)).clone_for_update(); - let name_expr = make::expr_path(make::ext::ident_path(&var_name)).clone_for_update(); + let let_stmt = make.let_stmt(ident_pat.into(), None, Some(to_extract_no_ref)); match anchor { Anchor::Before(place) => { let prev_ws = place.prev_sibling_or_token().and_then(|it| it.into_token()); let indent_to = IndentLevel::from_node(&place); - let insert_place = edit.make_syntax_mut(place); // Adjust ws to insert depending on if this is all inline or on separate lines let trailing_ws = if prev_ws.is_some_and(|it| it.text().starts_with('\n')) { @@ -146,37 +157,20 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op " ".to_owned() }; - ted::insert_all_raw( - ted::Position::before(insert_place), + editor.insert_all( + Position::before(place), vec![ let_stmt.syntax().clone().into(), make::tokens::whitespace(&trailing_ws).into(), ], ); - ted::replace(expr_replace, name_expr.syntax()); - - if let Some(cap) = ctx.config.snippet_cap { - if let Some(ast::Pat::IdentPat(ident_pat)) = let_stmt.pat() { - if let Some(name) = ident_pat.name() { - edit.add_tabstop_before(cap, name); - } - } - } + editor.replace(expr_replace, name_expr.syntax()); } Anchor::Replace(stmt) => { cov_mark::hit!(test_extract_var_expr_stmt); - let stmt_replace = edit.make_mut(stmt); - ted::replace(stmt_replace.syntax(), let_stmt.syntax()); - - if let Some(cap) = ctx.config.snippet_cap { - if let Some(ast::Pat::IdentPat(ident_pat)) = let_stmt.pat() { - if let Some(name) = ident_pat.name() { - edit.add_tabstop_before(cap, name); - } - } - } + editor.replace(stmt.syntax(), let_stmt.syntax()); } Anchor::WrapInBlock(to_wrap) => { let indent_to = to_wrap.indent_level(); @@ -184,47 +178,22 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let block = if to_wrap.syntax() == &expr_replace { // Since `expr_replace` is the same that needs to be wrapped in a block, // we can just directly replace it with a block - let block = - make::block_expr([let_stmt.into()], Some(name_expr)).clone_for_update(); - ted::replace(expr_replace, block.syntax()); - - block + make.block_expr([let_stmt.into()], Some(name_expr)) } else { - // `expr_replace` is a descendant of `to_wrap`, so both steps need to be - // handled separately, otherwise we wrap the wrong expression - let to_wrap = edit.make_mut(to_wrap); - - // Replace the target expr first so that we don't need to find where - // `expr_replace` is in the wrapped `to_wrap` - ted::replace(expr_replace, name_expr.syntax()); - - // Wrap `to_wrap` in a block - let block = make::block_expr([let_stmt.into()], Some(to_wrap.clone())) - .clone_for_update(); - ted::replace(to_wrap.syntax(), block.syntax()); - - block + // `expr_replace` is a descendant of `to_wrap`, so we just replace it with `name_expr`. + editor.replace(expr_replace, name_expr.syntax()); + make.block_expr([let_stmt.into()], Some(to_wrap.clone())) }; - if let Some(cap) = ctx.config.snippet_cap { - // Adding a tabstop to `name` requires finding the let stmt again, since - // the existing `let_stmt` is not actually added to the tree - let pat = block.statements().find_map(|stmt| { - let ast::Stmt::LetStmt(let_stmt) = stmt else { return None }; - let_stmt.pat() - }); - - if let Some(ast::Pat::IdentPat(ident_pat)) = pat { - if let Some(name) = ident_pat.name() { - edit.add_tabstop_before(cap, name); - } - } - } + editor.replace(to_wrap.syntax(), block.syntax()); // fixup indentation of block block.indent(indent_to); } } + + editor.add_mappings(make.finish_with_mappings()); + edit.add_file_edits(ctx.file_id(), editor); edit.rename(); }, ) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs index 0d403f49b7a..1d05419c96d 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs @@ -7,7 +7,7 @@ use hir::ImportPathConfig; use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap}; -use crate::snippet::Snippet; +use crate::{snippet::Snippet, CompletionFieldsToResolve}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct CompletionConfig { @@ -27,6 +27,7 @@ pub struct CompletionConfig { pub prefer_absolute: bool, pub snippets: Vec<Snippet>, pub limit: Option<usize>, + pub fields_to_resolve: CompletionFieldsToResolve, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index 192f1b43fac..e49a9e3b064 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -7,7 +7,8 @@ mod tests; use std::{iter, ops::ControlFlow}; use hir::{ - HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo, + HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, + TypeInfo, }; use ide_db::{ base_db::SourceDatabase, famous_defs::FamousDefs, helpers::is_editable_crate, FilePosition, @@ -743,7 +744,12 @@ impl<'a> CompletionContext<'a> { } }); - let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count(); + let depth_from_crate_root = iter::successors(Some(module), |m| m.parent(db)) + // `BlockExpr` modules are not count as module depth + .filter(|m| !matches!(m.definition_source(db).value, ModuleSource::BlockExpr(_))) + .count() + // exclude `m` itself + .saturating_sub(1); let complete_semicolon = if config.add_semicolon_to_unit { let inside_closure_ret = token.parent_ancestors().try_for_each(|ancestor| { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs index 58d1fad0950..a78976d3fd8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs @@ -37,6 +37,31 @@ pub use crate::{ snippet::{Snippet, SnippetScope}, }; +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct CompletionFieldsToResolve { + pub resolve_label_details: bool, + pub resolve_tags: bool, + pub resolve_detail: bool, + pub resolve_documentation: bool, + pub resolve_filter_text: bool, + pub resolve_text_edit: bool, + pub resolve_command: bool, +} + +impl CompletionFieldsToResolve { + pub const fn empty() -> Self { + Self { + resolve_label_details: false, + resolve_tags: false, + resolve_detail: false, + resolve_documentation: false, + resolve_filter_text: false, + resolve_text_edit: false, + resolve_command: false, + } + } +} + //FIXME: split the following feature into fine-grained features. // Feature: Magic Completions diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs index 9d77d970071..f371012de3f 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs @@ -37,8 +37,8 @@ use test_fixture::ChangeFixture; use test_utils::assert_eq_text; use crate::{ - resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionItem, - CompletionItemKind, + resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, + CompletionItem, CompletionItemKind, }; /// Lots of basic item definitions @@ -84,6 +84,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { prefer_absolute: false, snippets: Vec::new(), limit: None, + fields_to_resolve: CompletionFieldsToResolve::empty(), }; pub(crate) fn completion_list(ra_fixture: &str) -> String { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs index abf4438a71f..266109765ab 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs @@ -7081,8 +7081,8 @@ This feature has no tracking issue, and is therefore likely internal to the comp "##, }, Lint { - label: "object_safe_for_dispatch", - description: r##"# `object_safe_for_dispatch` + label: "dyn_compatible_for_dispatch", + description: r##"# `dyn_compatible_for_dispatch` The tracking issue for this feature is: [#43561] diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs index 926fae0d317..4c197b45338 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs @@ -614,7 +614,7 @@ fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering { (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (Some(a_name), Some(b_name)) => { - // snake_case < CamelCase < UPPER_SNAKE_CASE + // snake_case < UpperCamelCase < UPPER_SNAKE_CASE let a_text = a_name.as_str().trim_start_matches("r#"); let b_text = b_name.as_str().trim_start_matches("r#"); if a_text.starts_with(char::is_lowercase) 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 bb121f4a80a..19d8a15422e 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 @@ -13,6 +13,7 @@ use crate::{ salsa::{Database, ParallelDatabase, Snapshot}, Cancelled, CrateId, SourceDatabase, SourceRootDatabase, }, + symbol_index::SymbolsDatabase, FxIndexMap, RootDatabase, }; @@ -54,11 +55,13 @@ pub fn parallel_prime_caches( let (progress_sender, progress_receiver) = crossbeam_channel::unbounded(); let (work_sender, work_receiver) = crossbeam_channel::unbounded(); let graph = graph.clone(); + let local_roots = db.local_roots(); let prime_caches_worker = move |db: Snapshot<RootDatabase>| { while let Ok((crate_id, crate_name)) = work_receiver.recv() { progress_sender .send(ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name })?; + // Compute the DefMap and possibly ImportMap let file_id = graph[crate_id].root_file_id; let root_id = db.file_source_root(file_id); if db.source_root(root_id).is_library { @@ -68,6 +71,19 @@ pub fn parallel_prime_caches( db.import_map(crate_id); } + // Compute the symbol search index. + // This primes the cache for `ide_db::symbol_index::world_symbols()`. + // + // We do this for workspace crates only (members of local_roots), because doing it + // for all dependencies could be *very* unnecessarily slow in a large project. + // + // FIXME: We should do it unconditionally if the configuration is set to default to + // searching dependencies (rust-analyzer.workspace.symbol.search.scope), but we + // would need to pipe that configuration information down here. + if local_roots.contains(&root_id) { + db.crate_symbols(crate_id.into()); + } + progress_sender.send(ParallelPrimeCacheWorkerProgress::EndCrate { crate_id })?; } 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 4166b08339b..852ee595be4 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -529,9 +529,13 @@ impl<'a> FindUsages<'a> { }) .into_iter() .flat_map(move |token| { - sema.descend_into_macros_exact_if_in_macro(token) - .into_iter() - .filter_map(|it| it.parent()) + if sema.might_be_inside_macro_call(&token) { + sema.descend_into_macros_exact(token) + } else { + <_>::from([token]) + } + .into_iter() + .filter_map(|it| it.parent()) }) } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 83a1eb44a61..bbdeb7cf085 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -229,10 +229,10 @@ fn foo() { check_diagnostics( r#" struct non_camel_case_name {} - // ^^^^^^^^^^^^^^^^^^^ 💡 warn: Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName` + // ^^^^^^^^^^^^^^^^^^^ 💡 warn: Structure `non_camel_case_name` should have UpperCamelCase name, e.g. `NonCamelCaseName` struct SCREAMING_CASE {} - // ^^^^^^^^^^^^^^ 💡 warn: Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase` + // ^^^^^^^^^^^^^^ 💡 warn: Structure `SCREAMING_CASE` should have UpperCamelCase name, e.g. `ScreamingCase` "#, ); } @@ -261,10 +261,10 @@ struct SomeStruct { SomeField: u8 } check_diagnostics( r#" enum some_enum { Val(u8) } - // ^^^^^^^^^ 💡 warn: Enum `some_enum` should have CamelCase name, e.g. `SomeEnum` + // ^^^^^^^^^ 💡 warn: Enum `some_enum` should have UpperCamelCase name, e.g. `SomeEnum` enum SOME_ENUM {} - // ^^^^^^^^^ 💡 warn: Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum` + // ^^^^^^^^^ 💡 warn: Enum `SOME_ENUM` should have UpperCamelCase name, e.g. `SomeEnum` "#, ); } @@ -283,7 +283,7 @@ enum AABB {} check_diagnostics( r#" enum SomeEnum { SOME_VARIANT(u8) } - // ^^^^^^^^^^^^ 💡 warn: Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant` + // ^^^^^^^^^^^^ 💡 warn: Variant `SOME_VARIANT` should have UpperCamelCase name, e.g. `SomeVariant` "#, ); } @@ -313,7 +313,7 @@ static some_weird_const: u8 = 10; check_diagnostics( r#" struct someStruct; - // ^^^^^^^^^^ 💡 warn: Structure `someStruct` should have CamelCase name, e.g. `SomeStruct` + // ^^^^^^^^^^ 💡 warn: Structure `someStruct` should have UpperCamelCase name, e.g. `SomeStruct` impl someStruct { fn SomeFunc(&self) { @@ -530,11 +530,11 @@ extern { check_diagnostics( r#" trait BAD_TRAIT { - // ^^^^^^^^^ 💡 warn: Trait `BAD_TRAIT` should have CamelCase name, e.g. `BadTrait` + // ^^^^^^^^^ 💡 warn: Trait `BAD_TRAIT` should have UpperCamelCase name, e.g. `BadTrait` const bad_const: u8; // ^^^^^^^^^ 💡 warn: Constant `bad_const` should have UPPER_SNAKE_CASE name, e.g. `BAD_CONST` type BAD_TYPE; - // ^^^^^^^^ 💡 warn: Type alias `BAD_TYPE` should have CamelCase name, e.g. `BadType` + // ^^^^^^^^ 💡 warn: Type alias `BAD_TYPE` should have UpperCamelCase name, e.g. `BadType` fn BAD_FUNCTION(); // ^^^^^^^^^^^^ 💡 warn: Function `BAD_FUNCTION` should have snake_case name, e.g. `bad_function` fn BadFunction(); @@ -552,11 +552,11 @@ trait BAD_TRAIT { check_diagnostics_with_disabled( r#" trait BAD_TRAIT { - // ^^^^^^^^^ 💡 warn: Trait `BAD_TRAIT` should have CamelCase name, e.g. `BadTrait` + // ^^^^^^^^^ 💡 warn: Trait `BAD_TRAIT` should have UpperCamelCase name, e.g. `BadTrait` const bad_const: u8; // ^^^^^^^^^ 💡 warn: Constant `bad_const` should have UPPER_SNAKE_CASE name, e.g. `BAD_CONST` type BAD_TYPE; - // ^^^^^^^^ 💡 warn: Type alias `BAD_TYPE` should have CamelCase name, e.g. `BadType` + // ^^^^^^^^ 💡 warn: Type alias `BAD_TYPE` should have UpperCamelCase name, e.g. `BadType` fn BAD_FUNCTION(BAD_PARAM: u8); // ^^^^^^^^^^^^ 💡 warn: Function `BAD_FUNCTION` should have snake_case name, e.g. `bad_function` // ^^^^^^^^^ 💡 warn: Parameter `BAD_PARAM` should have snake_case name, e.g. `bad_param` @@ -664,7 +664,7 @@ mod CheckNonstandardStyle { mod CheckBadStyle { //^^^^^^^^^^^^^ 💡 error: Module `CheckBadStyle` should have snake_case name, e.g. `check_bad_style` struct fooo; - //^^^^ 💡 error: Structure `fooo` should have CamelCase name, e.g. `Fooo` + //^^^^ 💡 error: Structure `fooo` should have UpperCamelCase name, e.g. `Fooo` } mod F { @@ -676,7 +676,7 @@ mod F { #[deny(non_snake_case, non_camel_case_types)] pub struct some_type { - //^^^^^^^^^ 💡 error: Structure `some_type` should have CamelCase name, e.g. `SomeType` + //^^^^^^^^^ 💡 error: Structure `some_type` should have UpperCamelCase name, e.g. `SomeType` SOME_FIELD: u8, //^^^^^^^^^^ 💡 error: Field `SOME_FIELD` should have snake_case name, e.g. `some_field` SomeField: u16, @@ -693,11 +693,11 @@ pub static SomeStatic: u8 = 10; #[deny(non_snake_case, non_camel_case_types, non_upper_case_globals)] trait BAD_TRAIT { - // ^^^^^^^^^ 💡 error: Trait `BAD_TRAIT` should have CamelCase name, e.g. `BadTrait` + // ^^^^^^^^^ 💡 error: Trait `BAD_TRAIT` should have UpperCamelCase name, e.g. `BadTrait` const bad_const: u8; // ^^^^^^^^^ 💡 error: Constant `bad_const` should have UPPER_SNAKE_CASE name, e.g. `BAD_CONST` type BAD_TYPE; - // ^^^^^^^^ 💡 error: Type alias `BAD_TYPE` should have CamelCase name, e.g. `BadType` + // ^^^^^^^^ 💡 error: Type alias `BAD_TYPE` should have UpperCamelCase name, e.g. `BadType` fn BAD_FUNCTION(BAD_PARAM: u8); // ^^^^^^^^^^^^ 💡 error: Function `BAD_FUNCTION` should have snake_case name, e.g. `bad_function` // ^^^^^^^^^ 💡 error: Parameter `BAD_PARAM` should have snake_case name, e.g. `bad_param` @@ -952,7 +952,7 @@ fn foo() { let FOO; #[allow(non_snake_case)] struct qux; - // ^^^ 💡 warn: Structure `qux` should have CamelCase name, e.g. `Qux` + // ^^^ 💡 warn: Structure `qux` should have UpperCamelCase name, e.g. `Qux` fn BAZ() { // ^^^ 💡 error: Function `BAZ` should have snake_case name, e.g. `baz` diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs index 6ae9dde84be..9dacbd8badf 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -29,7 +29,7 @@ pub(crate) fn goto_declaration( .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?; let range = original_token.text_range(); let info: Vec<NavigationTarget> = sema - .descend_into_macros(original_token) + .descend_into_macros_no_opaque(original_token) .iter() .filter_map(|token| { let parent = token.parent()?; diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 8836166d969..c61b2ba84f2 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -83,7 +83,7 @@ pub(crate) fn goto_definition( } let navs = sema - .descend_into_macros(original_token.clone()) + .descend_into_macros_no_opaque(original_token.clone()) .into_iter() .filter_map(|token| { let parent = token.parent()?; @@ -2661,6 +2661,24 @@ fn foo() { } #[test] + fn label_inside_macro() { + check( + r#" +macro_rules! m { + ($s:stmt) => { $s }; +} + +fn foo() { + 'label: loop { + // ^^^^^^ + m!(continue 'label$0); + } +} +"#, + ); + } + + #[test] fn goto_def_on_return_in_try() { check( r#" diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs index ca04b7bb5a9..c7ebd9a3531 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs @@ -69,7 +69,7 @@ pub(crate) fn goto_type_definition( } let range = token.text_range(); - sema.descend_into_macros(token) + sema.descend_into_macros_no_opaque(token) .into_iter() .filter_map(|token| { let ty = sema diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index 83adf6548a8..01fa316d5fc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -3,9 +3,9 @@ use std::{mem, ops::Not}; use either::Either; use hir::{ - db::ExpandDatabase, Adt, AsAssocItem, AsExternAssocItem, CaptureKind, HasCrate, HasSource, - HirDisplay, Layout, LayoutError, MethodViolationCode, Name, ObjectSafetyViolation, Semantics, - Trait, Type, TypeInfo, + db::ExpandDatabase, Adt, AsAssocItem, AsExternAssocItem, AssocItemContainer, CaptureKind, + DynCompatibilityViolation, HasCrate, HasSource, HirDisplay, Layout, LayoutError, + MethodViolationCode, Name, Semantics, Trait, Type, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, @@ -529,10 +529,10 @@ pub(super) fn definition( _ => None, }; - let object_safety_info = if let Definition::Trait(it) = def { - let mut object_safety_info = String::new(); - render_object_safety(db, &mut object_safety_info, it.object_safety(db)); - Some(object_safety_info) + let dyn_compatibility_info = if let Definition::Trait(it) = def { + let mut dyn_compatibility_info = String::new(); + render_dyn_compatibility(db, &mut dyn_compatibility_info, it.dyn_compatibility(db)); + Some(dyn_compatibility_info) } else { None }; @@ -546,8 +546,8 @@ pub(super) fn definition( desc.push_str(&layout_info); desc.push('\n'); } - if let Some(object_safety_info) = object_safety_info { - desc.push_str(&object_safety_info); + if let Some(dyn_compatibility_info) = dyn_compatibility_info { + desc.push_str(&dyn_compatibility_info); desc.push('\n'); } desc.push_str(&label); @@ -813,7 +813,15 @@ fn definition_mod_path(db: &RootDatabase, def: &Definition, edition: Edition) -> if matches!(def, Definition::GenericParam(_) | Definition::Local(_) | Definition::Label(_)) { return None; } - def.module(db).map(|module| path(db, module, definition_owner_name(db, def, edition), edition)) + let container: Option<Definition> = + def.as_assoc_item(db).and_then(|assoc| match assoc.container(db) { + AssocItemContainer::Trait(trait_) => Some(trait_.into()), + AssocItemContainer::Impl(impl_) => impl_.self_ty(db).as_adt().map(|adt| adt.into()), + }); + container + .unwrap_or(*def) + .module(db) + .map(|module| path(db, module, definition_owner_name(db, def, edition), edition)) } fn markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Markup { @@ -980,24 +988,24 @@ fn keyword_hints( } } -fn render_object_safety( +fn render_dyn_compatibility( db: &RootDatabase, buf: &mut String, - safety: Option<ObjectSafetyViolation>, + safety: Option<DynCompatibilityViolation>, ) { let Some(osv) = safety else { - buf.push_str("// Object Safety: Yes"); + buf.push_str("// Dyn Compatible: Yes"); return; }; - buf.push_str("// Object Safety: No\n// - Reason: "); + buf.push_str("// Dyn Compatible: No\n// - Reason: "); match osv { - ObjectSafetyViolation::SizedSelf => { + DynCompatibilityViolation::SizedSelf => { buf.push_str("has a `Self: Sized` bound"); } - ObjectSafetyViolation::SelfReferential => { + DynCompatibilityViolation::SelfReferential => { buf.push_str("has a bound that references `Self`"); } - ObjectSafetyViolation::Method(func, mvc) => { + DynCompatibilityViolation::Method(func, mvc) => { let name = hir::Function::from(func).name(db); format_to!( buf, @@ -1020,7 +1028,7 @@ fn render_object_safety( }; buf.push_str(desc); } - ObjectSafetyViolation::AssocConst(const_) => { + DynCompatibilityViolation::AssocConst(const_) => { let name = hir::Const::from(const_).name(db); if let Some(name) = name { format_to!(buf, "has an associated constant `{}`", name.as_str()); @@ -1028,11 +1036,11 @@ fn render_object_safety( buf.push_str("has an associated constant"); } } - ObjectSafetyViolation::GAT(alias) => { + DynCompatibilityViolation::GAT(alias) => { let name = hir::TypeAlias::from(alias).name(db); format_to!(buf, "has a generic associated type `{}`", name.as_str()); } - ObjectSafetyViolation::HasNonSafeSuperTrait(super_trait) => { + DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait) => { let name = hir::Trait::from(super_trait).name(db); format_to!(buf, "has a object unsafe supertrait `{}`", name.as_str()); } 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 cca62d2181f..e60be577f79 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -7175,7 +7175,7 @@ impl T$0 for () {} ``` ```rust - // Object Safety: Yes + // Dyn Compatible: Yes trait T {} ``` "#]], @@ -7195,7 +7195,7 @@ impl T$0 for () {} ``` ```rust - // Object Safety: Yes + // Dyn Compatible: Yes trait T {} ``` "#]], @@ -7219,7 +7219,7 @@ impl T$0 for () {} ``` ```rust - // Object Safety: No + // Dyn Compatible: No // - Reason: has a method `func` that is non dispatchable because of: // - missing a receiver trait T { /* … */ } @@ -7245,7 +7245,7 @@ impl T$0 for () {} ``` ```rust - // Object Safety: No + // Dyn Compatible: No // - Reason: has a method `func` that is non dispatchable because of: // - missing a receiver trait T { @@ -7275,7 +7275,7 @@ impl T$0 for () {} ``` ```rust - // Object Safety: No + // Dyn Compatible: No // - Reason: has a method `func` that is non dispatchable because of: // - missing a receiver trait T { @@ -7305,7 +7305,7 @@ impl T$0 for () {} ``` ```rust - // Object Safety: No + // Dyn Compatible: No // - Reason: has a method `func` that is non dispatchable because of: // - missing a receiver trait T { @@ -8962,3 +8962,29 @@ fn test_hover_function_with_pat_param() { "#]], ); } + +#[test] +fn hover_path_inside_block_scope() { + check( + r#" +mod m { + const _: () = { + mod m2 { + const C$0: () = (); + } + }; +} +"#, + expect![[r#" + *C* + + ```rust + test::m::m2 + ``` + + ```rust + const C: () = () + ``` + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 547286c3f4d..c46c4c8ce94 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -119,8 +119,8 @@ pub use ide_assists::{ Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve, }; pub use ide_completion::{ - CallableSnippets, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, - Snippet, SnippetScope, + CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem, + CompletionItemKind, CompletionRelevance, Snippet, SnippetScope, }; pub use ide_db::{ base_db::{Cancelled, CrateGraph, CrateId, FileChange, SourceRoot, SourceRootId}, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index 927fdaa178c..961b2a4c938 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -409,7 +409,8 @@ fn traverse( let mut r = 0; sema.descend_into_macros_breakable( InRealFile::new(file_id, token.clone()), - |tok| { + |tok, _ctx| { + // FIXME: Consider checking ctx transparency for being opaque? let tok = tok.value; let tok_kind = tok.kind(); diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index baa45174236..0e1606a6991 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -265,6 +265,11 @@ impl ProjectFolders { entries.push(manifest.to_owned()); } + for buildfile in ws.buildfiles() { + file_set_roots.push(VfsPath::from(buildfile.to_owned())); + entries.push(buildfile.to_owned()); + } + // In case of detached files we do **not** look for a rust-analyzer.toml. if !matches!(ws.kind, ProjectWorkspaceKind::DetachedFile { .. }) { let ws_root = ws.workspace_root(); @@ -521,7 +526,7 @@ mod tests { #[test] fn test_loading_rust_analyzer() { let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); - let cargo_config = CargoConfig::default(); + let cargo_config = CargoConfig { set_test: true, ..CargoConfig::default() }; let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: false, with_proc_macro_server: ProcMacroServerChoice::None, 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 dceac815e0b..7ea23b4f752 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -187,6 +187,12 @@ impl<'a> Converter<'a> { } rustc_lexer::TokenKind::RawIdent => IDENT, + + rustc_lexer::TokenKind::GuardedStrPrefix => { + err = "Invalid string literal (reserved syntax)"; + ERROR + }, + rustc_lexer::TokenKind::Literal { kind, .. } => { self.extend_literal(token_text.len(), kind); return; diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index 7cc21bcf131..2dc6f0357e3 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -100,6 +100,7 @@ pub struct CargoConfig { pub invocation_strategy: InvocationStrategy, /// Optional path to use instead of `target` when building pub target_dir: Option<Utf8PathBuf>, + pub set_test: bool, } pub type Package = Idx<PackageData>; diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs index 30d1ddb636e..5099697a696 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs @@ -35,6 +35,7 @@ fn load_cargo_with_overrides( rustc: Err(None), cargo_config_extra_env: Default::default(), error: None, + set_test: true, }, cfg_overrides, sysroot: Sysroot::empty(), @@ -242,6 +243,7 @@ fn smoke_test_real_sysroot_cargo() { rustc: Err(None), cargo_config_extra_env: Default::default(), error: None, + set_test: true, }, sysroot, rustc_cfg: Vec::new(), 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 17b40a87cda..71b9b61e205 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -78,6 +78,7 @@ pub enum ProjectWorkspaceKind { rustc: Result<Box<(CargoWorkspace, WorkspaceBuildScripts)>, Option<String>>, /// Environment variables set in the `.cargo/config` file. cargo_config_extra_env: FxHashMap<String, String>, + set_test: bool, }, /// Project workspace was specified using a `rust-project.json` file. Json(ProjectJson), @@ -98,6 +99,7 @@ pub enum ProjectWorkspaceKind { cargo: Option<(CargoWorkspace, WorkspaceBuildScripts, Option<Arc<anyhow::Error>>)>, /// Environment variables set in the `.cargo/config` file. cargo_config_extra_env: FxHashMap<String, String>, + set_test: bool, }, } @@ -112,6 +114,7 @@ impl fmt::Debug for ProjectWorkspace { build_scripts, rustc, cargo_config_extra_env, + set_test, } => f .debug_struct("Cargo") .field("root", &cargo.workspace_root().file_name()) @@ -126,6 +129,7 @@ impl fmt::Debug for ProjectWorkspace { .field("toolchain", &toolchain) .field("data_layout", &target_layout) .field("cargo_config_extra_env", &cargo_config_extra_env) + .field("set_test", set_test) .field("build_scripts", &build_scripts.error().unwrap_or("ok")) .finish(), ProjectWorkspaceKind::Json(project) => { @@ -137,12 +141,14 @@ impl fmt::Debug for ProjectWorkspace { .field("toolchain", &toolchain) .field("data_layout", &target_layout) .field("n_cfg_overrides", &cfg_overrides.len()); + debug_struct.finish() } ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, cargo_config_extra_env, + set_test, } => f .debug_struct("DetachedFiles") .field("file", &file) @@ -154,6 +160,7 @@ impl fmt::Debug for ProjectWorkspace { .field("data_layout", &target_layout) .field("n_cfg_overrides", &cfg_overrides.len()) .field("cargo_config_extra_env", &cargo_config_extra_env) + .field("set_test", set_test) .finish(), } } @@ -329,6 +336,7 @@ impl ProjectWorkspace { rustc, cargo_config_extra_env, error: error.map(Arc::new), + set_test: config.set_test, }, sysroot, rustc_cfg, @@ -423,6 +431,7 @@ impl ProjectWorkspace { file: detached_file.to_owned(), cargo: cargo_script, cargo_config_extra_env, + set_test: config.set_test, }, sysroot, rustc_cfg, @@ -539,6 +548,17 @@ impl ProjectWorkspace { } } + pub fn buildfiles(&self) -> Vec<AbsPathBuf> { + match &self.kind { + ProjectWorkspaceKind::Json(project) => project + .crates() + .filter_map(|(_, krate)| krate.build.as_ref().map(|build| build.build_file.clone())) + .map(AbsPathBuf::assert) + .collect(), + _ => vec![], + } + } + pub fn find_sysroot_proc_macro_srv(&self) -> anyhow::Result<AbsPathBuf> { self.sysroot.discover_proc_macro_srv() } @@ -598,6 +618,7 @@ impl ProjectWorkspace { build_scripts, cargo_config_extra_env: _, error: _, + set_test: _, } => { cargo .packages() @@ -739,6 +760,7 @@ impl ProjectWorkspace { build_scripts, cargo_config_extra_env: _, error: _, + set_test, } => ( cargo_to_crate_graph( load, @@ -748,10 +770,11 @@ impl ProjectWorkspace { rustc_cfg.clone(), cfg_overrides, build_scripts, + *set_test, ), sysroot, ), - ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, .. } => ( + ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, set_test, .. } => ( if let Some((cargo, build_scripts, _)) = cargo_script { cargo_to_crate_graph( &mut |path| load(path), @@ -761,6 +784,7 @@ impl ProjectWorkspace { rustc_cfg.clone(), cfg_overrides, build_scripts, + *set_test, ) } else { detached_file_to_crate_graph( @@ -769,6 +793,7 @@ impl ProjectWorkspace { file, sysroot, cfg_overrides, + *set_test, ) }, sysroot, @@ -802,6 +827,7 @@ impl ProjectWorkspace { cargo_config_extra_env, build_scripts: _, error: _, + set_test: _, }, ProjectWorkspaceKind::Cargo { cargo: o_cargo, @@ -809,6 +835,7 @@ impl ProjectWorkspace { cargo_config_extra_env: o_cargo_config_extra_env, build_scripts: _, error: _, + set_test: _, }, ) => { cargo == o_cargo @@ -823,11 +850,13 @@ impl ProjectWorkspace { file, cargo: Some((cargo_script, _, _)), cargo_config_extra_env, + set_test: _, }, ProjectWorkspaceKind::DetachedFile { file: o_file, cargo: Some((o_cargo_script, _, _)), cargo_config_extra_env: o_cargo_config_extra_env, + set_test: _, }, ) => { file == o_file @@ -976,6 +1005,7 @@ fn cargo_to_crate_graph( rustc_cfg: Vec<CfgAtom>, override_cfg: &CfgOverrides, build_scripts: &WorkspaceBuildScripts, + set_test: bool, ) -> (CrateGraph, ProcMacroPaths) { let _p = tracing::info_span!("cargo_to_crate_graph").entered(); let mut res = (CrateGraph::default(), ProcMacroPaths::default()); @@ -1000,8 +1030,10 @@ fn cargo_to_crate_graph( let mut cfg_options = cfg_options.clone(); if cargo[pkg].is_local { - // Add test cfg for local crates - cfg_options.insert_atom(sym::test.clone()); + if set_test { + // Add test cfg for local crates + cfg_options.insert_atom(sym::test.clone()); + } cfg_options.insert_atom(sym::rust_analyzer.clone()); } @@ -1162,6 +1194,7 @@ fn detached_file_to_crate_graph( detached_file: &ManifestPath, sysroot: &Sysroot, override_cfg: &CfgOverrides, + set_test: bool, ) -> (CrateGraph, ProcMacroPaths) { let _p = tracing::info_span!("detached_file_to_crate_graph").entered(); let mut crate_graph = CrateGraph::default(); @@ -1169,7 +1202,9 @@ fn detached_file_to_crate_graph( sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load); let mut cfg_options = CfgOptions::from_iter(rustc_cfg); - cfg_options.insert_atom(sym::test.clone()); + if set_test { + cfg_options.insert_atom(sym::test.clone()); + } cfg_options.insert_atom(sym::rust_analyzer.clone()); override_cfg.apply(&mut cfg_options, ""); let cfg_options = Arc::new(cfg_options); @@ -1415,6 +1450,7 @@ fn sysroot_to_crate_graph( ..Default::default() }, &WorkspaceBuildScripts::default(), + false, ); let mut pub_deps = vec![]; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs index 41b42573f08..ecc8333503e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs @@ -137,6 +137,7 @@ fn setup_logging(log_file_flag: Option<PathBuf>) -> anyhow::Result<()> { filter: env::var("RA_LOG").ok().unwrap_or_else(|| "error".to_owned()), chalk_filter: env::var("CHALK_DEBUG").ok(), profile_filter: env::var("RA_PROFILE").ok(), + json_profile_filter: std::env::var("RA_PROFILE_JSON").ok(), } .init()?; 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 e899e0e8eea..4844c514ae9 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 @@ -65,6 +65,7 @@ impl flags::AnalysisStats { false => Some(RustLibSource::Discover), }, all_targets: true, + set_test: true, ..Default::default() }; let no_progress = &|_| (); @@ -81,7 +82,13 @@ impl flags::AnalysisStats { with_proc_macro_server: if self.disable_proc_macros { ProcMacroServerChoice::None } else { - ProcMacroServerChoice::Sysroot + match self.proc_macro_srv { + Some(ref path) => { + let path = vfs::AbsPathBuf::assert_utf8(path.to_owned()); + ProcMacroServerChoice::Explicit(path) + } + None => ProcMacroServerChoice::Sysroot, + } }, prefill_caches: false, }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs index 73e71658d17..60d621b214a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs @@ -76,6 +76,8 @@ xflags::xflags! { optional --disable-build-scripts /// Don't use expand proc macros. optional --disable-proc-macros + /// Run the proc-macro-srv binary at the specified path. + optional --proc-macro-srv path: PathBuf /// Skip body lowering. optional --skip-lowering /// Skip type inference. @@ -120,7 +122,7 @@ xflags::xflags! { optional --disable-build-scripts /// Don't use expand proc macros. optional --disable-proc-macros - /// Run a custom proc-macro-srv binary. + /// Run the proc-macro-srv binary at the specified path. optional --proc-macro-srv path: PathBuf } @@ -133,7 +135,7 @@ xflags::xflags! { optional --disable-build-scripts /// Don't use expand proc macros. optional --disable-proc-macros - /// Run a custom proc-macro-srv binary. + /// Run the proc-macro-srv binary at the specified path. optional --proc-macro-srv path: PathBuf } @@ -233,6 +235,7 @@ pub struct AnalysisStats { pub no_sysroot: bool, pub disable_build_scripts: bool, pub disable_proc_macros: bool, + pub proc_macro_srv: Option<PathBuf>, pub skip_lowering: bool, pub skip_inference: bool, pub skip_mir_stats: bool, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs index e4263a3f667..ca8acf57bff 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs @@ -277,6 +277,7 @@ impl flags::Lsif { let cargo_config = &CargoConfig { sysroot: Some(RustLibSource::Discover), all_targets: true, + set_test: true, ..Default::default() }; let no_progress = &|_| (); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs index f90ebcfdb2e..11534bbeba9 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs @@ -16,6 +16,7 @@ impl flags::RunTests { let cargo_config = CargoConfig { sysroot: Some(RustLibSource::Discover), all_targets: true, + set_test: true, ..Default::default() }; let load_cargo_config = LoadCargoConfig { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs index 730f3c08abb..30378db0b38 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -70,6 +70,7 @@ impl Tester { let cargo_config = CargoConfig { sysroot: Some(RustLibSource::Discover), all_targets: true, + set_test: true, ..Default::default() }; @@ -85,6 +86,7 @@ impl Tester { file: ManifestPath::try_from(tmp_file).unwrap(), cargo: None, cargo_config_extra_env: Default::default(), + set_test: true, }, sysroot, rustc_cfg: vec![], diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs index e9198977dea..ff009e69547 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs @@ -24,11 +24,6 @@ impl flags::Scip { let now = Instant::now(); let no_progress = &|s| (eprintln!("rust-analyzer: Loading {s}")); - let load_cargo_config = LoadCargoConfig { - load_out_dirs_from_check: true, - with_proc_macro_server: ProcMacroServerChoice::Sysroot, - prefill_caches: true, - }; let root = vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(&self.path)).normalize(); @@ -51,6 +46,11 @@ impl flags::Scip { // FIXME @alibektas : What happens to errors without logging? error!(?error_sink, "Config Error(s)"); } + let load_cargo_config = LoadCargoConfig { + load_out_dirs_from_check: true, + with_proc_macro_server: ProcMacroServerChoice::Sysroot, + prefill_caches: true, + }; let cargo_config = config.cargo(None); let (db, vfs, _) = load_workspace_at( root.as_path().as_ref(), diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs index bdca800a0d6..c03688e8009 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs @@ -13,6 +13,7 @@ impl flags::Ssr { let cargo_config = CargoConfig { sysroot: Some(RustLibSource::Discover), all_targets: true, + set_test: true, ..Default::default() }; let load_cargo_config = LoadCargoConfig { @@ -50,7 +51,8 @@ impl flags::Search { pub fn run(self) -> anyhow::Result<()> { use ide_db::base_db::SourceRootDatabase; use ide_db::symbol_index::SymbolsDatabase; - let cargo_config = CargoConfig::default(); + let cargo_config = + CargoConfig { all_targets: true, set_test: true, ..CargoConfig::default() }; let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, 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 4cc60695fe6..ef2e542cf22 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,10 @@ use std::{ use cfg::{CfgAtom, CfgDiff}; use hir::Symbol; use ide::{ - AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, - GenericParameterHints, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, - InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, - MemoryLayoutHoverRenderKind, Snippet, SnippetScope, SourceRootId, + AssistConfig, 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}, @@ -574,6 +574,9 @@ config_data! { /// set to a path relative to the workspace to use that path. cargo_targetDir | rust_analyzerTargetDir: Option<TargetDirectory> = None, + /// Set `cfg(test)` for local crates. Defaults to true. + cfg_setTest: bool = true, + /// Run the check command for diagnostics on save. checkOnSave | checkOnSave_enable: bool = true, @@ -695,7 +698,6 @@ config_data! { workspace_symbol_search_limit: usize = 128, /// Workspace symbol search scope. workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = WorkspaceSymbolSearchScopeDef::Workspace, - } } @@ -1391,6 +1393,7 @@ impl Config { } pub fn completion(&self, source_root: Option<SourceRootId>) -> CompletionConfig { + let client_capability_fields = self.completion_resolve_support_properties(); CompletionConfig { enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(), enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned() @@ -1415,6 +1418,15 @@ impl Config { limit: self.completion_limit(source_root).to_owned(), enable_term_search: self.completion_termSearch_enable(source_root).to_owned(), term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64, + fields_to_resolve: CompletionFieldsToResolve { + resolve_label_details: client_capability_fields.contains("labelDetails"), + resolve_tags: client_capability_fields.contains("tags"), + resolve_detail: client_capability_fields.contains("detail"), + resolve_documentation: client_capability_fields.contains("documentation"), + resolve_filter_text: client_capability_fields.contains("filterText"), + resolve_text_edit: client_capability_fields.contains("textEdit"), + resolve_command: client_capability_fields.contains("command"), + }, } } @@ -1859,9 +1871,14 @@ impl Config { extra_args: self.cargo_extraArgs(source_root).clone(), extra_env: self.cargo_extraEnv(source_root).clone(), target_dir: self.target_dir_from_config(source_root), + set_test: *self.cfg_setTest(source_root), } } + pub fn cfg_set_test(&self, source_root: Option<SourceRootId>) -> bool { + *self.cfg_setTest(source_root) + } + pub(crate) fn completion_snippets_default() -> FxHashMap<String, SnippetDef> { serde_json::from_str( r#"{ diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs index 7e9162eee6e..96b164228ef 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs @@ -7,6 +7,7 @@ use paths::{AbsPathBuf, Utf8Path, Utf8PathBuf}; use project_model::ProjectJsonData; use serde::{Deserialize, Serialize}; use serde_json::Value; +use tracing::{info_span, span::EnteredSpan}; use crate::command::{CommandHandle, ParseFromLine}; @@ -60,7 +61,10 @@ impl DiscoverCommand { let mut cmd = Command::new(command); cmd.args(args); - Ok(DiscoverHandle { _handle: CommandHandle::spawn(cmd, self.sender.clone())? }) + Ok(DiscoverHandle { + _handle: CommandHandle::spawn(cmd, self.sender.clone())?, + span: info_span!("discover_command").entered(), + }) } } @@ -68,6 +72,8 @@ impl DiscoverCommand { #[derive(Debug)] pub(crate) struct DiscoverHandle { _handle: CommandHandle<DiscoverProjectMessage>, + #[allow(dead_code)] // not accessed, but used to log on drop. + span: EnteredSpan, } /// An enum containing either progress messages, an error, 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 89487aa673b..c3142c9cfca 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 @@ -46,6 +46,11 @@ pub(crate) struct FetchWorkspaceRequest { pub(crate) force_crate_graph_reload: bool, } +pub(crate) struct FetchWorkspaceResponse { + pub(crate) workspaces: Vec<anyhow::Result<ProjectWorkspace>>, + pub(crate) force_crate_graph_reload: bool, +} + // Enforces drop order pub(crate) struct Handle<H, C> { pub(crate) handle: H, @@ -111,6 +116,9 @@ pub(crate) struct GlobalState { pub(crate) vfs_config_version: u32, pub(crate) vfs_progress_config_version: u32, pub(crate) vfs_done: bool, + // used to track how long VFS loading takes. this can't be on `vfs::loader::Handle`, + // as that handle's lifetime is the same as `GlobalState` itself. + pub(crate) vfs_span: Option<tracing::span::EnteredSpan>, pub(crate) wants_to_switch: Option<Cause>, /// `workspaces` field stores the data we actually use, while the `OpQueue` @@ -143,8 +151,7 @@ pub(crate) struct GlobalState { pub(crate) detached_files: FxHashSet<ManifestPath>, // op queues - pub(crate) fetch_workspaces_queue: - OpQueue<FetchWorkspaceRequest, Option<(Vec<anyhow::Result<ProjectWorkspace>>, bool)>>, + pub(crate) fetch_workspaces_queue: OpQueue<FetchWorkspaceRequest, FetchWorkspaceResponse>, pub(crate) fetch_build_data_queue: OpQueue<(), (Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)>, pub(crate) fetch_proc_macros_queue: OpQueue<Vec<ProcMacroPaths>, bool>, @@ -253,6 +260,7 @@ impl GlobalState { vfs: Arc::new(RwLock::new((vfs::Vfs::default(), IntMap::default()))), vfs_config_version: 0, vfs_progress_config_version: 0, + vfs_span: None, vfs_done: true, wants_to_switch: None, @@ -498,7 +506,7 @@ impl GlobalState { mem_docs: self.mem_docs.clone(), semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache), proc_macros_loaded: !self.config.expand_proc_macros() - || *self.fetch_proc_macros_queue.last_op_result(), + || self.fetch_proc_macros_queue.last_op_result().copied().unwrap_or(false), flycheck: self.flycheck.clone(), } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs index 49b1ba32a79..bb03eb3c89b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs @@ -380,7 +380,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { if id == flycheck.id() { updated = true; match package.filter(|_| { - !world.config.flycheck_workspace(source_root_id) || target.is_some() + !world.config.flycheck_workspace(source_root_id) && target.is_some() }) { Some(package) => flycheck .restart_for_package(package, target.clone().map(TupleExt::head)), 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 bcbd970a0d2..9773d8dbce0 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 @@ -10,9 +10,9 @@ use std::{ use anyhow::Context; use ide::{ - AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange, - HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory, - Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit, + AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve, + FilePosition, FileRange, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, + RangeInfo, ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit, }; use ide_db::SymbolKind; use itertools::Itertools; @@ -1019,9 +1019,11 @@ pub(crate) fn handle_completion( let items = to_proto::completion_items( &snap.config, + &completion_config.fields_to_resolve, &line_index, snap.file_version(position.file_id), text_document_position, + completion_trigger_character, items, ); @@ -1054,36 +1056,70 @@ pub(crate) fn handle_completion_resolve( }; let source_root = snap.analysis.source_root_id(file_id)?; - let additional_edits = snap - .analysis - .resolve_completion_edits( - &snap.config.completion(Some(source_root)), - FilePosition { file_id, offset }, - resolve_data - .imports - .into_iter() - .map(|import| (import.full_import_path, import.imported_name)), - )? - .into_iter() - .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel))) - .collect::<Vec<_>>(); + let mut forced_resolve_completions_config = snap.config.completion(Some(source_root)); + forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty(); - if !all_edits_are_disjoint(&original_completion, &additional_edits) { - return Err(LspError::new( - ErrorCode::InternalError as i32, - "Import edit overlaps with the original completion edits, this is not LSP-compliant" - .into(), - ) - .into()); - } + let position = FilePosition { file_id, offset }; + let Some(resolved_completions) = snap.analysis.completions( + &forced_resolve_completions_config, + position, + resolve_data.trigger_character, + )? + else { + return Ok(original_completion); + }; + let resolved_completions = to_proto::completion_items( + &snap.config, + &forced_resolve_completions_config.fields_to_resolve, + &line_index, + snap.file_version(position.file_id), + resolve_data.position, + resolve_data.trigger_character, + resolved_completions, + ); + let Some(mut resolved_completion) = resolved_completions.into_iter().find(|completion| { + completion.label == original_completion.label + && completion.kind == original_completion.kind + && completion.deprecated == original_completion.deprecated + && completion.preselect == original_completion.preselect + && completion.sort_text == original_completion.sort_text + }) else { + return Ok(original_completion); + }; - if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() { - original_additional_edits.extend(additional_edits) - } else { - original_completion.additional_text_edits = Some(additional_edits); + if !resolve_data.imports.is_empty() { + let additional_edits = snap + .analysis + .resolve_completion_edits( + &forced_resolve_completions_config, + position, + resolve_data + .imports + .into_iter() + .map(|import| (import.full_import_path, import.imported_name)), + )? + .into_iter() + .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel))) + .collect::<Vec<_>>(); + + if !all_edits_are_disjoint(&resolved_completion, &additional_edits) { + return Err(LspError::new( + ErrorCode::InternalError as i32, + "Import edit overlaps with the original completion edits, this is not LSP-compliant" + .into(), + ) + .into()); + } + + if let Some(original_additional_edits) = resolved_completion.additional_text_edits.as_mut() + { + original_additional_edits.extend(additional_edits) + } else { + resolved_completion.additional_text_edits = Some(additional_edits); + } } - Ok(original_completion) + Ok(resolved_completion) } pub(crate) fn handle_folding_range( diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs index 118469df730..8946c7acb93 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -12,7 +12,8 @@ use hir::ChangeWithProcMacros; use ide::{ - AnalysisHost, CallableSnippets, CompletionConfig, DiagnosticsConfig, FilePosition, TextSize, + AnalysisHost, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig, + FilePosition, TextSize, }; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig}, @@ -36,6 +37,8 @@ fn integrated_highlighting_benchmark() { let cargo_config = CargoConfig { sysroot: Some(project_model::RustLibSource::Discover), + all_targets: true, + set_test: true, ..CargoConfig::default() }; let load_cargo_config = LoadCargoConfig { @@ -102,6 +105,8 @@ fn integrated_completion_benchmark() { let cargo_config = CargoConfig { sysroot: Some(project_model::RustLibSource::Discover), + all_targets: true, + set_test: true, ..CargoConfig::default() }; let load_cargo_config = LoadCargoConfig { @@ -168,6 +173,7 @@ fn integrated_completion_benchmark() { snippets: Vec::new(), limit: None, add_semicolon_to_unit: true, + fields_to_resolve: CompletionFieldsToResolve::empty(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -215,6 +221,7 @@ fn integrated_completion_benchmark() { snippets: Vec::new(), limit: None, add_semicolon_to_unit: true, + fields_to_resolve: CompletionFieldsToResolve::empty(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -260,6 +267,7 @@ fn integrated_completion_benchmark() { snippets: Vec::new(), limit: None, add_semicolon_to_unit: true, + fields_to_resolve: CompletionFieldsToResolve::empty(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -279,6 +287,8 @@ fn integrated_diagnostics_benchmark() { let cargo_config = CargoConfig { sysroot: Some(project_model::RustLibSource::Discover), + all_targets: true, + set_test: true, ..CargoConfig::default() }; let load_cargo_config = LoadCargoConfig { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs index 714991e8116..234204695cb 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs @@ -34,6 +34,7 @@ mod handlers { pub mod tracing { pub mod config; + pub mod json; pub use config::Config; pub mod hprof; } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs index 9610808c27e..3b19284f241 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs @@ -448,7 +448,7 @@ impl ClientCapabilities { .unwrap_or_default() } - pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<String> { + pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<&str> { self.0 .text_document .as_ref() @@ -457,8 +457,22 @@ impl ClientCapabilities { .map(|inlay_resolve| inlay_resolve.properties.iter()) .into_iter() .flatten() - .cloned() - .collect::<FxHashSet<_>>() + .map(|s| s.as_str()) + .collect() + } + + pub fn completion_resolve_support_properties(&self) -> FxHashSet<&str> { + self.0 + .text_document + .as_ref() + .and_then(|text| text.completion.as_ref()) + .and_then(|completion_caps| completion_caps.completion_item.as_ref()) + .and_then(|completion_item_caps| completion_item_caps.resolve_support.as_ref()) + .map(|resolve_support| resolve_support.properties.iter()) + .into_iter() + .flatten() + .map(|s| s.as_str()) + .collect() } pub fn hover_markdown_support(&self) -> bool { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs index 618481bbc66..8039f0644ee 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs @@ -825,6 +825,7 @@ pub struct CompletionResolveData { pub position: lsp_types::TextDocumentPositionParams, pub imports: Vec<CompletionImport>, pub version: Option<i32>, + pub trigger_character: Option<char>, } #[derive(Debug, Serialize, Deserialize)] 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 4902c9f88c1..375b7428c2d 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 @@ -6,9 +6,9 @@ use std::{ }; use ide::{ - Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem, - CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, - Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, + Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionFieldsToResolve, + CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, + FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, @@ -227,9 +227,11 @@ pub(crate) fn snippet_text_edit_vec( pub(crate) fn completion_items( config: &Config, + fields_to_resolve: &CompletionFieldsToResolve, line_index: &LineIndex, version: Option<i32>, tdpp: lsp_types::TextDocumentPositionParams, + completion_trigger_character: Option<char>, mut items: Vec<CompletionItem>, ) -> Vec<lsp_types::CompletionItem> { if config.completion_hide_deprecated() { @@ -239,7 +241,17 @@ pub(crate) fn completion_items( let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default(); let mut res = Vec::with_capacity(items.len()); for item in items { - completion_item(&mut res, config, line_index, version, &tdpp, max_relevance, item); + completion_item( + &mut res, + config, + fields_to_resolve, + line_index, + version, + &tdpp, + max_relevance, + completion_trigger_character, + item, + ); } if let Some(limit) = config.completion(None).limit { @@ -253,21 +265,33 @@ pub(crate) fn completion_items( fn completion_item( acc: &mut Vec<lsp_types::CompletionItem>, config: &Config, + fields_to_resolve: &CompletionFieldsToResolve, line_index: &LineIndex, version: Option<i32>, tdpp: &lsp_types::TextDocumentPositionParams, max_relevance: u32, + completion_trigger_character: Option<char>, item: CompletionItem, ) { let insert_replace_support = config.insert_replace_support().then_some(tdpp.position); let ref_match = item.ref_match(); - let lookup = item.lookup().to_owned(); let mut additional_text_edits = Vec::new(); + let mut something_to_resolve = false; - // LSP does not allow arbitrary edits in completion, so we have to do a - // non-trivial mapping here. - let text_edit = { + let filter_text = if fields_to_resolve.resolve_filter_text { + 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; + None + } else { + // LSP does not allow arbitrary edits in completion, so we have to do a + // non-trivial mapping here. let mut text_edit = None; let source_range = item.source_range; for indel in item.text_edit { @@ -290,25 +314,49 @@ fn completion_item( additional_text_edits.push(text_edit); } } - text_edit.unwrap() + Some(text_edit.unwrap()) }; let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET); - let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]); + let tags = if fields_to_resolve.resolve_tags { + 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 { - Some(command::trigger_parameter_hints()) + if fields_to_resolve.resolve_command { + something_to_resolve = true; + None + } else { + Some(command::trigger_parameter_hints()) + } + } else { + None + }; + + let detail = if fields_to_resolve.resolve_detail { + 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(); None + } else { + item.documentation.map(documentation) }; let mut lsp_item = lsp_types::CompletionItem { label: item.label.to_string(), - detail: item.detail, - filter_text: Some(lookup), + detail, + filter_text, kind: Some(completion_item_kind(item.kind)), - text_edit: Some(text_edit), + text_edit, additional_text_edits: Some(additional_text_edits), - documentation: item.documentation.map(documentation), + documentation, deprecated: Some(item.deprecated), tags, command, @@ -317,29 +365,40 @@ fn completion_item( }; if config.completion_label_details_support() { - lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails { - detail: item.label_detail.as_ref().map(ToString::to_string), - description: lsp_item.detail.clone(), - }); + if fields_to_resolve.resolve_label_details { + something_to_resolve = true; + } else { + lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails { + detail: item.label_detail.as_ref().map(ToString::to_string), + description: lsp_item.detail.clone(), + }); + } } else if let Some(label_detail) = item.label_detail { lsp_item.label.push_str(label_detail.as_str()); } set_score(&mut lsp_item, max_relevance, item.relevance); - if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() { - let imports = item - .import_to_add - .into_iter() - .map(|(import_path, import_name)| lsp_ext::CompletionImport { - full_import_path: import_path, - imported_name: import_name, - }) - .collect::<Vec<_>>(); - if !imports.is_empty() { - let data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports, version }; - lsp_item.data = Some(to_value(data).unwrap()); - } + let imports = + if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() { + item.import_to_add + .into_iter() + .map(|(import_path, import_name)| lsp_ext::CompletionImport { + full_import_path: import_path, + imported_name: import_name, + }) + .collect() + } else { + Vec::new() + }; + if something_to_resolve || !imports.is_empty() { + let data = lsp_ext::CompletionResolveData { + position: tdpp.clone(), + imports, + version, + trigger_character: completion_trigger_character, + }; + lsp_item.data = Some(to_value(data).unwrap()); } if let Some((label, indel, relevance)) = ref_match { 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 83559230257..ef289720568 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 @@ -22,7 +22,9 @@ use crate::{ diagnostics::{fetch_native_diagnostics, DiagnosticsGeneration, NativeDiagnosticsFetchKind}, discover::{DiscoverArgument, DiscoverCommand, DiscoverProjectMessage}, flycheck::{self, FlycheckMessage}, - global_state::{file_id_to_url, url_to_file_id, FetchWorkspaceRequest, GlobalState}, + global_state::{ + file_id_to_url, url_to_file_id, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState, + }, hack_recover_crate_name, handlers::dispatch::{NotificationDispatcher, RequestDispatcher}, lsp::{ @@ -695,9 +697,9 @@ impl GlobalState { let (state, msg) = match progress { ProjectWorkspaceProgress::Begin => (Progress::Begin, None), ProjectWorkspaceProgress::Report(msg) => (Progress::Report, Some(msg)), - ProjectWorkspaceProgress::End(workspaces, force_reload_crate_graph) => { - self.fetch_workspaces_queue - .op_completed(Some((workspaces, force_reload_crate_graph))); + ProjectWorkspaceProgress::End(workspaces, force_crate_graph_reload) => { + let resp = FetchWorkspaceResponse { workspaces, force_crate_graph_reload }; + self.fetch_workspaces_queue.op_completed(resp); if let Err(e) = self.fetch_workspace_error() { error!("FetchWorkspaceError: {e}"); } @@ -794,13 +796,20 @@ impl GlobalState { } } vfs::loader::Message::Progress { n_total, n_done, dir, config_version } => { - let _p = tracing::info_span!("GlobalState::handle_vfs_mgs/progress").entered(); + let _p = span!(Level::INFO, "GlobalState::handle_vfs_mgs/progress").entered(); always!(config_version <= self.vfs_config_version); let (n_done, state) = match n_done { - LoadingProgress::Started => (0, Progress::Begin), + LoadingProgress::Started => { + self.vfs_span = + Some(span!(Level::INFO, "vfs_load", total = n_total).entered()); + (0, Progress::Begin) + } LoadingProgress::Progress(n_done) => (n_done.min(n_total), Progress::Report), - LoadingProgress::Finished => (n_total, Progress::End), + LoadingProgress::Finished => { + self.vfs_span = None; + (n_total, Progress::End) + } }; self.vfs_progress_config_version = config_version; @@ -881,6 +890,7 @@ impl GlobalState { .expect("No title could be found; this is a bug"); match message { DiscoverProjectMessage::Finished { project, buildfile } => { + self.discover_handle = None; self.report_progress(&title, Progress::End, None, None, None); self.discover_workspace_queue.op_completed(()); @@ -892,6 +902,7 @@ impl GlobalState { self.report_progress(&title, Progress::Report, Some(message), None, None) } DiscoverProjectMessage::Error { error, source } => { + self.discover_handle = None; let message = format!("Project discovery failed: {error}"); self.discover_workspace_queue.op_completed(()); self.show_and_log_error(message.clone(), source); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/op_queue.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/op_queue.rs index 5c4c858e150..123f20605ab 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/op_queue.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/op_queue.rs @@ -27,12 +27,12 @@ pub(crate) type Cause = String; pub(crate) struct OpQueue<Args = (), Output = ()> { op_requested: Option<(Cause, Args)>, op_in_progress: bool, - last_op_result: Output, + last_op_result: Option<Output>, } -impl<Args, Output: Default> Default for OpQueue<Args, Output> { +impl<Args, Output> Default for OpQueue<Args, Output> { fn default() -> Self { - Self { op_requested: None, op_in_progress: false, last_op_result: Default::default() } + Self { op_requested: None, op_in_progress: false, last_op_result: None } } } @@ -56,12 +56,12 @@ impl<Args, Output> OpQueue<Args, Output> { pub(crate) fn op_completed(&mut self, result: Output) { assert!(self.op_in_progress); self.op_in_progress = false; - self.last_op_result = result; + self.last_op_result = Some(result); } /// Get the result of the last operation. - pub(crate) fn last_op_result(&self) -> &Output { - &self.last_op_result + pub(crate) fn last_op_result(&self) -> Option<&Output> { + self.last_op_result.as_ref() } // Is there an operation that has started, but hasn't yet finished? 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 f6765715c5a..60ee0295a3a 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,7 @@ use vfs::{AbsPath, AbsPathBuf, ChangeKind}; use crate::{ config::{Config, FilesWatcher, LinkedProject}, flycheck::{FlycheckConfig, FlycheckHandle}, - global_state::{FetchWorkspaceRequest, GlobalState}, + global_state::{FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState}, lsp_ext, main_loop::{DiscoverProjectParam, Task}, op_queue::Cause, @@ -448,15 +448,15 @@ impl GlobalState { let _p = tracing::info_span!("GlobalState::switch_workspaces").entered(); tracing::info!(%cause, "will switch workspaces"); - let Some((workspaces, force_reload_crate_graph)) = + let Some(FetchWorkspaceResponse { workspaces, force_crate_graph_reload }) = self.fetch_workspaces_queue.last_op_result() else { return; }; - info!(%cause, ?force_reload_crate_graph); + info!(%cause, ?force_crate_graph_reload); if self.fetch_workspace_error().is_err() && !self.workspaces.is_empty() { - if *force_reload_crate_graph { + if *force_crate_graph_reload { self.recreate_crate_graph(cause); } // It only makes sense to switch to a partially broken workspace @@ -474,8 +474,12 @@ impl GlobalState { .all(|(l, r)| l.eq_ignore_build_data(r)); if same_workspaces { - let (workspaces, build_scripts) = self.fetch_build_data_queue.last_op_result(); - if Arc::ptr_eq(workspaces, &self.workspaces) { + let (workspaces, build_scripts) = match self.fetch_build_data_queue.last_op_result() { + Some((workspaces, build_scripts)) => (workspaces.clone(), build_scripts.as_slice()), + None => (Default::default(), Default::default()), + }; + + if Arc::ptr_eq(&workspaces, &self.workspaces) { info!("set build scripts to workspaces"); let workspaces = workspaces @@ -492,7 +496,7 @@ impl GlobalState { self.workspaces = Arc::new(workspaces); } else { info!("build scripts do not match the version of the active workspace"); - if *force_reload_crate_graph { + if *force_crate_graph_reload { self.recreate_crate_graph(cause); } @@ -739,22 +743,18 @@ impl GlobalState { pub(super) fn fetch_workspace_error(&self) -> Result<(), String> { let mut buf = String::new(); - let Some((last_op_result, _)) = self.fetch_workspaces_queue.last_op_result() else { + let Some(FetchWorkspaceResponse { workspaces, .. }) = + self.fetch_workspaces_queue.last_op_result() + else { return Ok(()); }; - if !self.discover_workspace_queue.op_in_progress() { - if last_op_result.is_empty() { - stdx::format_to!(buf, "rust-analyzer failed to discover workspace"); - } else { - for ws in last_op_result { - if let Err(err) = ws { - stdx::format_to!( - buf, - "rust-analyzer failed to load workspace: {:#}\n", - err - ); - } + if workspaces.is_empty() && self.config.discover_workspace_config().is_none() { + stdx::format_to!(buf, "rust-analyzer failed to fetch workspace"); + } else { + for ws in workspaces { + if let Err(err) = ws { + stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err); } } } @@ -769,7 +769,11 @@ impl GlobalState { pub(super) fn fetch_build_data_error(&self) -> Result<(), String> { let mut buf = String::new(); - for ws in &self.fetch_build_data_queue.last_op_result().1 { + let Some((_, ws)) = &self.fetch_build_data_queue.last_op_result() else { + return Ok(()); + }; + + for ws in ws { match ws { Ok(data) => { if let Some(stderr) = data.error() { 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 f330754f19a..b73f6e77514 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 @@ -1,17 +1,20 @@ //! Simple logger that logs either to stderr or to a file, using `tracing_subscriber` //! filter syntax and `tracing_appender` for non blocking output. -use std::io; +use std::io::{self}; use anyhow::Context; use tracing::level_filters::LevelFilter; use tracing_subscriber::{ - filter::Targets, fmt::MakeWriter, layer::SubscriberExt, util::SubscriberInitExt, Layer, - Registry, + filter::{filter_fn, Targets}, + fmt::MakeWriter, + layer::SubscriberExt, + Layer, Registry, }; use tracing_tree::HierarchicalLayer; use crate::tracing::hprof; +use crate::tracing::json; #[derive(Debug)] pub struct Config<T> { @@ -34,6 +37,12 @@ pub struct Config<T> { /// env RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more than 10 /// ``` pub profile_filter: Option<String>, + + /// Filtering syntax, set in a shell: + /// ``` + /// env RA_PROFILE_JSON=foo|bar|baz + /// ``` + pub json_profile_filter: Option<String>, } impl<T> Config<T> @@ -41,7 +50,7 @@ where T: for<'writer> MakeWriter<'writer> + Send + Sync + 'static, { pub fn init(self) -> anyhow::Result<()> { - let filter: Targets = self + let targets_filter: Targets = self .filter .parse() .with_context(|| format!("invalid log filter: `{}`", self.filter))?; @@ -50,31 +59,60 @@ where let ra_fmt_layer = tracing_subscriber::fmt::layer() .with_target(false) + .with_ansi(false) .with_writer(writer) - .with_filter(filter); - - let mut chalk_layer = None; - if let Some(chalk_filter) = self.chalk_filter { - let level: LevelFilter = - chalk_filter.parse().with_context(|| "invalid chalk log filter")?; - - let chalk_filter = Targets::new() - .with_target("chalk_solve", level) - .with_target("chalk_ir", level) - .with_target("chalk_recursive", level); - chalk_layer = Some( + .with_filter(targets_filter); + + let chalk_layer = match self.chalk_filter { + Some(chalk_filter) => { + let level: LevelFilter = + chalk_filter.parse().with_context(|| "invalid chalk log filter")?; + + let chalk_filter = Targets::new() + .with_target("chalk_solve", level) + .with_target("chalk_ir", level) + .with_target("chalk_recursive", level); + // TODO: remove `.with_filter(LevelFilter::OFF)` on the `None` branch. HierarchicalLayer::default() .with_indent_lines(true) .with_ansi(false) .with_indent_amount(2) .with_writer(io::stderr) - .with_filter(chalk_filter), - ); + .with_filter(chalk_filter) + .boxed() + } + None => None::<HierarchicalLayer>.with_filter(LevelFilter::OFF).boxed(), + }; + + // TODO: remove `.with_filter(LevelFilter::OFF)` on the `None` branch. + let profiler_layer = match self.profile_filter { + Some(spec) => Some(hprof::SpanTree::new(&spec)).with_filter(LevelFilter::INFO), + None => None.with_filter(LevelFilter::OFF), + }; + + let json_profiler_layer = match self.json_profile_filter { + Some(spec) => { + let filter = json::JsonFilter::from_spec(&spec); + let filter = filter_fn(move |metadata| { + let allowed = match &filter.allowed_names { + Some(names) => names.contains(metadata.name()), + None => true, + }; + + allowed && metadata.is_span() + }); + Some(json::TimingLayer::new(std::io::stderr).with_filter(filter)) + } + None => None, }; - let profiler_layer = self.profile_filter.map(|spec| hprof::layer(&spec)); + let subscriber = Registry::default() + .with(ra_fmt_layer) + .with(json_profiler_layer) + .with(profiler_layer) + .with(chalk_layer); - Registry::default().with(ra_fmt_layer).with(chalk_layer).with(profiler_layer).try_init()?; + tracing::subscriber::set_global_default(subscriber)?; Ok(()) } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/hprof.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/hprof.rs index 2d1604e70be..cad92962f34 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/hprof.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/hprof.rs @@ -33,6 +33,7 @@ use std::{ fmt::Write, + marker::PhantomData, mem, time::{Duration, Instant}, }; @@ -50,53 +51,42 @@ use tracing_subscriber::{ Layer, Registry, }; -use crate::tracing::hprof; - pub fn init(spec: &str) -> tracing::subscriber::DefaultGuard { - let subscriber = Registry::default().with(layer(spec)); + let subscriber = Registry::default().with(SpanTree::new(spec)); tracing::subscriber::set_default(subscriber) } -pub fn layer<S>(spec: &str) -> impl Layer<S> -where - S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, -{ - let (write_filter, allowed_names) = WriteFilter::from_spec(spec); - - // this filter the first pass for `tracing`: these are all the "profiling" spans, but things like - // span depth or duration are not filtered here: that only occurs at write time. - let profile_filter = filter::filter_fn(move |metadata| { - let allowed = match &allowed_names { - Some(names) => names.contains(metadata.name()), - None => true, - }; - - allowed - && metadata.is_span() - && metadata.level() >= &Level::INFO - && !metadata.target().starts_with("salsa") - && metadata.name() != "compute_exhaustiveness_and_usefulness" - && !metadata.target().starts_with("chalk") - }); - - hprof::SpanTree::default().aggregate(true).spec_filter(write_filter).with_filter(profile_filter) -} - -#[derive(Default, Debug)] -pub(crate) struct SpanTree { +#[derive(Debug)] +pub(crate) struct SpanTree<S> { aggregate: bool, write_filter: WriteFilter, + _inner: PhantomData<fn(S)>, } -impl SpanTree { - /// Merge identical sibling spans together. - pub(crate) fn aggregate(self, yes: bool) -> SpanTree { - SpanTree { aggregate: yes, ..self } - } - - /// Add a write-time filter for span duration or tree depth. - pub(crate) fn spec_filter(self, write_filter: WriteFilter) -> SpanTree { - SpanTree { write_filter, ..self } +impl<S> SpanTree<S> +where + S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, +{ + pub(crate) fn new(spec: &str) -> impl Layer<S> { + let (write_filter, allowed_names) = WriteFilter::from_spec(spec); + + // this filter the first pass for `tracing`: these are all the "profiling" spans, but things like + // span depth or duration are not filtered here: that only occurs at write time. + let profile_filter = filter::filter_fn(move |metadata| { + let allowed = match &allowed_names { + Some(names) => names.contains(metadata.name()), + None => true, + }; + + allowed + && metadata.is_span() + && metadata.level() >= &Level::INFO + && !metadata.target().starts_with("salsa") + && metadata.name() != "compute_exhaustiveness_and_usefulness" + && !metadata.target().starts_with("chalk") + }); + + Self { aggregate: true, write_filter, _inner: PhantomData }.with_filter(profile_filter) } } @@ -136,7 +126,7 @@ impl<'a> Visit for DataVisitor<'a> { } } -impl<S> Layer<S> for SpanTree +impl<S> Layer<S> for SpanTree<S> where S: Subscriber + for<'span> LookupSpan<'span>, { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/json.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/json.rs new file mode 100644 index 00000000000..f540a33b451 --- /dev/null +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/json.rs @@ -0,0 +1,90 @@ +//! A [tracing_subscriber::layer::Layer] that exports new-line delinated JSON. +//! +//! Usage: +//! +//! ```rust +//! let layer = json::TimingLayer::new(std::io::stderr); +//! Registry::default().with(layer).init(); +//! ``` + +use std::{io::Write as _, marker::PhantomData, time::Instant}; + +use ide_db::FxHashSet; +use tracing::{ + span::{Attributes, Id}, + Event, Subscriber, +}; +use tracing_subscriber::{fmt::MakeWriter, layer::Context, registry::LookupSpan, Layer}; + +struct JsonData { + name: &'static str, + start: std::time::Instant, +} + +impl JsonData { + fn new(name: &'static str) -> Self { + Self { name, start: Instant::now() } + } +} + +#[derive(Debug)] +pub(crate) struct TimingLayer<S, W> { + writer: W, + _inner: PhantomData<fn(S)>, +} + +impl<S, W> TimingLayer<S, W> { + pub(crate) fn new(writer: W) -> Self { + Self { writer, _inner: PhantomData } + } +} + +impl<S, W> Layer<S> for TimingLayer<S, W> +where + S: Subscriber + for<'span> LookupSpan<'span>, + W: for<'writer> MakeWriter<'writer> + Send + Sync + 'static, +{ + fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { + let span = ctx.span(id).unwrap(); + + let data = JsonData::new(attrs.metadata().name()); + span.extensions_mut().insert(data); + } + + fn on_event(&self, _event: &Event<'_>, _ctx: Context<'_, S>) {} + + fn on_close(&self, id: Id, ctx: Context<'_, S>) { + #[derive(serde::Serialize)] + struct JsonDataInner { + name: &'static str, + elapsed_ms: u128, + } + + let span = ctx.span(&id).unwrap(); + let Some(data) = span.extensions_mut().remove::<JsonData>() else { + return; + }; + + let data = JsonDataInner { name: data.name, elapsed_ms: data.start.elapsed().as_millis() }; + let mut out = serde_json::to_string(&data).expect("Unable to serialize data"); + out.push('\n'); + self.writer.make_writer().write_all(out.as_bytes()).expect("Unable to write data"); + } +} + +#[derive(Default, Clone, Debug)] +pub(crate) struct JsonFilter { + pub(crate) allowed_names: Option<FxHashSet<String>>, +} + +impl JsonFilter { + pub(crate) fn from_spec(spec: &str) -> Self { + let allowed_names = if spec == "*" { + None + } else { + Some(FxHashSet::from_iter(spec.split('|').map(String::from))) + }; + + Self { allowed_names } + } +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs index 06ce9846818..18aface632d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs @@ -103,6 +103,7 @@ impl Project<'_> { filter: std::env::var("RA_LOG").ok().unwrap_or_else(|| "error".to_owned()), chalk_filter: std::env::var("CHALK_DEBUG").ok(), profile_filter: std::env::var("RA_PROFILE").ok(), + json_profile_filter: std::env::var("RA_PROFILE_JSON").ok(), }; }); diff --git a/src/tools/rust-analyzer/crates/span/src/hygiene.rs b/src/tools/rust-analyzer/crates/span/src/hygiene.rs index cb9c092f5fc..3863b3e809c 100644 --- a/src/tools/rust-analyzer/crates/span/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/span/src/hygiene.rs @@ -142,3 +142,12 @@ pub enum Transparency { /// Def-site spans in procedural macros, identifiers from `macro` by default use this. Opaque, } + +impl Transparency { + /// Returns `true` if the transparency is [`Opaque`]. + /// + /// [`Opaque`]: Transparency::Opaque + pub fn is_opaque(&self) -> bool { + matches!(self, Self::Opaque) + } +} diff --git a/src/tools/rust-analyzer/crates/span/src/map.rs b/src/tools/rust-analyzer/crates/span/src/map.rs index f80de05ec65..66bbce18594 100644 --- a/src/tools/rust-analyzer/crates/span/src/map.rs +++ b/src/tools/rust-analyzer/crates/span/src/map.rs @@ -55,7 +55,10 @@ where /// Returns all [`TextRange`]s that correspond to the given span. /// /// Note this does a linear search through the entire backing vector. - pub fn ranges_with_span_exact(&self, span: SpanData<S>) -> impl Iterator<Item = TextRange> + '_ + pub fn ranges_with_span_exact( + &self, + span: SpanData<S>, + ) -> impl Iterator<Item = (TextRange, S)> + '_ where S: Copy, { @@ -64,14 +67,14 @@ where return None; } let start = idx.checked_sub(1).map_or(TextSize::new(0), |prev| self.spans[prev].0); - Some(TextRange::new(start, end)) + Some((TextRange::new(start, end), s.ctx)) }) } /// Returns all [`TextRange`]s whose spans contain the given span. /// /// Note this does a linear search through the entire backing vector. - pub fn ranges_with_span(&self, span: SpanData<S>) -> impl Iterator<Item = TextRange> + '_ + pub fn ranges_with_span(&self, span: SpanData<S>) -> impl Iterator<Item = (TextRange, S)> + '_ where S: Copy, { @@ -83,7 +86,7 @@ where return None; } let start = idx.checked_sub(1).map_or(TextSize::new(0), |prev| self.spans[prev].0); - Some(TextRange::new(start, end)) + Some((TextRange::new(start, end), s.ctx)) }) } diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs index 0ccd0886760..3a05b83e497 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs @@ -148,6 +148,7 @@ pub fn token_tree_to_syntax_node<Ctx>( ) -> (Parse<SyntaxNode>, SpanMap<Ctx>) where SpanData<Ctx>: Copy + fmt::Debug, + Ctx: PartialEq, { let buffer = match tt { tt::Subtree { @@ -892,6 +893,7 @@ fn delim_to_str(d: tt::DelimiterKind, closing: bool) -> Option<&'static str> { impl<Ctx> TtTreeSink<'_, Ctx> where SpanData<Ctx>: Copy + fmt::Debug, + Ctx: PartialEq, { /// Parses a float literal as if it was a one to two name ref nodes with a dot inbetween. /// This occurs when a float literal is used as a field access. @@ -949,6 +951,7 @@ where } let mut last = self.cursor; + let mut combined_span = None; 'tokens: for _ in 0..n_tokens { let tmp: u8; if self.cursor.eof() { @@ -982,7 +985,10 @@ where format_to!(self.buf, "{lit}"); debug_assert_ne!(self.buf.len() - buf_l, 0); self.text_pos += TextSize::new((self.buf.len() - buf_l) as u32); - self.token_map.push(self.text_pos, lit.span); + combined_span = match combined_span { + None => Some(lit.span), + Some(prev_span) => Some(Self::merge_spans(prev_span, lit.span)), + }; self.cursor = self.cursor.bump(); continue 'tokens; } @@ -1006,9 +1012,13 @@ where }; self.buf += text; self.text_pos += TextSize::of(text); - self.token_map.push(self.text_pos, span); + combined_span = match combined_span { + None => Some(span), + Some(prev_span) => Some(Self::merge_spans(prev_span, span)), + } } + self.token_map.push(self.text_pos, combined_span.expect("expected at least one token")); self.inner.token(kind, self.buf.as_str()); self.buf.clear(); // FIXME: Emitting whitespace for this is really just a hack, we should get rid of it. @@ -1043,4 +1053,22 @@ where fn error(&mut self, error: String) { self.inner.error(error, self.text_pos) } + + fn merge_spans(a: SpanData<Ctx>, b: SpanData<Ctx>) -> SpanData<Ctx> { + // We don't do what rustc does exactly, rustc does something clever when the spans have different syntax contexts + // but this runs afoul of our separation between `span` and `hir-expand`. + SpanData { + range: if a.ctx == b.ctx { + TextRange::new( + std::cmp::min(a.range.start(), b.range.start()), + std::cmp::max(a.range.end(), b.range.end()), + ) + } else { + // Combining ranges make no sense when they come from different syntax contexts. + a.range + }, + anchor: a.anchor, + ctx: a.ctx, + } + } } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast.rs b/src/tools/rust-analyzer/crates/syntax/src/ast.rs index 3282bd6eff2..32b1f5f7544 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast.rs @@ -8,6 +8,7 @@ pub mod make; mod node_ext; mod operators; pub mod prec; +pub mod syntax_factory; mod token_ext; mod traits; @@ -166,7 +167,7 @@ mod support { } #[test] -fn assert_ast_is_object_safe() { +fn assert_ast_is_dyn_compatible() { fn _f(_: &dyn AstNode, _: &dyn HasName) {} } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory.rs new file mode 100644 index 00000000000..73bbe49105d --- /dev/null +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory.rs @@ -0,0 +1,45 @@ +//! Builds upon [`crate::ast::make`] constructors to create ast fragments with +//! optional syntax mappings. +//! +//! Instead of forcing make constructors to perform syntax mapping, we instead +//! let [`SyntaxFactory`] handle constructing the mappings. Care must be taken +//! to remember to feed the syntax mappings into a [`SyntaxEditor`](crate::syntax_editor::SyntaxEditor), +//! if applicable. + +mod constructors; + +use std::cell::{RefCell, RefMut}; + +use crate::syntax_editor::SyntaxMapping; + +pub struct SyntaxFactory { + // Stored in a refcell so that the factory methods can be &self + mappings: Option<RefCell<SyntaxMapping>>, +} + +impl SyntaxFactory { + /// Creates a new [`SyntaxFactory`], generating mappings between input nodes and generated nodes. + pub fn new() -> Self { + Self { mappings: Some(RefCell::new(SyntaxMapping::new())) } + } + + /// Creates a [`SyntaxFactory`] without generating mappings. + pub fn without_mappings() -> Self { + Self { mappings: None } + } + + /// Gets all of the tracked syntax mappings, if any. + pub fn finish_with_mappings(self) -> SyntaxMapping { + self.mappings.unwrap_or_default().into_inner() + } + + fn mappings(&self) -> Option<RefMut<'_, SyntaxMapping>> { + self.mappings.as_ref().map(|it| it.borrow_mut()) + } +} + +impl Default for SyntaxFactory { + fn default() -> Self { + Self::without_mappings() + } +} diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs new file mode 100644 index 00000000000..9f88add0f78 --- /dev/null +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -0,0 +1,110 @@ +//! Wrappers over [`make`] constructors +use itertools::Itertools; + +use crate::{ + ast::{self, make, HasName}, + syntax_editor::SyntaxMappingBuilder, + AstNode, +}; + +use super::SyntaxFactory; + +impl SyntaxFactory { + pub fn name(&self, name: &str) -> ast::Name { + make::name(name).clone_for_update() + } + + pub fn ident_pat(&self, ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat { + let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + + pub fn block_expr( + &self, + stmts: impl IntoIterator<Item = ast::Stmt>, + tail_expr: Option<ast::Expr>, + ) -> ast::BlockExpr { + let stmts = stmts.into_iter().collect_vec(); + let input = stmts.iter().map(|it| it.syntax().clone()).collect_vec(); + + let ast = make::block_expr(stmts, tail_expr.clone()).clone_for_update(); + + if let Some((mut mapping, stmt_list)) = self.mappings().zip(ast.stmt_list()) { + let mut builder = SyntaxMappingBuilder::new(stmt_list.syntax().clone()); + + builder.map_children( + input.into_iter(), + stmt_list.statements().map(|it| it.syntax().clone()), + ); + + if let Some((input, output)) = tail_expr.zip(stmt_list.tail_expr()) { + builder.map_node(input.syntax().clone(), output.syntax().clone()); + } + + builder.finish(&mut mapping); + } + + ast + } + + pub fn expr_path(&self, path: ast::Path) -> ast::Expr { + let ast::Expr::PathExpr(ast) = make::expr_path(path.clone()).clone_for_update() else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast.into() + } + + pub fn expr_ref(&self, expr: ast::Expr, exclusive: bool) -> ast::Expr { + let ast::Expr::RefExpr(ast) = make::expr_ref(expr.clone(), exclusive).clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast.into() + } + + pub fn let_stmt( + &self, + pattern: ast::Pat, + ty: Option<ast::Type>, + initializer: Option<ast::Expr>, + ) -> ast::LetStmt { + let ast = + make::let_stmt(pattern.clone(), ty.clone(), initializer.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(pattern.syntax().clone(), ast.pat().unwrap().syntax().clone()); + if let Some(input) = ty { + builder.map_node(input.syntax().clone(), ast.ty().unwrap().syntax().clone()); + } + if let Some(input) = initializer { + builder + .map_node(input.syntax().clone(), ast.initializer().unwrap().syntax().clone()); + } + builder.finish(&mut mapping); + } + + ast + } +} diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index eb114f5e5f1..714f5a99111 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -100,6 +100,10 @@ impl SyntaxEditor { pub fn finish(self) -> SyntaxEdit { edit_algo::apply_edits(self) } + + pub fn add_mappings(&mut self, other: SyntaxMapping) { + self.mappings.merge(other); + } } /// Represents a completed [`SyntaxEditor`] operation. @@ -319,85 +323,14 @@ fn is_ancestor_or_self_of_element(node: &SyntaxElement, ancestor: &SyntaxNode) - #[cfg(test)] mod tests { use expect_test::expect; - use itertools::Itertools; use crate::{ - ast::{self, make, HasName}, + ast::{self, make, syntax_factory::SyntaxFactory}, AstNode, }; use super::*; - fn make_ident_pat( - editor: Option<&mut SyntaxEditor>, - ref_: bool, - mut_: bool, - name: ast::Name, - ) -> ast::IdentPat { - let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update(); - - if let Some(editor) = editor { - let mut mapping = SyntaxMappingBuilder::new(ast.syntax().clone()); - mapping.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); - mapping.finish(editor); - } - - ast - } - - fn make_let_stmt( - editor: Option<&mut SyntaxEditor>, - pattern: ast::Pat, - ty: Option<ast::Type>, - initializer: Option<ast::Expr>, - ) -> ast::LetStmt { - let ast = - make::let_stmt(pattern.clone(), ty.clone(), initializer.clone()).clone_for_update(); - - if let Some(editor) = editor { - let mut mapping = SyntaxMappingBuilder::new(ast.syntax().clone()); - mapping.map_node(pattern.syntax().clone(), ast.pat().unwrap().syntax().clone()); - if let Some(input) = ty { - mapping.map_node(input.syntax().clone(), ast.ty().unwrap().syntax().clone()); - } - if let Some(input) = initializer { - mapping - .map_node(input.syntax().clone(), ast.initializer().unwrap().syntax().clone()); - } - mapping.finish(editor); - } - - ast - } - - fn make_block_expr( - editor: Option<&mut SyntaxEditor>, - stmts: impl IntoIterator<Item = ast::Stmt>, - tail_expr: Option<ast::Expr>, - ) -> ast::BlockExpr { - let stmts = stmts.into_iter().collect_vec(); - let input = stmts.iter().map(|it| it.syntax().clone()).collect_vec(); - - let ast = make::block_expr(stmts, tail_expr.clone()).clone_for_update(); - - if let Some((editor, stmt_list)) = editor.zip(ast.stmt_list()) { - let mut mapping = SyntaxMappingBuilder::new(stmt_list.syntax().clone()); - - mapping.map_children( - input.into_iter(), - stmt_list.statements().map(|it| it.syntax().clone()), - ); - - if let Some((input, output)) = tail_expr.zip(stmt_list.tail_expr()) { - mapping.map_node(input.syntax().clone(), output.syntax().clone()); - } - - mapping.finish(editor); - } - - ast - } - #[test] fn basic_usage() { let root = make::match_arm( @@ -417,6 +350,7 @@ mod tests { let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap(); let mut editor = SyntaxEditor::new(root.syntax().clone()); + let make = SyntaxFactory::new(); let name = make::name("var_name"); let name_ref = make::name_ref("var_name").clone_for_update(); @@ -425,21 +359,20 @@ mod tests { editor.add_annotation(name.syntax(), placeholder_snippet); editor.add_annotation(name_ref.syntax(), placeholder_snippet); - let make_ident_pat = make_ident_pat(Some(&mut editor), false, false, name); - let make_let_stmt = make_let_stmt( - Some(&mut editor), - make_ident_pat.into(), - None, - Some(to_replace.clone().into()), - ); - let new_block = make_block_expr( - Some(&mut editor), - [make_let_stmt.into()], + let new_block = make.block_expr( + [make + .let_stmt( + make.ident_pat(false, false, name.clone()).into(), + None, + Some(to_replace.clone().into()), + ) + .into()], Some(to_wrap.clone().into()), ); editor.replace(to_replace.syntax(), name_ref.syntax()); editor.replace(to_wrap.syntax(), new_block.syntax()); + editor.add_mappings(make.finish_with_mappings()); let edit = editor.finish(); @@ -473,11 +406,11 @@ mod tests { let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); let mut editor = SyntaxEditor::new(root.syntax().clone()); + let make = SyntaxFactory::without_mappings(); editor.insert( Position::first_child_of(root.stmt_list().unwrap().syntax()), - make_let_stmt( - None, + make.let_stmt( make::ext::simple_ident_pat(make::name("first")).into(), None, Some(make::expr_literal("1").into()), @@ -487,8 +420,7 @@ mod tests { editor.insert( Position::after(second_let.syntax()), - make_let_stmt( - None, + make.let_stmt( make::ext::simple_ident_pat(make::name("third")).into(), None, Some(make::expr_literal("3").into()), @@ -528,19 +460,17 @@ mod tests { let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); let mut editor = SyntaxEditor::new(root.syntax().clone()); + let make = SyntaxFactory::new(); - let new_block_expr = - make_block_expr(Some(&mut editor), [], Some(ast::Expr::BlockExpr(inner_block.clone()))); + let new_block_expr = make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone()))); - let first_let = make_let_stmt( - Some(&mut editor), + let first_let = make.let_stmt( make::ext::simple_ident_pat(make::name("first")).into(), None, Some(make::expr_literal("1").into()), ); - let third_let = make_let_stmt( - Some(&mut editor), + let third_let = make.let_stmt( make::ext::simple_ident_pat(make::name("third")).into(), None, Some(make::expr_literal("3").into()), @@ -552,6 +482,7 @@ mod tests { ); editor.insert(Position::after(second_let.syntax()), third_let.syntax()); editor.replace(inner_block.syntax(), new_block_expr.syntax()); + editor.add_mappings(make.finish_with_mappings()); let edit = editor.finish(); @@ -581,12 +512,11 @@ mod tests { let inner_block = root.clone(); let mut editor = SyntaxEditor::new(root.syntax().clone()); + let make = SyntaxFactory::new(); - let new_block_expr = - make_block_expr(Some(&mut editor), [], Some(ast::Expr::BlockExpr(inner_block.clone()))); + let new_block_expr = make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone()))); - let first_let = make_let_stmt( - Some(&mut editor), + let first_let = make.let_stmt( make::ext::simple_ident_pat(make::name("first")).into(), None, Some(make::expr_literal("1").into()), @@ -597,6 +527,7 @@ mod tests { first_let.syntax(), ); editor.replace(inner_block.syntax(), new_block_expr.syntax()); + editor.add_mappings(make.finish_with_mappings()); let edit = editor.finish(); diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs index 9bb5e6d9338..16bc55ed2d4 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs @@ -7,8 +7,6 @@ use rustc_hash::FxHashMap; use crate::{SyntaxElement, SyntaxNode}; -use super::SyntaxEditor; - #[derive(Debug, Default)] pub struct SyntaxMapping { // important information to keep track of: @@ -209,7 +207,7 @@ impl SyntaxMapping { Some(output) } - fn add_mapping(&mut self, syntax_mapping: SyntaxMappingBuilder) { + pub fn add_mapping(&mut self, syntax_mapping: SyntaxMappingBuilder) { let SyntaxMappingBuilder { parent_node, node_mappings } = syntax_mapping; let parent_entry: u32 = self.entry_parents.len().try_into().unwrap(); @@ -257,8 +255,8 @@ impl SyntaxMappingBuilder { } } - pub fn finish(self, editor: &mut SyntaxEditor) { - editor.mappings.add_mapping(self); + pub fn finish(self, mappings: &mut SyntaxMapping) { + mappings.add_mapping(self); } } diff --git a/src/tools/rust-analyzer/crates/vfs/src/loader.rs b/src/tools/rust-analyzer/crates/vfs/src/loader.rs index f24354cb493..c49e4c4322d 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/loader.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/loader.rs @@ -1,4 +1,4 @@ -//! Object safe interface for file watching and reading. +//! Dynamically compatible interface for file watching and reading. use std::fmt; use paths::{AbsPath, AbsPathBuf}; @@ -232,6 +232,6 @@ impl fmt::Debug for Message { } #[test] -fn handle_is_object_safe() { +fn handle_is_dyn_compatible() { fn _assert(_: &dyn Handle) {} } diff --git a/src/tools/rust-analyzer/docs/dev/README.md b/src/tools/rust-analyzer/docs/dev/README.md index 002b8ba2a66..12e6d829a08 100644 --- a/src/tools/rust-analyzer/docs/dev/README.md +++ b/src/tools/rust-analyzer/docs/dev/README.md @@ -178,7 +178,15 @@ RA_PROFILE=foo|bar|baz // enabled only selected entries RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more than 10 ms ``` -In particular, I have `export RA_PROFILE='*>10'` in my shell profile. +Some rust-analyzer contributors have `export RA_PROFILE='*>10'` in my shell profile. + +For machine-readable JSON output, we have the `RA_PROFILE_JSON` env variable. We support +filtering only by span name: + +``` +RA_PROFILE=* // dump everything +RA_PROFILE_JSON="vfs_load|parallel_prime_caches|discover_command" // dump selected spans +``` We also have a "counting" profiler which counts number of instances of popular structs. It is enabled by `RA_COUNT=1`. diff --git a/src/tools/rust-analyzer/docs/dev/architecture.md b/src/tools/rust-analyzer/docs/dev/architecture.md index 4f8723a9368..6aa57b2f9be 100644 --- a/src/tools/rust-analyzer/docs/dev/architecture.md +++ b/src/tools/rust-analyzer/docs/dev/architecture.md @@ -42,7 +42,7 @@ The underlying engine makes sure that model is computed lazily (on-demand) and c `crates/rust-analyzer/src/bin/main.rs` contains the main function which spawns LSP. This is *the* entry point, but it front-loads a lot of complexity, so it's fine to just skim through it. -`crates/rust-analyzer/src/handlers.rs` implements all LSP requests and is a great place to start if you are already familiar with LSP. +`crates/rust-analyzer/src/handlers/requests.rs` implements all LSP requests and is a great place to start if you are already familiar with LSP. `Analysis` and `AnalysisHost` types define the main API for consumers of IDE services. diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md index b7bac4d29fa..7764f7843a0 100644 --- a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md +++ b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@ <!--- -lsp/ext.rs hash: 6292ee8d88d4c9ec +lsp/ext.rs hash: 90cf7718d54fe3c2 If you need to change the above hash to make the test pass, please check if you need to adjust this doc as well and ping this issue: diff --git a/src/tools/rust-analyzer/docs/dev/syntax.md b/src/tools/rust-analyzer/docs/dev/syntax.md index 6c4daecc58f..3dcd430cea5 100644 --- a/src/tools/rust-analyzer/docs/dev/syntax.md +++ b/src/tools/rust-analyzer/docs/dev/syntax.md @@ -378,7 +378,7 @@ impl AstNode for AssocItem { } ``` -Shared AST substructures are modeled via (object safe) traits: +Shared AST substructures are modeled via (dynamically compatible) traits: ```rust trait HasVisibility: AstNode { diff --git a/src/tools/rust-analyzer/docs/user/generated_config.adoc b/src/tools/rust-analyzer/docs/user/generated_config.adoc index f37fd7f4ab3..708fc2b7891 100644 --- a/src/tools/rust-analyzer/docs/user/generated_config.adoc +++ b/src/tools/rust-analyzer/docs/user/generated_config.adoc @@ -158,6 +158,11 @@ building from locking the `Cargo.lock` at the expense of duplicating build artif Set to `true` to use a subdirectory of the existing target directory or set to a path relative to the workspace to use that path. -- +[[rust-analyzer.cfg.setTest]]rust-analyzer.cfg.setTest (default: `true`):: ++ +-- +Set `cfg(test)` for local crates. Defaults to true. +-- [[rust-analyzer.checkOnSave]]rust-analyzer.checkOnSave (default: `true`):: + -- diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index b66f0a64d57..a823e5bb96c 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -512,6 +512,11 @@ "type": "boolean", "default": false }, + "rust-analyzer.debug.buildBeforeRestart": { + "markdownDescription": "Whether to rebuild the project modules before debugging the same test again", + "type": "boolean", + "default": false + }, "rust-analyzer.debug.engineSettings": { "type": "object", "default": {}, @@ -849,6 +854,16 @@ } }, { + "title": "cfg", + "properties": { + "rust-analyzer.cfg.setTest": { + "markdownDescription": "Set `cfg(test)` for local crates. Defaults to true.", + "default": true, + "type": "boolean" + } + } + }, + { "title": "general", "properties": { "rust-analyzer.checkOnSave": { diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index daead47e942..35867f710d5 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -23,10 +23,11 @@ export async function bootstrap( if (!isValidExecutable(path, config.serverExtraEnv)) { throw new Error( - `Failed to execute ${path} --version.` + config.serverPath - ? `\`config.server.path\` or \`config.serverPath\` has been set explicitly.\ + `Failed to execute ${path} --version.` + + (config.serverPath + ? `\`config.server.path\` or \`config.serverPath\` has been set explicitly.\ Consider removing this config or making a valid server binary available at that path.` - : "", + : ""), ); } diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts index 1e3dc608095..abb4099f9f5 100644 --- a/src/tools/rust-analyzer/editors/code/src/config.ts +++ b/src/tools/rust-analyzer/editors/code/src/config.ts @@ -24,6 +24,7 @@ export class Config { "serverPath", "server", "files", + "cfg", ].map((opt) => `${this.rootSection}.${opt}`); private readonly requiresWindowReloadOpts = ["testExplorer"].map( @@ -299,6 +300,7 @@ export class Config { engine: this.get<string>("debug.engine"), engineSettings: this.get<object>("debug.engineSettings") ?? {}, openDebugPane: this.get<boolean>("debug.openDebugPane"), + buildBeforeRestart: this.get<boolean>("debug.buildBeforeRestart"), sourceFileMap: sourceFileMap, }; } diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 3aae0f9ce6e..fb7e340e517 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -5,12 +5,15 @@ import type * as ra from "./lsp_ext"; import { Cargo } from "./toolchain"; import type { Ctx } from "./ctx"; -import { prepareEnv } from "./run"; +import { createTaskFromRunnable, prepareEnv } from "./run"; import { execute, isCargoRunnableArgs, unwrapUndefinable } from "./util"; import type { Config } from "./config"; const debugOutput = vscode.window.createOutputChannel("Debug"); +// Here we want to keep track on everything that's currently running +const activeDebugSessionIds: string[] = []; + export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> { const scope = ctx.activeRustEditor?.document.uri; if (!scope) return; @@ -45,6 +48,8 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis const wsLaunchSection = vscode.workspace.getConfiguration("launch"); const configurations = wsLaunchSection.get<any[]>("configurations") || []; + // The runnable label is the name of the test with the "test prefix" + // e.g. test test_feature_x const index = configurations.findIndex((c) => c.name === runnable.label); if (-1 !== index) { debugConfig = configurations[index]; @@ -168,6 +173,8 @@ async function getDebugConfiguration( if (debugConfig.name === "run binary") { // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs, // fn to_lsp_runnable(...) with RunnableKind::Bin + // FIXME: Neither crates\rust-analyzer\src\main_loop\handlers.rs + // nor to_lsp_runnable exist anymore debugConfig.name = `run ${path.basename(executable)}`; } @@ -359,3 +366,49 @@ function quote(xs: string[]) { }) .join(" "); } + +async function recompileTestFromDebuggingSession(session: vscode.DebugSession, ctx: Ctx) { + const { cwd, args: sessionArgs }: vscode.DebugConfiguration = session.configuration; + + const args: ra.CargoRunnableArgs = { + cwd: cwd, + cargoArgs: ["test", "--no-run", "--test", "lib"], + + // The first element of the debug configuration args is the test path e.g. "test_bar::foo::test_a::test_b" + executableArgs: sessionArgs, + }; + const runnable: ra.Runnable = { + kind: "cargo", + label: "compile-test", + args, + }; + const task: vscode.Task = await createTaskFromRunnable(runnable, ctx.config); + + // It is not needed to call the language server, since the test path is already resolved in the + // configuration option. We can simply call a debug configuration with the --no-run option to compile + await vscode.tasks.executeTask(task); +} + +export function initializeDebugSessionTrackingAndRebuild(ctx: Ctx) { + vscode.debug.onDidStartDebugSession((session: vscode.DebugSession) => { + if (!activeDebugSessionIds.includes(session.id)) { + activeDebugSessionIds.push(session.id); + } + }); + + vscode.debug.onDidTerminateDebugSession(async (session: vscode.DebugSession) => { + // The id of the session will be the same when pressing restart the restart button + if (activeDebugSessionIds.find((s) => s === session.id)) { + await recompileTestFromDebuggingSession(session, ctx); + } + removeActiveSession(session); + }); +} + +function removeActiveSession(session: vscode.DebugSession) { + const activeSessionId = activeDebugSessionIds.findIndex((id) => id === session.id); + + if (activeSessionId !== -1) { + activeDebugSessionIds.splice(activeSessionId, 1); + } +} diff --git a/src/tools/rust-analyzer/editors/code/src/main.ts b/src/tools/rust-analyzer/editors/code/src/main.ts index 4769fdd864a..0ddc5619e99 100644 --- a/src/tools/rust-analyzer/editors/code/src/main.ts +++ b/src/tools/rust-analyzer/editors/code/src/main.ts @@ -6,6 +6,7 @@ import { type CommandFactory, Ctx, fetchWorkspace } from "./ctx"; import * as diagnostics from "./diagnostics"; import { activateTaskProvider } from "./tasks"; import { setContextValue } from "./util"; +import { initializeDebugSessionTrackingAndRebuild } from "./debug"; const RUST_PROJECT_CONTEXT_NAME = "inRustProject"; @@ -102,6 +103,10 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> { ctx.subscriptions, ); + if (ctx.config.debug.buildBeforeRestart) { + initializeDebugSessionTrackingAndRebuild(ctx); + } + await ctx.start(); return ctx; } diff --git a/src/tools/rust-analyzer/editors/code/src/run.ts b/src/tools/rust-analyzer/editors/code/src/run.ts index dd0da6b62c8..8a82a5a58cf 100644 --- a/src/tools/rust-analyzer/editors/code/src/run.ts +++ b/src/tools/rust-analyzer/editors/code/src/run.ts @@ -36,7 +36,7 @@ export async function selectRunnable( if (runnables.length === 0) { // it is the debug case, run always has at least 'cargo check ...' - // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables + // see crates\rust-analyzer\src\handlers\request.rs, handle_runnables await vscode.window.showErrorMessage("There's no debug target!"); quickPick.dispose(); return; diff --git a/src/tools/rust-analyzer/editors/code/src/toolchain.ts b/src/tools/rust-analyzer/editors/code/src/toolchain.ts index 850a6a55616..e8bab9c3d84 100644 --- a/src/tools/rust-analyzer/editors/code/src/toolchain.ts +++ b/src/tools/rust-analyzer/editors/code/src/toolchain.ts @@ -29,7 +29,7 @@ export class Cargo { static artifactSpec(cargoArgs: string[], executableArgs?: string[]): ArtifactSpec { cargoArgs = [...cargoArgs, "--message-format=json"]; // arguments for a runnable from the quick pick should be updated. - // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens + // see crates\rust-analyzer\src\handlers\request.rs, handle_code_lens switch (cargoArgs[0]) { case "run": cargoArgs[0] = "build"; diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 79ed6cc7d74..f217c6a19cb 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -1b5aa96d6016bafe50e071b45d4d2e3c90fd766f +cf24c73141a77db730f4b7fda69dcd7e8b113b51 diff --git a/src/tools/rustdoc-gui/tester.js b/src/tools/rustdoc-gui/tester.js index 8f6626f6296..c5874b4ee02 100644 --- a/src/tools/rustdoc-gui/tester.js +++ b/src/tools/rustdoc-gui/tester.js @@ -19,7 +19,6 @@ function showHelp() { console.log(" --debug : show extra information about script run"); console.log(" --show-text : render font in pages"); console.log(" --no-headless : disable headless mode"); - console.log(" --no-sandbox : disable sandbox mode"); console.log(" --help : show this message then quit"); console.log(" --tests-folder [PATH] : location of the .GOML tests folder"); console.log(" --jobs [NUMBER] : number of threads to run tests on"); @@ -40,7 +39,6 @@ function parseOptions(args) { "no_headless": false, "jobs": -1, "executable_path": null, - "no_sandbox": false, }; const correspondences = { "--doc-folder": "doc_folder", @@ -49,7 +47,6 @@ function parseOptions(args) { "--show-text": "show_text", "--no-headless": "no_headless", "--executable-path": "executable_path", - "--no-sandbox": "no_sandbox", }; for (let i = 0; i < args.length; ++i) { @@ -80,9 +77,6 @@ function parseOptions(args) { } else if (arg === "--help") { showHelp(); process.exit(0); - } else if (arg === "--no-sandbox") { - console.log("`--no-sandbox` is being used. Be very careful!"); - opts[correspondences[arg]] = true; } else if (correspondences[arg]) { opts[correspondences[arg]] = true; } else { @@ -203,6 +197,7 @@ async function main(argv) { const args = [ "--variable", "DOC_PATH", opts["doc_folder"].split("\\").join("/"), "--enable-fail-on-js-error", "--allow-file-access-from-files", + "--no-sandbox", ]; if (opts["debug"]) { debug = true; @@ -211,9 +206,6 @@ async function main(argv) { if (opts["show_text"]) { args.push("--show-text"); } - if (opts["no_sandbox"]) { - args.push("--no-sandbox"); - } if (opts["no_headless"]) { args.push("--no-headless"); headless = false; @@ -262,19 +254,6 @@ async function main(argv) { console.log(`Running ${files.length} rustdoc-gui ...`); } - // We catch this "event" to display a nicer message in case of unexpected exit (because of a - // missing `--no-sandbox`). - const exitHandling = () => { - if (!opts["no_sandbox"]) { - console.log(""); - console.log( - "`browser-ui-test` crashed unexpectedly. Please try again with adding `--test-args \ ---no-sandbox` at the end. For example: `x.py test tests/rustdoc-gui --test-args --no-sandbox`"); - console.log(""); - } - }; - process.on("exit", exitHandling); - const originalFilesLen = files.length; const results = createEmptyResults(); const status_bar = char_printer(files.length); @@ -299,9 +278,6 @@ async function main(argv) { Array.prototype.push.apply(results.failed, new_results.failed); Array.prototype.push.apply(results.errored, new_results.errored); - // We don't need this listener anymore. - process.removeListener("exit", exitHandling); - if (debug) { results.successful.sort(by_filename); results.successful.forEach(r => { diff --git a/src/tools/rustfmt/src/attr.rs b/src/tools/rustfmt/src/attr.rs index 5c2068b6a22..381c938ae80 100644 --- a/src/tools/rustfmt/src/attr.rs +++ b/src/tools/rustfmt/src/attr.rs @@ -90,7 +90,7 @@ fn format_derive( let item_spans = attr.meta_item_list().map(|meta_item_list| { meta_item_list .into_iter() - .map(|nested_meta_item| nested_meta_item.span()) + .map(|meta_item_inner| meta_item_inner.span()) })?; let items = itemize_list( @@ -243,17 +243,15 @@ fn rewrite_initial_doc_comments( Ok((0, None)) } -impl Rewrite for ast::NestedMetaItem { +impl Rewrite for ast::MetaItemInner { fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> { self.rewrite_result(context, shape).ok() } fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult { match self { - ast::NestedMetaItem::MetaItem(ref meta_item) => { - meta_item.rewrite_result(context, shape) - } - ast::NestedMetaItem::Lit(ref l) => { + ast::MetaItemInner::MetaItem(ref meta_item) => meta_item.rewrite_result(context, shape), + ast::MetaItemInner::Lit(ref l) => { rewrite_literal(context, l.as_token_lit(), l.span, shape) } } @@ -535,10 +533,10 @@ pub(crate) trait MetaVisitor<'ast> { fn visit_meta_list( &mut self, _meta_item: &'ast ast::MetaItem, - list: &'ast [ast::NestedMetaItem], + list: &'ast [ast::MetaItemInner], ) { for nm in list { - self.visit_nested_meta_item(nm); + self.visit_meta_item_inner(nm); } } @@ -551,10 +549,10 @@ pub(crate) trait MetaVisitor<'ast> { ) { } - fn visit_nested_meta_item(&mut self, nm: &'ast ast::NestedMetaItem) { + fn visit_meta_item_inner(&mut self, nm: &'ast ast::MetaItemInner) { match nm { - ast::NestedMetaItem::MetaItem(ref meta_item) => self.visit_meta_item(meta_item), - ast::NestedMetaItem::Lit(ref lit) => self.visit_meta_item_lit(lit), + ast::MetaItemInner::MetaItem(ref meta_item) => self.visit_meta_item(meta_item), + ast::MetaItemInner::Lit(ref lit) => self.visit_meta_item_lit(lit), } } diff --git a/src/tools/rustfmt/src/overflow.rs b/src/tools/rustfmt/src/overflow.rs index fdc8e23e72b..728adff2c7d 100644 --- a/src/tools/rustfmt/src/overflow.rs +++ b/src/tools/rustfmt/src/overflow.rs @@ -78,7 +78,7 @@ pub(crate) enum OverflowableItem<'a> { Expr(&'a ast::Expr), GenericParam(&'a ast::GenericParam), MacroArg(&'a MacroArg), - NestedMetaItem(&'a ast::NestedMetaItem), + MetaItemInner(&'a ast::MetaItemInner), SegmentParam(&'a SegmentParam<'a>), FieldDef(&'a ast::FieldDef), TuplePatField(&'a TuplePatField<'a>), @@ -123,7 +123,7 @@ impl<'a> OverflowableItem<'a> { OverflowableItem::Expr(expr) => f(*expr), OverflowableItem::GenericParam(gp) => f(*gp), OverflowableItem::MacroArg(macro_arg) => f(*macro_arg), - OverflowableItem::NestedMetaItem(nmi) => f(*nmi), + OverflowableItem::MetaItemInner(nmi) => f(*nmi), OverflowableItem::SegmentParam(sp) => f(*sp), OverflowableItem::FieldDef(sf) => f(*sf), OverflowableItem::TuplePatField(pat) => f(*pat), @@ -138,9 +138,9 @@ impl<'a> OverflowableItem<'a> { OverflowableItem::Expr(expr) => is_simple_expr(expr), OverflowableItem::MacroArg(MacroArg::Keyword(..)) => true, OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_simple_expr(expr), - OverflowableItem::NestedMetaItem(nested_meta_item) => match nested_meta_item { - ast::NestedMetaItem::Lit(..) => true, - ast::NestedMetaItem::MetaItem(ref meta_item) => { + OverflowableItem::MetaItemInner(meta_item_inner) => match meta_item_inner { + ast::MetaItemInner::Lit(..) => true, + ast::MetaItemInner::MetaItem(ref meta_item) => { matches!(meta_item.kind, ast::MetaItemKind::Word) } }, @@ -184,12 +184,10 @@ impl<'a> OverflowableItem<'a> { MacroArg::Item(..) => len == 1, MacroArg::Keyword(..) => false, }, - OverflowableItem::NestedMetaItem(nested_meta_item) if len == 1 => { - match nested_meta_item { - ast::NestedMetaItem::Lit(..) => false, - ast::NestedMetaItem::MetaItem(..) => true, - } - } + OverflowableItem::MetaItemInner(meta_item_inner) if len == 1 => match meta_item_inner { + ast::MetaItemInner::Lit(..) => false, + ast::MetaItemInner::MetaItem(..) => true, + }, OverflowableItem::SegmentParam(SegmentParam::Type(ty)) => { can_be_overflowed_type(context, ty, len) } @@ -202,7 +200,7 @@ impl<'a> OverflowableItem<'a> { fn special_cases(&self, config: &Config) -> impl Iterator<Item = &(&'static str, usize)> { let base_cases = match self { OverflowableItem::MacroArg(..) => SPECIAL_CASE_MACROS, - OverflowableItem::NestedMetaItem(..) => SPECIAL_CASE_ATTR, + OverflowableItem::MetaItemInner(..) => SPECIAL_CASE_ATTR, _ => &[], }; let additional_cases = match self { @@ -261,7 +259,7 @@ macro_rules! impl_into_overflowable_item_for_rustfmt_types { impl_into_overflowable_item_for_ast_node!( Expr, GenericParam, - NestedMetaItem, + MetaItemInner, FieldDef, Ty, Pat, diff --git a/src/tools/rustfmt/src/parse/macros/asm.rs b/src/tools/rustfmt/src/parse/macros/asm.rs index 5ee083da539..58c8d21bd7a 100644 --- a/src/tools/rustfmt/src/parse/macros/asm.rs +++ b/src/tools/rustfmt/src/parse/macros/asm.rs @@ -7,5 +7,5 @@ use crate::rewrite::RewriteContext; pub(crate) fn parse_asm(context: &RewriteContext<'_>, mac: &ast::MacCall) -> Option<AsmArgs> { let ts = mac.args.tokens.clone(); let mut parser = super::build_parser(context, ts); - parse_asm_args(&mut parser, mac.span(), false).ok() + parse_asm_args(&mut parser, mac.span(), ast::AsmMacro::Asm).ok() } diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs index 9f27a05939e..54ad56ed3f3 100644 --- a/src/tools/rustfmt/src/parse/session.rs +++ b/src/tools/rustfmt/src/parse/session.rs @@ -46,7 +46,7 @@ impl SilentOnIgnoredFilesEmitter { } impl Translate for SilentOnIgnoredFilesEmitter { - fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> { + fn fluent_bundle(&self) -> Option<&rustc_errors::FluentBundle> { self.emitter.fluent_bundle() } @@ -56,7 +56,7 @@ impl Translate for SilentOnIgnoredFilesEmitter { } impl Emitter for SilentOnIgnoredFilesEmitter { - fn source_map(&self) -> Option<&Lrc<SourceMap>> { + fn source_map(&self) -> Option<&SourceMap> { None } @@ -344,7 +344,7 @@ mod tests { } impl Translate for TestEmitter { - fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> { + fn fluent_bundle(&self) -> Option<&rustc_errors::FluentBundle> { None } @@ -354,7 +354,7 @@ mod tests { } impl Emitter for TestEmitter { - fn source_map(&self) -> Option<&Lrc<SourceMap>> { + fn source_map(&self) -> Option<&SourceMap> { None } diff --git a/src/tools/rustfmt/src/skip.rs b/src/tools/rustfmt/src/skip.rs index d733f7068fd..cd3860d3d7f 100644 --- a/src/tools/rustfmt/src/skip.rs +++ b/src/tools/rustfmt/src/skip.rs @@ -116,8 +116,8 @@ fn get_skip_names(kind: &str, attrs: &[ast::Attribute]) -> Vec<String> { } if let Some(list) = attr.meta_item_list() { - for nested_meta_item in list { - if let Some(name) = nested_meta_item.ident() { + for meta_item_inner in list { + if let Some(name) = meta_item_inner.ident() { skip_names.push(name.to_string()); } } diff --git a/src/tools/rustfmt/src/spanned.rs b/src/tools/rustfmt/src/spanned.rs index b4424e476ee..555a9240798 100644 --- a/src/tools/rustfmt/src/spanned.rs +++ b/src/tools/rustfmt/src/spanned.rs @@ -199,7 +199,7 @@ impl Spanned for MacroArg { } } -impl Spanned for ast::NestedMetaItem { +impl Spanned for ast::MetaItemInner { fn span(&self) -> Span { self.span() } diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs index 07b483b2b37..f75c4f0fad7 100644 --- a/src/tools/rustfmt/src/types.rs +++ b/src/tools/rustfmt/src/types.rs @@ -951,8 +951,6 @@ impl Rewrite for ast::Ty { ast::TyKind::Tup(ref items) => { rewrite_tuple(context, items.iter(), self.span, shape, items.len() == 1) } - ast::TyKind::AnonStruct(..) => Ok(context.snippet(self.span).to_owned()), - ast::TyKind::AnonUnion(..) => Ok(context.snippet(self.span).to_owned()), ast::TyKind::Path(ref q_self, ref path) => { rewrite_path(context, PathContext::Type, q_self, path, shape) } diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index d1cfc6acc49..0ca34a79491 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use rustc_ast::ast::{ - self, Attribute, MetaItem, MetaItemKind, NestedMetaItem, NodeId, Path, Visibility, + self, Attribute, MetaItem, MetaItemInner, MetaItemKind, NodeId, Path, Visibility, VisibilityKind, }; use rustc_ast::ptr; @@ -257,10 +257,10 @@ fn is_skip(meta_item: &MetaItem) -> bool { } #[inline] -fn is_skip_nested(meta_item: &NestedMetaItem) -> bool { +fn is_skip_nested(meta_item: &MetaItemInner) -> bool { match meta_item { - NestedMetaItem::MetaItem(ref mi) => is_skip(mi), - NestedMetaItem::Lit(_) => false, + MetaItemInner::MetaItem(ref mi) => is_skip(mi), + MetaItemInner::Lit(_) => false, } } diff --git a/src/tools/rustfmt/tests/target/anonymous-types.rs b/src/tools/rustfmt/tests/target/anonymous-types.rs deleted file mode 100644 index e8c2d83878c..00000000000 --- a/src/tools/rustfmt/tests/target/anonymous-types.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Test for issue 85480 -// Pretty print anonymous struct and union types - -// pp-exact -// pretty-compare-only - -struct Foo { - _: union { - _: struct { - a: u8, - b: u16, - }, - c: u32, - }, - d: u64, - e: f32, -} - -// Test for https://github.com/rust-lang/rust/issues/117942 -struct Foo { - _: union { - #[rustfmt::skip] - f: String, - }, - #[rustfmt::skip] - _: struct { - g: i32, - }, -} - -fn main() {} diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt index 50d21c7ed4c..7a0f98d59f0 100644 --- a/src/tools/tidy/src/allowed_run_make_makefiles.txt +++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt @@ -1,6 +1,5 @@ run-make/branch-protection-check-IBT/Makefile run-make/cat-and-grep-sanity-check/Makefile -run-make/emit-to-stdout/Makefile run-make/extern-fn-reachable/Makefile run-make/incr-add-rust-src-component/Makefile run-make/issue-84395-lto-embed-bitcode/Makefile diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 1ffad06457f..5163f039a23 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -129,11 +129,14 @@ const EXCEPTIONS_STDARCH: ExceptionList = &[ const EXCEPTIONS_CARGO: ExceptionList = &[ // tidy-alphabetical-start + ("arrayref", "BSD-2-Clause"), ("bitmaps", "MPL-2.0+"), + ("blake3", "CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception"), ("bytesize", "Apache-2.0"), ("ciborium", "Apache-2.0"), ("ciborium-io", "Apache-2.0"), ("ciborium-ll", "Apache-2.0"), + ("constant_time_eq", "CC0-1.0 OR MIT-0 OR Apache-2.0"), ("dunce", "CC0-1.0 OR MIT-0 OR Apache-2.0"), ("encoding_rs", "(Apache-2.0 OR MIT) AND BSD-3-Clause"), ("fiat-crypto", "MIT OR Apache-2.0 OR BSD-1-Clause"), diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index 86cae849b97..66c176c6f44 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -802,7 +802,6 @@ ui/consts/issue-70942-trait-vs-impl-mismatch.rs ui/consts/issue-73976-monomorphic.rs ui/consts/issue-73976-polymorphic.rs ui/consts/issue-76064.rs -ui/consts/issue-77062-large-zst-array.rs ui/consts/issue-78655.rs ui/consts/issue-79137-monomorphic.rs ui/consts/issue-79137-toogeneric.rs @@ -3173,10 +3172,6 @@ ui/nll/user-annotations/issue-55241.rs ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.rs ui/nll/user-annotations/issue-57731-ascibed-coupled-types.rs ui/numbers-arithmetic/issue-8460.rs -ui/object-safety/issue-102762.rs -ui/object-safety/issue-102933.rs -ui/object-safety/issue-106247.rs -ui/object-safety/issue-19538.rs ui/on-unimplemented/issue-104140.rs ui/or-patterns/issue-64879-trailing-before-guard.rs ui/or-patterns/issue-67514-irrefutable-param.rs diff --git a/src/tools/unicode-table-generator/src/raw_emitter.rs b/src/tools/unicode-table-generator/src/raw_emitter.rs index bfffa1eee60..8097d6a8caf 100644 --- a/src/tools/unicode-table-generator/src/raw_emitter.rs +++ b/src/tools/unicode-table-generator/src/raw_emitter.rs @@ -77,7 +77,7 @@ impl RawEmitter { writeln!( &mut self.file, - "const BITSET_CANONICAL: &'static [u64; {}] = &[{}];", + "static BITSET_CANONICAL: [u64; {}] = [{}];", canonicalized.canonical_words.len(), fmt_list(canonicalized.canonical_words.iter().map(|v| Bits(*v))), ) @@ -85,7 +85,7 @@ impl RawEmitter { self.bytes_used += 8 * canonicalized.canonical_words.len(); writeln!( &mut self.file, - "const BITSET_MAPPING: &'static [(u8, u8); {}] = &[{}];", + "static BITSET_MAPPING: [(u8, u8); {}] = [{}];", canonicalized.canonicalized_words.len(), fmt_list(&canonicalized.canonicalized_words), ) @@ -139,7 +139,7 @@ impl RawEmitter { writeln!( &mut self.file, - "const BITSET_CHUNKS_MAP: &'static [u8; {}] = &[{}];", + "static BITSET_CHUNKS_MAP: [u8; {}] = [{}];", chunk_indices.len(), fmt_list(&chunk_indices), ) @@ -147,7 +147,7 @@ impl RawEmitter { self.bytes_used += chunk_indices.len(); writeln!( &mut self.file, - "const BITSET_INDEX_CHUNKS: &'static [[u8; {}]; {}] = &[{}];", + "static BITSET_INDEX_CHUNKS: [[u8; {}]; {}] = [{}];", chunk_length, chunks.len(), fmt_list(chunks.iter()), diff --git a/src/tools/wasm-component-ld/Cargo.toml b/src/tools/wasm-component-ld/Cargo.toml index 49f4e59650e..acdb1aa1ab7 100644 --- a/src/tools/wasm-component-ld/Cargo.toml +++ b/src/tools/wasm-component-ld/Cargo.toml @@ -10,4 +10,4 @@ name = "wasm-component-ld" path = "src/main.rs" [dependencies] -wasm-component-ld = "0.5.9" +wasm-component-ld = "0.5.10" diff --git a/src/version b/src/version index 6b4de0a42b0..bd0f9e6c28f 100644 --- a/src/version +++ b/src/version @@ -1 +1 @@ -1.83.0 +1.84.0 |
