diff options
| author | The rustc-dev-guide Cronjob Bot <github-actions@github.com> | 2025-06-12 04:06:44 +0000 |
|---|---|---|
| committer | The rustc-dev-guide Cronjob Bot <github-actions@github.com> | 2025-06-12 04:06:44 +0000 |
| commit | 6da0ebf087736289ff29f641c7498f4a94d83639 (patch) | |
| tree | 3ef67bc66cf749e5172054480bdb5f1d8715dc5f /src | |
| parent | 8a7fbded4dd50f297bbcda39a94624ffb689a849 (diff) | |
| parent | 14346303d760027e53214e705109a62c0f00b214 (diff) | |
| download | rust-6da0ebf087736289ff29f641c7498f4a94d83639.tar.gz rust-6da0ebf087736289ff29f641c7498f4a94d83639.zip | |
Merge from rustc
Diffstat (limited to 'src')
149 files changed, 3774 insertions, 1788 deletions
diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py index c60c6b8db64..d8c6be78247 100644 --- a/src/bootstrap/bootstrap.py +++ b/src/bootstrap/bootstrap.py @@ -394,6 +394,7 @@ def default_build_triple(verbose): "i686": "i686", "i686-AT386": "i686", "i786": "i686", + "loongarch32": "loongarch32", "loongarch64": "loongarch64", "m68k": "m68k", "csky": "csky", diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 587fca80374..7aa9e6cc2b5 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -1681,7 +1681,8 @@ impl Step for Extended { cmd.run(builder); } - if target.is_windows() { + // FIXME(mati865): `gnullvm` here is temporary, remove it once it can host itself + if target.is_windows() && !target.contains("gnullvm") { let exe = tmp.join("exe"); let _ = fs::remove_dir_all(&exe); diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index 9861637d8c8..717accb399a 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -23,7 +23,6 @@ use crate::core::builder::{ Builder, Cargo as CargoCommand, RunConfig, ShouldRun, Step, cargo_profile_var, }; use crate::core::config::{DebuginfoLevel, RustcLto, TargetSelection}; -use crate::utils::channel::GitInfo; use crate::utils::exec::{BootstrapCommand, command}; use crate::utils::helpers::{add_dylib_path, exe, t}; use crate::{Compiler, FileType, Kind, Mode, gha}; @@ -278,7 +277,7 @@ pub fn prepare_tool_cargo( cargo.env("CFG_VER_DESCRIPTION", description); } - let info = GitInfo::new(builder.config.omit_git_hash, &dir); + let info = builder.config.git_info(builder.config.omit_git_hash, &dir); if let Some(sha) = info.sha() { cargo.env("CFG_COMMIT_HASH", sha); } diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 1e9af68a92d..c6cfdb69356 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -11,7 +11,7 @@ use crate::utils::build_stamp; use crate::utils::helpers::{self, LldThreads, check_cfg_arg, linker_args, linker_flags}; use crate::{ BootstrapCommand, CLang, Compiler, Config, DocTests, DryRun, EXTRA_CHECK_CFGS, GitRepo, Mode, - TargetSelection, command, prepare_behaviour_dump_dir, t, + RemapScheme, TargetSelection, command, prepare_behaviour_dump_dir, t, }; /// Represents flag values in `String` form with whitespace delimiter to pass it to the compiler @@ -551,7 +551,7 @@ impl Builder<'_> { let libdir = self.rustc_libdir(compiler); let sysroot_str = sysroot.as_os_str().to_str().expect("sysroot should be UTF-8"); - if self.is_verbose() && !matches!(self.config.dry_run, DryRun::SelfCheck) { + if self.is_verbose() && !matches!(self.config.get_dry_run(), DryRun::SelfCheck) { println!("using sysroot {sysroot_str}"); } @@ -636,6 +636,15 @@ impl Builder<'_> { for (restricted_mode, name, values) in EXTRA_CHECK_CFGS { if restricted_mode.is_none() || *restricted_mode == Some(mode) { rustflags.arg(&check_cfg_arg(name, *values)); + + if *name == "bootstrap" { + // Cargo doesn't pass RUSTFLAGS to proc_macros: + // https://github.com/rust-lang/cargo/issues/4423 + // Thus, if we are on stage 0, we explicitly set `--cfg=bootstrap`. + // We also declare that the flag is expected, which we need to do to not + // get warnings about it being unexpected. + hostflags.arg(check_cfg_arg(name, *values)); + } } } @@ -645,13 +654,6 @@ impl Builder<'_> { if stage == 0 { hostflags.arg("--cfg=bootstrap"); } - // Cargo doesn't pass RUSTFLAGS to proc_macros: - // https://github.com/rust-lang/cargo/issues/4423 - // Thus, if we are on stage 0, we explicitly set `--cfg=bootstrap`. - // We also declare that the flag is expected, which we need to do to not - // get warnings about it being unexpected. - hostflags.arg("-Zunstable-options"); - hostflags.arg("--check-cfg=cfg(bootstrap)"); // FIXME: It might be better to use the same value for both `RUSTFLAGS` and `RUSTDOCFLAGS`, // but this breaks CI. At the very least, stage0 `rustdoc` needs `--cfg bootstrap`. See @@ -920,13 +922,46 @@ impl Builder<'_> { hostflags.arg(format!("-Ctarget-feature={sign}crt-static")); } - if let Some(map_to) = self.build.debuginfo_map_to(GitRepo::Rustc) { - let map = format!("{}={}", self.build.src.display(), map_to); - cargo.env("RUSTC_DEBUGINFO_MAP", map); + // `rustc` needs to know the remapping scheme, in order to know how to reverse it (unremap) + // later. Two env vars are set and made available to the compiler + // + // - `CFG_VIRTUAL_RUST_SOURCE_BASE_DIR`: `rust-src` remap scheme (`NonCompiler`) + // - `CFG_VIRTUAL_RUSTC_DEV_SOURCE_BASE_DIR`: `rustc-dev` remap scheme (`Compiler`) + // + // Keep this scheme in sync with `rustc_metadata::rmeta::decoder`'s + // `try_to_translate_virtual_to_real`. + // + // `RUSTC_DEBUGINFO_MAP` is used to pass through to the underlying rustc + // `--remap-path-prefix`. + match mode { + Mode::Rustc | Mode::Codegen => { + if let Some(ref map_to) = + self.build.debuginfo_map_to(GitRepo::Rustc, RemapScheme::NonCompiler) + { + cargo.env("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR", map_to); + } - // `rustc` needs to know the virtual `/rustc/$hash` we're mapping to, - // in order to opportunistically reverse it later. - cargo.env("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR", map_to); + if let Some(ref map_to) = + self.build.debuginfo_map_to(GitRepo::Rustc, RemapScheme::Compiler) + { + // When building compiler sources, we want to apply the compiler remap scheme. + cargo.env( + "RUSTC_DEBUGINFO_MAP", + format!("{}={}", self.build.src.display(), map_to), + ); + cargo.env("CFG_VIRTUAL_RUSTC_DEV_SOURCE_BASE_DIR", map_to); + } + } + Mode::Std | Mode::ToolBootstrap | Mode::ToolRustc | Mode::ToolStd => { + if let Some(ref map_to) = + self.build.debuginfo_map_to(GitRepo::Rustc, RemapScheme::NonCompiler) + { + cargo.env( + "RUSTC_DEBUGINFO_MAP", + format!("{}={}", self.build.src.display(), map_to), + ); + } + } } if self.config.rust_remap_debuginfo { diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 19b79bfe818..8b1520de3a8 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -22,6 +22,7 @@ use crate::core::config::flags::Subcommand; use crate::core::config::{DryRun, TargetSelection}; use crate::utils::cache::Cache; use crate::utils::exec::{BootstrapCommand, command}; +use crate::utils::execution_context::ExecutionContext; use crate::utils::helpers::{self, LldThreads, add_dylib_path, exe, libdir, linker_args, t}; use crate::{Build, Crate, trace}; @@ -442,13 +443,15 @@ impl StepDescription { fn is_excluded(&self, builder: &Builder<'_>, pathset: &PathSet) -> bool { if builder.config.skip.iter().any(|e| pathset.has(e, builder.kind)) { - if !matches!(builder.config.dry_run, DryRun::SelfCheck) { + if !matches!(builder.config.get_dry_run(), DryRun::SelfCheck) { println!("Skipping {pathset:?} because it is excluded"); } return true; } - if !builder.config.skip.is_empty() && !matches!(builder.config.dry_run, DryRun::SelfCheck) { + if !builder.config.skip.is_empty() + && !matches!(builder.config.get_dry_run(), DryRun::SelfCheck) + { builder.verbose(|| { println!( "{:?} not skipped for {:?} -- not in {:?}", @@ -1633,4 +1636,14 @@ impl<'a> Builder<'a> { self.info(&format!("{err}\n")); } } + + pub fn exec_ctx(&self) -> &ExecutionContext { + &self.config.exec_ctx + } +} + +impl<'a> AsRef<ExecutionContext> for Builder<'a> { + fn as_ref(&self) -> &ExecutionContext { + self.exec_ctx() + } } diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index baa22fc7f72..a264d772c56 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -22,7 +22,7 @@ fn configure_with_args(cmd: &[String], host: &[&str], target: &[&str]) -> Config let mut config = Config::parse(Flags::parse(cmd)); // don't save toolstates config.save_toolstates = None; - config.dry_run = DryRun::SelfCheck; + config.set_dry_run(DryRun::SelfCheck); // Ignore most submodules, since we don't need them for a dry run, and the // tests run much faster without them. diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index a92d58ef9e8..860aa120326 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -24,7 +24,7 @@ use std::{cmp, env, fs}; use build_helper::ci::CiEnv; use build_helper::exit; -use build_helper::git::{GitConfig, PathFreshness, check_path_modifications, output_result}; +use build_helper::git::{GitConfig, PathFreshness, check_path_modifications}; use serde::Deserialize; #[cfg(feature = "tracing")] use tracing::{instrument, span}; @@ -47,8 +47,10 @@ use crate::core::config::{ }; use crate::core::download::is_download_ci_available; use crate::utils::channel; +use crate::utils::exec::command; +use crate::utils::execution_context::ExecutionContext; use crate::utils::helpers::exe; -use crate::{Command, GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, output, t}; +use crate::{GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, t}; /// Each path in this list is considered "allowed" in the `download-rustc="if-unchanged"` logic. /// This means they can be modified and changes to these paths should never trigger a compiler build @@ -129,7 +131,6 @@ pub struct Config { pub jobs: Option<u32>, pub cmd: Subcommand, pub incremental: bool, - pub dry_run: DryRun, pub dump_bootstrap_shims: bool, /// Arguments appearing after `--` to be forwarded to tools, /// e.g. `--fix-broken` or test arguments. @@ -304,6 +305,8 @@ pub struct Config { /// This is mostly for RA as building the stage1 compiler to check the library tree /// on each code change might be too much for some computers. pub skip_std_check_if_no_download_rustc: bool, + + pub exec_ctx: ExecutionContext, } impl Config { @@ -360,6 +363,14 @@ impl Config { } } + pub fn set_dry_run(&mut self, dry_run: DryRun) { + self.exec_ctx.set_dry_run(dry_run); + } + + pub fn get_dry_run(&self) -> &DryRun { + self.exec_ctx.get_dry_run() + } + #[cfg_attr( feature = "tracing", instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::parse", skip_all) @@ -382,6 +393,11 @@ impl Config { get_toml: impl Fn(&Path) -> Result<TomlConfig, toml::de::Error>, ) -> Config { let mut config = Config::default_opts(); + let mut exec_ctx = ExecutionContext::new(); + exec_ctx.set_verbose(flags.verbose); + exec_ctx.set_fail_fast(flags.cmd.fail_fast()); + + config.exec_ctx = exec_ctx; // Set flags. config.paths = std::mem::take(&mut flags.paths); @@ -410,7 +426,7 @@ impl Config { config.on_fail = flags.on_fail; config.cmd = flags.cmd; config.incremental = flags.incremental; - config.dry_run = if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled }; + config.set_dry_run(if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled }); config.dump_bootstrap_shims = flags.dump_bootstrap_shims; config.keep_stage = flags.keep_stage; config.keep_stage_std = flags.keep_stage_std; @@ -440,14 +456,9 @@ impl Config { // has already been (kinda-cross-)compiled to Windows land, we require a normal Windows path. cmd.arg("rev-parse").arg("--show-cdup"); // Discard stderr because we expect this to fail when building from a tarball. - let output = cmd - .as_command_mut() - .stderr(std::process::Stdio::null()) - .output() - .ok() - .and_then(|output| if output.status.success() { Some(output) } else { None }); - if let Some(output) = output { - let git_root_relative = String::from_utf8(output.stdout).unwrap(); + let output = cmd.allow_failure().run_capture_stdout(&config); + if output.is_success() { + let git_root_relative = output.stdout(); // We need to canonicalize this path to make sure it uses backslashes instead of forward slashes, // and to resolve any relative components. let git_root = env::current_dir() @@ -542,7 +553,7 @@ impl Config { build.cargo = build.cargo.take().or(std::env::var_os("CARGO").map(|p| p.into())); } - if GitInfo::new(false, &config.src).is_from_tarball() && toml.profile.is_none() { + if config.git_info(false, &config.src).is_from_tarball() && toml.profile.is_none() { toml.profile = Some("dist".into()); } @@ -749,7 +760,12 @@ impl Config { }; config.initial_sysroot = t!(PathBuf::from_str( - output(Command::new(&config.initial_rustc).args(["--print", "sysroot"])).trim() + command(&config.initial_rustc) + .args(["--print", "sysroot"]) + .run_always() + .run_capture_stdout(&config) + .stdout() + .trim() )); config.initial_cargo_clippy = cargo_clippy; @@ -845,19 +861,21 @@ impl Config { let default = config.channel == "dev"; config.omit_git_hash = toml.rust.as_ref().and_then(|r| r.omit_git_hash).unwrap_or(default); - config.rust_info = GitInfo::new(config.omit_git_hash, &config.src); - config.cargo_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/cargo")); + config.rust_info = config.git_info(config.omit_git_hash, &config.src); + config.cargo_info = + config.git_info(config.omit_git_hash, &config.src.join("src/tools/cargo")); config.rust_analyzer_info = - GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer")); + config.git_info(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer")); config.clippy_info = - GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/clippy")); - config.miri_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/miri")); + config.git_info(config.omit_git_hash, &config.src.join("src/tools/clippy")); + config.miri_info = + config.git_info(config.omit_git_hash, &config.src.join("src/tools/miri")); config.rustfmt_info = - GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rustfmt")); + config.git_info(config.omit_git_hash, &config.src.join("src/tools/rustfmt")); config.enzyme_info = - GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/enzyme")); - config.in_tree_llvm_info = GitInfo::new(false, &config.src.join("src/llvm-project")); - config.in_tree_gcc_info = GitInfo::new(false, &config.src.join("src/gcc")); + config.git_info(config.omit_git_hash, &config.src.join("src/tools/enzyme")); + config.in_tree_llvm_info = config.git_info(false, &config.src.join("src/llvm-project")); + config.in_tree_gcc_info = config.git_info(false, &config.src.join("src/gcc")); config.vendor = vendor.unwrap_or( config.rust_info.is_from_tarball() @@ -1017,28 +1035,13 @@ impl Config { } pub fn dry_run(&self) -> bool { - match self.dry_run { - DryRun::Disabled => false, - DryRun::SelfCheck | DryRun::UserSelected => true, - } + self.exec_ctx.dry_run() } pub fn is_explicit_stage(&self) -> bool { self.explicit_stage_from_cli || self.explicit_stage_from_config } - /// Runs a command, printing out nice contextual information if it fails. - /// Exits if the command failed to execute at all, otherwise returns its - /// `status.success()`. - #[deprecated = "use `Builder::try_run` instead where possible"] - pub(crate) fn try_run(&self, cmd: &mut Command) -> Result<(), ()> { - if self.dry_run() { - return Ok(()); - } - self.verbose(|| println!("running: {cmd:?}")); - build_helper::util::try_run(cmd, self.is_verbose()) - } - pub(crate) fn test_args(&self) -> Vec<&str> { let mut test_args = match self.cmd { Subcommand::Test { ref test_args, .. } @@ -1072,7 +1075,7 @@ impl Config { let mut git = helpers::git(Some(&self.src)); git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap())); - output(git.as_command_mut()) + git.run_capture_stdout(self).stdout() } /// Bootstrap embeds a version number into the name of shared libraries it uploads in CI. @@ -1260,9 +1263,7 @@ impl Config { /// Runs a function if verbosity is greater than 0 pub fn verbose(&self, f: impl Fn()) { - if self.is_verbose() { - f() - } + self.exec_ctx.verbose(f); } pub fn any_sanitizers_to_build(&self) -> bool { @@ -1324,7 +1325,7 @@ impl Config { // NOTE: The check for the empty directory is here because when running x.py the first time, // the submodule won't be checked out. Check it out now so we can build it. - if !GitInfo::new(false, &absolute_path).is_managed_git_subrepository() + if !self.git_info(false, &absolute_path).is_managed_git_subrepository() && !helpers::dir_is_empty(&absolute_path) { return; @@ -1343,16 +1344,16 @@ impl Config { }; // Determine commit checked out in submodule. - let checked_out_hash = output(submodule_git().args(["rev-parse", "HEAD"]).as_command_mut()); + let checked_out_hash = + submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(self).stdout(); let checked_out_hash = checked_out_hash.trim_end(); // Determine commit that the submodule *should* have. - let recorded = output( - helpers::git(Some(&self.src)) - .run_always() - .args(["ls-tree", "HEAD"]) - .arg(relative_path) - .as_command_mut(), - ); + let recorded = helpers::git(Some(&self.src)) + .run_always() + .args(["ls-tree", "HEAD"]) + .arg(relative_path) + .run_capture_stdout(self) + .stdout(); let actual_hash = recorded .split_whitespace() @@ -1376,21 +1377,20 @@ impl Config { let update = |progress: bool| { // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository, // even though that has no relation to the upstream for the submodule. - let current_branch = output_result( - helpers::git(Some(&self.src)) - .allow_failure() - .run_always() - .args(["symbolic-ref", "--short", "HEAD"]) - .as_command_mut(), - ) - .map(|b| b.trim().to_owned()); + let current_branch = helpers::git(Some(&self.src)) + .allow_failure() + .run_always() + .args(["symbolic-ref", "--short", "HEAD"]) + .run_capture(self); let mut git = helpers::git(Some(&self.src)).allow_failure(); git.run_always(); - if let Ok(branch) = current_branch { + if current_branch.is_success() { // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name. // This syntax isn't accepted by `branch.{branch}`. Strip it. - let branch = branch.strip_prefix("heads/").unwrap_or(&branch); + let branch = current_branch.stdout(); + let branch = branch.trim(); + let branch = branch.strip_prefix("heads/").unwrap_or(branch); git.arg("-c").arg(format!("branch.{branch}.remote=origin")); } git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]); @@ -1435,7 +1435,8 @@ impl Config { return; } - let stage0_output = output(Command::new(program_path).arg("--version")); + let stage0_output = + command(program_path).arg("--version").run_capture_stdout(self).stdout(); let mut stage0_output = stage0_output.lines().next().unwrap().split(' '); let stage0_name = stage0_output.next().unwrap(); @@ -1741,4 +1742,18 @@ impl Config { _ => !self.is_system_llvm(target), } } + + pub fn exec_ctx(&self) -> &ExecutionContext { + &self.exec_ctx + } + + pub fn git_info(&self, omit_git_hash: bool, dir: &Path) -> GitInfo { + GitInfo::new(omit_git_hash, dir, self) + } +} + +impl AsRef<ExecutionContext> for Config { + fn as_ref(&self) -> &ExecutionContext { + &self.exec_ctx + } } diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index 30617f58d43..bc4fa73a362 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -209,7 +209,8 @@ impl Flags { HelpVerboseOnly::try_parse_from(normalize_args(args)) { println!("NOTE: updating submodules before printing available paths"); - let config = Config::parse(Self::parse(&[String::from("build")])); + let flags = Self::parse(&[String::from("build")]); + let config = Config::parse(flags); let build = Build::new(config); let paths = Builder::get_help(&build, subcommand); if let Some(s) = paths { diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index 81f95f356a5..bb18fa802af 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -321,11 +321,11 @@ pub fn check_incompatible_options_for_ci_rustc( rpath, channel, description, - incremental, default_linker, std_features, // Rest of the options can simply be ignored. + incremental: _, debug: _, codegen_units: _, codegen_units_std: _, @@ -389,7 +389,6 @@ pub fn check_incompatible_options_for_ci_rustc( warn!(current_rust_config.channel, channel, "rust"); warn!(current_rust_config.description, description, "rust"); - warn!(current_rust_config.incremental, incremental, "rust"); Ok(()) } diff --git a/src/bootstrap/src/core/config/toml/target.rs b/src/bootstrap/src/core/config/toml/target.rs index 7f074d1b25e..b8fc3e74c35 100644 --- a/src/bootstrap/src/core/config/toml/target.rs +++ b/src/bootstrap/src/core/config/toml/target.rs @@ -84,7 +84,7 @@ pub struct Target { impl Target { pub fn from_triple(triple: &str) -> Self { let mut target: Self = Default::default(); - if triple.contains("-none") || triple.contains("nvptx") || triple.contains("switch") { + if !build_helper::targets::target_supports_std(triple) { target.no_std = true; } if triple.contains("emscripten") { diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs index ba00b405c61..f349b9a87ed 100644 --- a/src/bootstrap/src/core/download.rs +++ b/src/bootstrap/src/core/download.rs @@ -3,7 +3,6 @@ use std::ffi::OsString; use std::fs::{self, File}; use std::io::{BufRead, BufReader, BufWriter, ErrorKind, Write}; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; use std::sync::OnceLock; use xz2::bufread::XzDecoder; @@ -16,14 +15,7 @@ use crate::{Config, t}; static SHOULD_FIX_BINS_AND_DYLIBS: OnceLock<bool> = OnceLock::new(); -/// `Config::try_run` wrapper for this module to avoid warnings on `try_run`, since we don't have access to a `builder` yet. -fn try_run(config: &Config, cmd: &mut Command) -> Result<(), ()> { - #[expect(deprecated)] - config.try_run(cmd) -} - -fn extract_curl_version(out: &[u8]) -> semver::Version { - let out = String::from_utf8_lossy(out); +fn extract_curl_version(out: String) -> semver::Version { // The output should look like this: "curl <major>.<minor>.<patch> ..." out.lines() .next() @@ -32,18 +24,21 @@ fn extract_curl_version(out: &[u8]) -> semver::Version { .unwrap_or(semver::Version::new(1, 0, 0)) } -fn curl_version() -> semver::Version { - let mut curl = Command::new("curl"); +fn curl_version(config: &Config) -> semver::Version { + let mut curl = command("curl"); curl.arg("-V"); - let Ok(out) = curl.output() else { return semver::Version::new(1, 0, 0) }; - let out = out.stdout; - extract_curl_version(&out) + let curl = curl.run_capture_stdout(config); + if curl.is_failure() { + return semver::Version::new(1, 0, 0); + } + let output = curl.stdout(); + extract_curl_version(output) } /// Generic helpers that are useful anywhere in bootstrap. impl Config { pub fn is_verbose(&self) -> bool { - self.verbose > 0 + self.exec_ctx.is_verbose() } pub(crate) fn create<P: AsRef<Path>>(&self, path: P, s: &str) { @@ -85,20 +80,14 @@ impl Config { /// on NixOS fn should_fix_bins_and_dylibs(&self) -> bool { let val = *SHOULD_FIX_BINS_AND_DYLIBS.get_or_init(|| { - match Command::new("uname").arg("-s").stderr(Stdio::inherit()).output() { - Err(_) => return false, - Ok(output) if !output.status.success() => return false, - Ok(output) => { - let mut os_name = output.stdout; - if os_name.last() == Some(&b'\n') { - os_name.pop(); - } - if os_name != b"Linux" { - return false; - } - } + let uname = command("uname").allow_failure().arg("-s").run_capture_stdout(self); + if uname.is_failure() { + return false; + } + let output = uname.stdout(); + if !output.starts_with("Linux") { + return false; } - // If the user has asked binaries to be patched for Nix, then // don't check for NixOS or `/lib`. // NOTE: this intentionally comes after the Linux check: @@ -173,23 +162,18 @@ impl Config { ]; } "; - nix_build_succeeded = try_run( - self, - Command::new("nix-build").args([ - Path::new("-E"), - Path::new(NIX_EXPR), - Path::new("-o"), - &nix_deps_dir, - ]), - ) - .is_ok(); + nix_build_succeeded = command("nix-build") + .allow_failure() + .args([Path::new("-E"), Path::new(NIX_EXPR), Path::new("-o"), &nix_deps_dir]) + .run_capture_stdout(self) + .is_success(); nix_deps_dir }); if !nix_build_succeeded { return; } - let mut patchelf = Command::new(nix_deps_dir.join("bin/patchelf")); + let mut patchelf = command(nix_deps_dir.join("bin/patchelf")); patchelf.args(&[ OsString::from("--add-rpath"), OsString::from(t!(fs::canonicalize(nix_deps_dir)).join("lib")), @@ -200,8 +184,8 @@ impl Config { let dynamic_linker = t!(fs::read_to_string(dynamic_linker_path)); patchelf.args(["--set-interpreter", dynamic_linker.trim_end()]); } - - let _ = try_run(self, patchelf.arg(fname)); + patchelf.arg(fname); + let _ = patchelf.allow_failure().run_capture_stdout(self); } fn download_file(&self, url: &str, dest_path: &Path, help_on_error: &str) { @@ -267,7 +251,7 @@ impl Config { curl.arg("--progress-bar"); } // --retry-all-errors was added in 7.71.0, don't use it if curl is old. - if curl_version() >= semver::Version::new(7, 71, 0) { + if curl_version(self) >= semver::Version::new(7, 71, 0) { curl.arg("--retry-all-errors"); } curl.arg(url); @@ -275,7 +259,7 @@ impl Config { if self.build.contains("windows-msvc") { eprintln!("Fallback to PowerShell"); for _ in 0..3 { - if try_run(self, Command::new("PowerShell.exe").args([ + let powershell = command("PowerShell.exe").allow_failure().args([ "/nologo", "-Command", "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;", @@ -283,9 +267,12 @@ impl Config { "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')", url, tempfile.to_str().expect("invalid UTF-8 not supported with powershell downloads"), ), - ])).is_err() { + ]).run_capture_stdout(self); + + if powershell.is_failure() { return; } + eprintln!("\nspurious failure, trying again"); } } diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index af4ec679d08..b7bbd294f0e 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -34,6 +34,8 @@ pub struct Finder { // Targets can be removed from this list once they are present in the stage0 compiler (usually by updating the beta compiler of the bootstrap). const STAGE0_MISSING_TARGETS: &[&str] = &[ // just a dummy comment so the list doesn't get onelined + "loongarch32-unknown-none", + "loongarch32-unknown-none-softfloat", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM @@ -362,7 +364,7 @@ than building it. // Cygwin. The Cygwin build does not have generators for Visual // Studio, so detect that here and error. let out = - command("cmake").arg("--help").run_always().run_capture_stdout(build).stdout(); + command("cmake").arg("--help").run_always().run_capture_stdout(&build).stdout(); if !out.contains("Visual Studio") { panic!( " diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 07772b8932d..88d5d026820 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -32,6 +32,7 @@ use cc::Tool; use termcolor::{ColorChoice, StandardStream, WriteColor}; use utils::build_stamp::BuildStamp; use utils::channel::GitInfo; +use utils::execution_context::ExecutionContext; use crate::core::builder; use crate::core::builder::Kind; @@ -81,7 +82,10 @@ const LLD_FILE_NAMES: &[&str] = &["ld.lld", "ld64.lld", "lld-link", "wasm-ld"]; /// (Mode restriction, config name, config values (if any)) #[expect(clippy::type_complexity)] // It's fine for hard-coded list and type is explained above. const EXTRA_CHECK_CFGS: &[(Option<Mode>, &str, Option<&[&'static str]>)] = &[ - (None, "bootstrap", None), + (Some(Mode::Rustc), "bootstrap", None), + (Some(Mode::Codegen), "bootstrap", None), + (Some(Mode::ToolRustc), "bootstrap", None), + (Some(Mode::ToolStd), "bootstrap", None), (Some(Mode::Rustc), "llvm_enzyme", None), (Some(Mode::Codegen), "llvm_enzyme", None), (Some(Mode::ToolRustc), "llvm_enzyme", None), @@ -195,7 +199,6 @@ pub struct Build { crates: HashMap<String, Crate>, crate_paths: HashMap<PathBuf, String>, is_sudo: bool, - delayed_failures: RefCell<Vec<String>>, prerelease_version: Cell<Option<u32>>, #[cfg(feature = "build-metrics")] @@ -272,6 +275,16 @@ impl Mode { } } +/// When `rust.rust_remap_debuginfo` is requested, the compiler needs to know how to +/// opportunistically unremap compiler vs non-compiler sources. We use two schemes, +/// [`RemapScheme::Compiler`] and [`RemapScheme::NonCompiler`]. +pub enum RemapScheme { + /// The [`RemapScheme::Compiler`] scheme will remap to `/rustc-dev/{hash}`. + Compiler, + /// The [`RemapScheme::NonCompiler`] scheme will remap to `/rustc/{hash}`. + NonCompiler, +} + #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum CLang { C, @@ -456,7 +469,6 @@ impl Build { crates: HashMap::new(), crate_paths: HashMap::new(), is_sudo, - delayed_failures: RefCell::new(Vec::new()), prerelease_version: Cell::new(None), #[cfg(feature = "build-metrics")] @@ -616,7 +628,7 @@ impl Build { return; } - if GitInfo::new(false, Path::new(submodule)).is_managed_git_subrepository() { + if config.git_info(false, Path::new(submodule)).is_managed_git_subrepository() { config.update_submodule(submodule); } } @@ -671,7 +683,7 @@ impl Build { #[cfg(feature = "tracing")] let _sanity_check_span = span!(tracing::Level::DEBUG, "(1) executing dry-run sanity-check").entered(); - self.config.dry_run = DryRun::SelfCheck; + self.config.set_dry_run(DryRun::SelfCheck); let builder = builder::Builder::new(self); builder.execute_cli(); } @@ -681,7 +693,7 @@ impl Build { #[cfg(feature = "tracing")] let _actual_run_span = span!(tracing::Level::DEBUG, "(2) executing actual run").entered(); - self.config.dry_run = DryRun::Disabled; + self.config.set_dry_run(DryRun::Disabled); let builder = builder::Builder::new(self); builder.execute_cli(); } @@ -697,14 +709,7 @@ impl Build { debug!("checking for postponed test failures from `test --no-fail-fast`"); // Check for postponed failures from `test --no-fail-fast`. - let failures = self.delayed_failures.borrow(); - if !failures.is_empty() { - eprintln!("\n{} command(s) did not execute successfully:\n", failures.len()); - for failure in failures.iter() { - eprintln!(" - {failure}\n"); - } - exit!(1); - } + self.config.exec_ctx().report_failures_and_exit(); #[cfg(feature = "build-metrics")] self.metrics.persist(self); @@ -940,133 +945,6 @@ impl Build { }) } - /// Execute a command and return its output. - /// Note: Ideally, you should use one of the BootstrapCommand::run* functions to - /// execute commands. They internally call this method. - #[track_caller] - fn run( - &self, - command: &mut BootstrapCommand, - stdout: OutputMode, - stderr: OutputMode, - ) -> CommandOutput { - command.mark_as_executed(); - if self.config.dry_run() && !command.run_always { - return CommandOutput::default(); - } - - #[cfg(feature = "tracing")] - let _run_span = trace_cmd!(command); - - let created_at = command.get_created_location(); - let executed_at = std::panic::Location::caller(); - - self.verbose(|| { - println!("running: {command:?} (created at {created_at}, executed at {executed_at})") - }); - - let cmd = command.as_command_mut(); - cmd.stdout(stdout.stdio()); - cmd.stderr(stderr.stdio()); - - let output = cmd.output(); - - use std::fmt::Write; - - let mut message = String::new(); - let output: CommandOutput = match output { - // Command has succeeded - Ok(output) if output.status.success() => { - CommandOutput::from_output(output, stdout, stderr) - } - // Command has started, but then it failed - Ok(output) => { - writeln!( - message, - r#" -Command {command:?} did not execute successfully. -Expected success, got {} -Created at: {created_at} -Executed at: {executed_at}"#, - output.status, - ) - .unwrap(); - - let output: CommandOutput = CommandOutput::from_output(output, stdout, stderr); - - // If the output mode is OutputMode::Capture, we can now print the output. - // If it is OutputMode::Print, then the output has already been printed to - // stdout/stderr, and we thus don't have anything captured to print anyway. - if stdout.captures() { - writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap(); - } - if stderr.captures() { - writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap(); - } - output - } - // The command did not even start - Err(e) => { - writeln!( - message, - "\n\nCommand {command:?} did not execute successfully.\ - \nIt was not possible to execute the command: {e:?}" - ) - .unwrap(); - CommandOutput::did_not_start(stdout, stderr) - } - }; - - let fail = |message: &str, output: CommandOutput| -> ! { - if self.is_verbose() { - println!("{message}"); - } else { - let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present()); - // If the command captures output, the user would not see any indication that - // it has failed. In this case, print a more verbose error, since to provide more - // context. - if stdout.is_some() || stderr.is_some() { - if let Some(stdout) = - output.stdout_if_present().take_if(|s| !s.trim().is_empty()) - { - println!("STDOUT:\n{stdout}\n"); - } - if let Some(stderr) = - output.stderr_if_present().take_if(|s| !s.trim().is_empty()) - { - println!("STDERR:\n{stderr}\n"); - } - println!("Command {command:?} has failed. Rerun with -v to see more details."); - } else { - println!("Command has failed. Rerun with -v to see more details."); - } - } - exit!(1); - }; - - if !output.is_success() { - match command.failure_behavior { - BehaviorOnFailure::DelayFail => { - if self.fail_fast { - fail(&message, output); - } - - let mut failures = self.delayed_failures.borrow_mut(); - failures.push(message); - } - BehaviorOnFailure::Exit => { - fail(&message, output); - } - BehaviorOnFailure::Ignore => { - // If failures are allowed, either the error has been printed already - // (OutputMode::Print) or the user used a capture output mode and wants to - // handle the error output on their own. - } - } - } - output - } - /// Check if verbosity is greater than the `level` pub fn is_verbose_than(&self, level: usize) -> bool { self.verbosity > level @@ -1080,7 +958,7 @@ Executed at: {executed_at}"#, } fn info(&self, msg: &str) { - match self.config.dry_run { + match self.config.get_dry_run() { DryRun::SelfCheck => (), DryRun::Disabled | DryRun::UserSelected => { println!("{msg}"); @@ -1203,7 +1081,7 @@ Executed at: {executed_at}"#, #[track_caller] fn group(&self, msg: &str) -> Option<gha::Group> { - match self.config.dry_run { + match self.config.get_dry_run() { DryRun::SelfCheck => None, DryRun::Disabled | DryRun::UserSelected => Some(gha::group(msg)), } @@ -1217,7 +1095,7 @@ Executed at: {executed_at}"#, }) } - fn debuginfo_map_to(&self, which: GitRepo) -> Option<String> { + fn debuginfo_map_to(&self, which: GitRepo, remap_scheme: RemapScheme) -> Option<String> { if !self.config.rust_remap_debuginfo { return None; } @@ -1225,7 +1103,24 @@ Executed at: {executed_at}"#, match which { GitRepo::Rustc => { let sha = self.rust_sha().unwrap_or(&self.version); - Some(format!("/rustc/{sha}")) + + match remap_scheme { + RemapScheme::Compiler => { + // For compiler sources, remap via `/rustc-dev/{sha}` to allow + // distinguishing between compiler sources vs library sources, since + // `rustc-dev` dist component places them under + // `$sysroot/lib/rustlib/rustc-src/rust` as opposed to `rust-src`'s + // `$sysroot/lib/rustlib/src/rust`. + // + // Keep this scheme in sync with `rustc_metadata::rmeta::decoder`'s + // `try_to_translate_virtual_to_real`. + Some(format!("/rustc-dev/{sha}")) + } + RemapScheme::NonCompiler => { + // For non-compiler sources, use `/rustc/{sha}` remapping scheme. + Some(format!("/rustc/{sha}")) + } + } } GitRepo::Llvm => Some(String::from("/rustc/llvm")), } @@ -1292,7 +1187,7 @@ Executed at: {executed_at}"#, base.push("-fno-omit-frame-pointer".into()); } - if let Some(map_to) = self.debuginfo_map_to(which) { + if let Some(map_to) = self.debuginfo_map_to(which, RemapScheme::NonCompiler) { let map = format!("{}={}", self.src.display(), map_to); let cc = self.cc(target); if cc.ends_with("clang") || cc.ends_with("gcc") { @@ -2015,6 +1910,16 @@ to download LLVM rather than building it. stream.reset().unwrap(); result } + + pub fn exec_ctx(&self) -> &ExecutionContext { + &self.config.exec_ctx + } +} + +impl AsRef<ExecutionContext> for Build { + fn as_ref(&self) -> &ExecutionContext { + &self.config.exec_ctx + } } #[cfg(unix)] diff --git a/src/bootstrap/src/utils/channel.rs b/src/bootstrap/src/utils/channel.rs index 4a9ecc7a4f8..38f250af42f 100644 --- a/src/bootstrap/src/utils/channel.rs +++ b/src/bootstrap/src/utils/channel.rs @@ -8,6 +8,7 @@ use std::fs; use std::path::Path; +use super::execution_context::ExecutionContext; use super::helpers; use crate::Build; use crate::utils::helpers::{start_process, t}; @@ -34,7 +35,7 @@ pub struct Info { } impl GitInfo { - pub fn new(omit_git_hash: bool, dir: &Path) -> GitInfo { + pub fn new(omit_git_hash: bool, dir: &Path, exec_ctx: impl AsRef<ExecutionContext>) -> GitInfo { // See if this even begins to look like a git dir if !dir.join(".git").exists() { match read_commit_info_file(dir) { @@ -43,10 +44,12 @@ impl GitInfo { } } - // Make sure git commands work - match helpers::git(Some(dir)).arg("rev-parse").as_command_mut().output() { - Ok(ref out) if out.status.success() => {} - _ => return GitInfo::Absent, + let mut git_command = helpers::git(Some(dir)); + git_command.arg("rev-parse"); + let output = git_command.allow_failure().run_capture(exec_ctx); + + if output.is_failure() { + return GitInfo::Absent; } // If we're ignoring the git info, we don't actually need to collect it, just make sure this diff --git a/src/bootstrap/src/utils/exec.rs b/src/bootstrap/src/utils/exec.rs index 64e46f10563..f297300e34a 100644 --- a/src/bootstrap/src/utils/exec.rs +++ b/src/bootstrap/src/utils/exec.rs @@ -11,7 +11,7 @@ use std::process::{Command, CommandArgs, CommandEnvs, ExitStatus, Output, Stdio} use build_helper::ci::CiEnv; use build_helper::drop_bomb::DropBomb; -use crate::Build; +use super::execution_context::ExecutionContext; /// What should be done when the command fails. #[derive(Debug, Copy, Clone)] @@ -125,7 +125,6 @@ impl BootstrapCommand { Self { failure_behavior: BehaviorOnFailure::DelayFail, ..self } } - #[expect(dead_code)] pub fn fail_fast(self) -> Self { Self { failure_behavior: BehaviorOnFailure::Exit, ..self } } @@ -143,20 +142,20 @@ impl BootstrapCommand { /// Run the command, while printing stdout and stderr. /// Returns true if the command has succeeded. #[track_caller] - pub fn run(&mut self, builder: &Build) -> bool { - builder.run(self, OutputMode::Print, OutputMode::Print).is_success() + pub fn run(&mut self, exec_ctx: impl AsRef<ExecutionContext>) -> bool { + exec_ctx.as_ref().run(self, OutputMode::Print, OutputMode::Print).is_success() } /// Run the command, while capturing and returning all its output. #[track_caller] - pub fn run_capture(&mut self, builder: &Build) -> CommandOutput { - builder.run(self, OutputMode::Capture, OutputMode::Capture) + pub fn run_capture(&mut self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput { + exec_ctx.as_ref().run(self, OutputMode::Capture, OutputMode::Capture) } /// Run the command, while capturing and returning stdout, and printing stderr. #[track_caller] - pub fn run_capture_stdout(&mut self, builder: &Build) -> CommandOutput { - builder.run(self, OutputMode::Capture, OutputMode::Print) + pub fn run_capture_stdout(&mut self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput { + exec_ctx.as_ref().run(self, OutputMode::Capture, OutputMode::Print) } /// Provides access to the stdlib Command inside. @@ -280,7 +279,6 @@ impl CommandOutput { !self.is_success() } - #[expect(dead_code)] pub fn status(&self) -> Option<ExitStatus> { match self.status { CommandStatus::Finished(status) => Some(status), diff --git a/src/bootstrap/src/utils/execution_context.rs b/src/bootstrap/src/utils/execution_context.rs new file mode 100644 index 00000000000..a5e1e9bcc07 --- /dev/null +++ b/src/bootstrap/src/utils/execution_context.rs @@ -0,0 +1,204 @@ +//! Shared execution context for running bootstrap commands. +//! +//! This module provides the [`ExecutionContext`] type, which holds global configuration +//! relevant during the execution of commands in bootstrap. This includes dry-run +//! mode, verbosity level, and behavior on failure. +use std::sync::{Arc, Mutex}; + +use crate::core::config::DryRun; +#[cfg(feature = "tracing")] +use crate::trace_cmd; +use crate::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode, exit}; + +#[derive(Clone, Default)] +pub struct ExecutionContext { + dry_run: DryRun, + verbose: u8, + pub fail_fast: bool, + delayed_failures: Arc<Mutex<Vec<String>>>, +} + +impl ExecutionContext { + pub fn new() -> Self { + ExecutionContext::default() + } + + pub fn dry_run(&self) -> bool { + match self.dry_run { + DryRun::Disabled => false, + DryRun::SelfCheck | DryRun::UserSelected => true, + } + } + + pub fn get_dry_run(&self) -> &DryRun { + &self.dry_run + } + + pub fn verbose(&self, f: impl Fn()) { + if self.is_verbose() { + f() + } + } + + pub fn is_verbose(&self) -> bool { + self.verbose > 0 + } + + pub fn fail_fast(&self) -> bool { + self.fail_fast + } + + pub fn set_dry_run(&mut self, value: DryRun) { + self.dry_run = value; + } + + pub fn set_verbose(&mut self, value: u8) { + self.verbose = value; + } + + pub fn set_fail_fast(&mut self, value: bool) { + self.fail_fast = value; + } + + pub fn add_to_delay_failure(&self, message: String) { + self.delayed_failures.lock().unwrap().push(message); + } + + pub fn report_failures_and_exit(&self) { + let failures = self.delayed_failures.lock().unwrap(); + if failures.is_empty() { + return; + } + eprintln!("\n{} command(s) did not execute successfully:\n", failures.len()); + for failure in &*failures { + eprintln!(" - {failure}"); + } + exit!(1); + } + + /// Execute a command and return its output. + /// Note: Ideally, you should use one of the BootstrapCommand::run* functions to + /// execute commands. They internally call this method. + #[track_caller] + pub fn run( + &self, + command: &mut BootstrapCommand, + stdout: OutputMode, + stderr: OutputMode, + ) -> CommandOutput { + command.mark_as_executed(); + if self.dry_run() && !command.run_always { + return CommandOutput::default(); + } + + #[cfg(feature = "tracing")] + let _run_span = trace_cmd!(command); + + let created_at = command.get_created_location(); + let executed_at = std::panic::Location::caller(); + + self.verbose(|| { + println!("running: {command:?} (created at {created_at}, executed at {executed_at})") + }); + + let cmd = command.as_command_mut(); + cmd.stdout(stdout.stdio()); + cmd.stderr(stderr.stdio()); + + let output = cmd.output(); + + use std::fmt::Write; + + let mut message = String::new(); + let output: CommandOutput = match output { + // Command has succeeded + Ok(output) if output.status.success() => { + CommandOutput::from_output(output, stdout, stderr) + } + // Command has started, but then it failed + Ok(output) => { + writeln!( + message, + r#" +Command {command:?} did not execute successfully. +Expected success, got {} +Created at: {created_at} +Executed at: {executed_at}"#, + output.status, + ) + .unwrap(); + + let output: CommandOutput = CommandOutput::from_output(output, stdout, stderr); + + // If the output mode is OutputMode::Capture, we can now print the output. + // If it is OutputMode::Print, then the output has already been printed to + // stdout/stderr, and we thus don't have anything captured to print anyway. + if stdout.captures() { + writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap(); + } + if stderr.captures() { + writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap(); + } + output + } + // The command did not even start + Err(e) => { + writeln!( + message, + "\n\nCommand {command:?} did not execute successfully.\ + \nIt was not possible to execute the command: {e:?}" + ) + .unwrap(); + CommandOutput::did_not_start(stdout, stderr) + } + }; + + let fail = |message: &str, output: CommandOutput| -> ! { + if self.is_verbose() { + println!("{message}"); + } else { + let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present()); + // If the command captures output, the user would not see any indication that + // it has failed. In this case, print a more verbose error, since to provide more + // context. + if stdout.is_some() || stderr.is_some() { + if let Some(stdout) = + output.stdout_if_present().take_if(|s| !s.trim().is_empty()) + { + println!("STDOUT:\n{stdout}\n"); + } + if let Some(stderr) = + output.stderr_if_present().take_if(|s| !s.trim().is_empty()) + { + println!("STDERR:\n{stderr}\n"); + } + println!("Command {command:?} has failed. Rerun with -v to see more details."); + } else { + println!("Command has failed. Rerun with -v to see more details."); + } + } + exit!(1); + }; + + if !output.is_success() { + match command.failure_behavior { + BehaviorOnFailure::DelayFail => { + if self.fail_fast { + fail(&message, output); + } + + self.add_to_delay_failure(message); + } + BehaviorOnFailure::Exit => { + fail(&message, output); + } + BehaviorOnFailure::Ignore => { + // If failures are allowed, either the error has been printed already + // (OutputMode::Print) or the user used a capture output mode and wants to + // handle the error output on their own. + } + } + } + output + } +} diff --git a/src/bootstrap/src/utils/mod.rs b/src/bootstrap/src/utils/mod.rs index 169fcec303e..5a0b90801e7 100644 --- a/src/bootstrap/src/utils/mod.rs +++ b/src/bootstrap/src/utils/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod cc_detect; pub(crate) mod change_tracker; pub(crate) mod channel; pub(crate) mod exec; +pub(crate) mod execution_context; pub(crate) mod helpers; pub(crate) mod job; pub(crate) mod render_tests; diff --git a/src/bootstrap/src/utils/render_tests.rs b/src/bootstrap/src/utils/render_tests.rs index 418f3ff975d..77e645a9e3c 100644 --- a/src/bootstrap/src/utils/render_tests.rs +++ b/src/bootstrap/src/utils/render_tests.rs @@ -43,8 +43,7 @@ pub(crate) fn try_run_tests( if builder.fail_fast { crate::exit!(1); } else { - let mut failures = builder.delayed_failures.borrow_mut(); - failures.push(format!("{cmd:?}")); + builder.config.exec_ctx().add_to_delay_failure(format!("{cmd:?}")); false } } else { diff --git a/src/build_helper/src/lib.rs b/src/build_helper/src/lib.rs index 1f5cf723641..05de8fd2d42 100644 --- a/src/build_helper/src/lib.rs +++ b/src/build_helper/src/lib.rs @@ -6,6 +6,7 @@ pub mod fs; pub mod git; pub mod metrics; pub mod stage0_parser; +pub mod targets; pub mod util; /// The default set of crates for opt-dist to collect LLVM profiles. diff --git a/src/build_helper/src/targets.rs b/src/build_helper/src/targets.rs new file mode 100644 index 00000000000..cccc413368b --- /dev/null +++ b/src/build_helper/src/targets.rs @@ -0,0 +1,11 @@ +// FIXME(#142296): this hack is because there is no reliable way (yet) to determine whether a given +// target supports std. In the long-term, we should try to implement a way to *reliably* determine +// target (std) metadata. +// +// NOTE: this is pulled out to `build_helpers` to share this hack between `bootstrap` and +// `compiletest`. +pub fn target_supports_std(target_tuple: &str) -> bool { + !(target_tuple.contains("-none") + || target_tuple.contains("nvptx") + || target_tuple.contains("switch")) +} diff --git a/src/ci/citool/src/jobs.rs b/src/ci/citool/src/jobs.rs index 2884ae08ea8..81e002edb15 100644 --- a/src/ci/citool/src/jobs.rs +++ b/src/ci/citool/src/jobs.rs @@ -161,6 +161,8 @@ pub enum RunType { TryJob { job_patterns: Option<Vec<String>> }, /// Merge attempt workflow AutoJob, + /// Fake job only used for sharing Github Actions cache. + MasterJob, } /// Maximum number of custom try jobs that can be requested in a single @@ -210,6 +212,7 @@ fn calculate_jobs( (jobs, "try", &db.envs.try_env) } RunType::AutoJob => (db.auto_jobs.clone(), "auto", &db.envs.auto_env), + RunType::MasterJob => return Ok(vec![]), }; let jobs = substitute_github_vars(jobs.clone()) .context("Failed to substitute GitHub context variables in jobs")?; @@ -262,7 +265,7 @@ pub fn calculate_job_matrix( eprintln!("Run type: {run_type:?}"); let jobs = calculate_jobs(&run_type, &db, channel)?; - if jobs.is_empty() { + if jobs.is_empty() && !matches!(run_type, RunType::MasterJob) { return Err(anyhow::anyhow!("Computed job list is empty")); } @@ -270,6 +273,7 @@ pub fn calculate_job_matrix( RunType::PullRequest => "pr", RunType::TryJob { .. } => "try", RunType::AutoJob => "auto", + RunType::MasterJob => "master", }; eprintln!("Output"); diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs index bb73a5ef909..fe1b36673a1 100644 --- a/src/ci/citool/src/main.rs +++ b/src/ci/citool/src/main.rs @@ -47,6 +47,7 @@ impl GitHubContext { Some(RunType::TryJob { job_patterns: patterns }) } ("push", "refs/heads/auto") => Some(RunType::AutoJob), + ("push", "refs/heads/master") => Some(RunType::MasterJob), _ => None, } } diff --git a/src/ci/citool/tests/jobs.rs b/src/ci/citool/tests/jobs.rs index fcdca899e06..83f2fc0ed1f 100644 --- a/src/ci/citool/tests/jobs.rs +++ b/src/ci/citool/tests/jobs.rs @@ -45,17 +45,31 @@ fn pr_jobs() { "#); } +#[test] +fn master_jobs() { + let stdout = get_matrix("push", "commit", "refs/heads/master"); + insta::assert_snapshot!(stdout, @r#" + jobs=[] + run_type=master + "#); +} + fn get_matrix(event_name: &str, commit_msg: &str, branch_ref: &str) -> String { - let output = Command::new("cargo") - .args(["run", "-q", "calculate-job-matrix", "--jobs-file", TEST_JOBS_YML_PATH]) + let path = std::env::var("PATH"); + let mut cmd = Command::new("cargo"); + cmd.args(["run", "-q", "calculate-job-matrix", "--jobs-file", TEST_JOBS_YML_PATH]) + .env_clear() .env("GITHUB_EVENT_NAME", event_name) .env("COMMIT_MESSAGE", commit_msg) .env("GITHUB_REF", branch_ref) .env("GITHUB_RUN_ID", "123") .env("GITHUB_RUN_ATTEMPT", "1") - .stdout(Stdio::piped()) - .output() - .expect("Failed to execute command"); + .stdout(Stdio::piped()); + if let Ok(path) = path { + cmd.env("PATH", path); + } + + let output = cmd.output().expect("Failed to execute command"); let stdout = String::from_utf8(output.stdout).unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); diff --git a/src/ci/docker/host-x86_64/mingw-check-2/Dockerfile b/src/ci/docker/host-x86_64/mingw-check-2/Dockerfile index a1d04bd984c..ce18a181d31 100644 --- a/src/ci/docker/host-x86_64/mingw-check-2/Dockerfile +++ b/src/ci/docker/host-x86_64/mingw-check-2/Dockerfile @@ -36,4 +36,7 @@ ENV SCRIPT \ RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library && \ mkdir -p /checkout/obj/staging/doc && \ cp -r build/x86_64-unknown-linux-gnu/doc /checkout/obj/staging && \ - RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library/test + RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library/test && \ + # The BOOTSTRAP_TRACING flag is added to verify whether the + # bootstrap process compiles successfully with this flag enabled. + BOOTSTRAP_TRACING=1 python3 ../x.py --help diff --git a/src/ci/docker/scripts/rfl-build.sh b/src/ci/docker/scripts/rfl-build.sh index fa18f67583f..c5992891398 100755 --- a/src/ci/docker/scripts/rfl-build.sh +++ b/src/ci/docker/scripts/rfl-build.sh @@ -2,8 +2,7 @@ set -euo pipefail -# https://github.com/Rust-for-Linux/linux/issues/1163 -LINUX_VERSION=3ca02fc80cc4fdac63aaa6796642f1e07be591d6 +LINUX_VERSION=v6.16-rc1 # Build rustc, rustdoc, cargo, clippy-driver and rustfmt ../x.py build --stage 2 library rustdoc clippy rustfmt diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index b6b2792d0ec..43c77d1ddf7 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -128,6 +128,11 @@ pr: <<: *job-linux-4c - name: mingw-check-tidy continue_on_error: true + free_disk: false + env: + # This submodule is expensive to checkout, and it should not be needed for + # tidy. This speeds up the PR CI job by ~1 minute. + SKIP_SUBMODULES: src/gcc <<: *job-linux-4c - name: x86_64-gnu-llvm-19 env: @@ -305,6 +310,10 @@ auto: - name: mingw-check-2 <<: *job-linux-4c + - name: mingw-check-tidy + free_disk: false + <<: *job-linux-4c + - name: test-various <<: *job-linux-4c diff --git a/src/ci/run.sh b/src/ci/run.sh index b6143af632d..e0c00dc1925 100755 --- a/src/ci/run.sh +++ b/src/ci/run.sh @@ -186,9 +186,11 @@ else # Download GCC from CI on test builders RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set gcc.download-ci-gcc=true" - if [ "$NO_DOWNLOAD_CI_RUSTC" = "" ]; then - RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.download-rustc=if-unchanged" - fi + # download-rustc seems to be broken on CI after the stage0 redesign + # Disable it until these issues are debugged and resolved +# 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/checkout-submodules.sh b/src/ci/scripts/checkout-submodules.sh index 5bb343241ae..3b646587dc2 100755 --- a/src/ci/scripts/checkout-submodules.sh +++ b/src/ci/scripts/checkout-submodules.sh @@ -55,7 +55,11 @@ for i in ${!modules[@]}; do bg_pids[${i}]=$! continue else + # Submodule paths contained in SKIP_SUBMODULES (comma-separated list) will not be + # checked out. + if [ -z "${SKIP_SUBMODULES:-}" ] || [[ ! ",$SKIP_SUBMODULES," = *",$module,"* ]]; then use_git="$use_git $module" + fi fi done retry sh -c "git submodule deinit -f $use_git && \ diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index f73a2811d5a..839076b809d 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -202,6 +202,7 @@ settings: `//@ needs-crate-type: cdylib, proc-macro` will cause the test to be ignored on `wasm32-unknown-unknown` target because the target does not support the `proc-macro` crate type. +- `needs-target-std` — ignores if target platform does not have std support. The following directives will check LLVM support: diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index e7dfaaf4fd5..559e4867bbb 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -33,7 +33,7 @@ All tier 1 targets with host tools support the full standard library. target | notes -------|------- [`aarch64-apple-darwin`](platform-support/apple-darwin.md) | ARM64 macOS (11.0+, Big Sur+) -`aarch64-unknown-linux-gnu` | ARM64 Linux (kernel 4.1, glibc 2.17+) +`aarch64-unknown-linux-gnu` | ARM64 Linux (kernel 4.1+, glibc 2.17+) `i686-pc-windows-msvc` | 32-bit MSVC (Windows 10+, Windows Server 2016+, Pentium 4) [^x86_32-floats-return-ABI] [^win32-msvc-alignment] `i686-unknown-linux-gnu` | 32-bit Linux (kernel 3.2+, glibc 2.17+, Pentium 4) [^x86_32-floats-return-ABI] [`x86_64-apple-darwin`](platform-support/apple-darwin.md) | 64-bit macOS (10.12+, Sierra+) @@ -91,20 +91,20 @@ target | notes `aarch64-pc-windows-msvc` | ARM64 Windows MSVC `aarch64-unknown-linux-musl` | ARM64 Linux with musl 1.2.3 [`aarch64-unknown-linux-ohos`](platform-support/openharmony.md) | ARM64 OpenHarmony -`arm-unknown-linux-gnueabi` | Armv6 Linux (kernel 3.2, glibc 2.17) -`arm-unknown-linux-gnueabihf` | Armv6 Linux, hardfloat (kernel 3.2, glibc 2.17) -`armv7-unknown-linux-gnueabihf` | Armv7-A Linux, hardfloat (kernel 3.2, glibc 2.17) +`arm-unknown-linux-gnueabi` | Armv6 Linux (kernel 3.2+, glibc 2.17) +`arm-unknown-linux-gnueabihf` | Armv6 Linux, hardfloat (kernel 3.2+, glibc 2.17) +`armv7-unknown-linux-gnueabihf` | Armv7-A Linux, hardfloat (kernel 3.2+, glibc 2.17) [`armv7-unknown-linux-ohos`](platform-support/openharmony.md) | Armv7-A OpenHarmony -[`loongarch64-unknown-linux-gnu`](platform-support/loongarch-linux.md) | LoongArch64 Linux, LP64D ABI (kernel 5.19, glibc 2.36) -[`loongarch64-unknown-linux-musl`](platform-support/loongarch-linux.md) | LoongArch64 Linux, LP64D ABI (kernel 5.19, musl 1.2.5) +[`loongarch64-unknown-linux-gnu`](platform-support/loongarch-linux.md) | LoongArch64 Linux, LP64D ABI (kernel 5.19+, glibc 2.36) +[`loongarch64-unknown-linux-musl`](platform-support/loongarch-linux.md) | LoongArch64 Linux, LP64D ABI (kernel 5.19+, musl 1.2.5) [`i686-pc-windows-gnu`](platform-support/windows-gnu.md) | 32-bit MinGW (Windows 10+, Windows Server 2016+, Pentium 4) [^x86_32-floats-return-ABI] [^win32-msvc-alignment] -`powerpc-unknown-linux-gnu` | PowerPC Linux (kernel 3.2, glibc 2.17) -`powerpc64-unknown-linux-gnu` | PPC64 Linux (kernel 3.2, glibc 2.17) -[`powerpc64le-unknown-linux-gnu`](platform-support/powerpc64le-unknown-linux-gnu.md) | PPC64LE Linux (kernel 3.10, glibc 2.17) -[`powerpc64le-unknown-linux-musl`](platform-support/powerpc64le-unknown-linux-musl.md) | PPC64LE Linux (kernel 4.19, musl 1.2.3) -[`riscv64gc-unknown-linux-gnu`](platform-support/riscv64gc-unknown-linux-gnu.md) | RISC-V Linux (kernel 4.20, glibc 2.29) -[`riscv64gc-unknown-linux-musl`](platform-support/riscv64gc-unknown-linux-musl.md) | RISC-V Linux (kernel 4.20, musl 1.2.3) -[`s390x-unknown-linux-gnu`](platform-support/s390x-unknown-linux-gnu.md) | S390x Linux (kernel 3.2, glibc 2.17) +`powerpc-unknown-linux-gnu` | PowerPC Linux (kernel 3.2+, glibc 2.17) +`powerpc64-unknown-linux-gnu` | PPC64 Linux (kernel 3.2+, glibc 2.17) +[`powerpc64le-unknown-linux-gnu`](platform-support/powerpc64le-unknown-linux-gnu.md) | PPC64LE Linux (kernel 3.10+, glibc 2.17) +[`powerpc64le-unknown-linux-musl`](platform-support/powerpc64le-unknown-linux-musl.md) | PPC64LE Linux (kernel 4.19+, musl 1.2.3) +[`riscv64gc-unknown-linux-gnu`](platform-support/riscv64gc-unknown-linux-gnu.md) | RISC-V Linux (kernel 4.20+, glibc 2.29) +[`riscv64gc-unknown-linux-musl`](platform-support/riscv64gc-unknown-linux-musl.md) | RISC-V Linux (kernel 4.20+, musl 1.2.3) +[`s390x-unknown-linux-gnu`](platform-support/s390x-unknown-linux-gnu.md) | S390x Linux (kernel 3.2+, glibc 2.17) [`x86_64-unknown-freebsd`](platform-support/freebsd.md) | 64-bit x86 FreeBSD [`x86_64-unknown-illumos`](platform-support/illumos.md) | illumos `x86_64-unknown-linux-musl` | 64-bit Linux with musl 1.2.3 @@ -158,16 +158,16 @@ target | std | notes [`arm64ec-pc-windows-msvc`](platform-support/arm64ec-pc-windows-msvc.md) | ✓ | Arm64EC Windows MSVC [`armebv7r-none-eabi`](platform-support/armv7r-none-eabi.md) | * | Bare Armv7-R, Big Endian [`armebv7r-none-eabihf`](platform-support/armv7r-none-eabi.md) | * | Bare Armv7-R, Big Endian, hardfloat -[`armv5te-unknown-linux-gnueabi`](platform-support/armv5te-unknown-linux-gnueabi.md) | ✓ | Armv5TE Linux (kernel 4.4, glibc 2.23) +[`armv5te-unknown-linux-gnueabi`](platform-support/armv5te-unknown-linux-gnueabi.md) | ✓ | Armv5TE Linux (kernel 4.4+, glibc 2.23) `armv5te-unknown-linux-musleabi` | ✓ | Armv5TE Linux with musl 1.2.3 [`armv7-linux-androideabi`](platform-support/android.md) | ✓ | Armv7-A Android -`armv7-unknown-linux-gnueabi` | ✓ | Armv7-A Linux (kernel 4.15, glibc 2.27) +`armv7-unknown-linux-gnueabi` | ✓ | Armv7-A Linux (kernel 4.15+, glibc 2.27) `armv7-unknown-linux-musleabi` | ✓ | Armv7-A Linux with musl 1.2.3 `armv7-unknown-linux-musleabihf` | ✓ | Armv7-A Linux with musl 1.2.3, hardfloat [`armv7a-none-eabi`](platform-support/arm-none-eabi.md) | * | Bare Armv7-A [`armv7r-none-eabi`](platform-support/armv7r-none-eabi.md) | * | Bare Armv7-R [`armv7r-none-eabihf`](platform-support/armv7r-none-eabi.md) | * | Bare Armv7-R, hardfloat -`i586-unknown-linux-gnu` | ✓ | 32-bit Linux (kernel 3.2, glibc 2.17, original Pentium) [^x86_32-floats-x87] +`i586-unknown-linux-gnu` | ✓ | 32-bit Linux (kernel 3.2+, glibc 2.17, original Pentium) [^x86_32-floats-x87] `i586-unknown-linux-musl` | ✓ | 32-bit Linux (musl 1.2.3, original Pentium) [^x86_32-floats-x87] [`i686-linux-android`](platform-support/android.md) | ✓ | 32-bit x86 Android ([Pentium 4 plus various extensions](https://developer.android.com/ndk/guides/abis.html#x86)) [^x86_32-floats-return-ABI] [`i686-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | ✓ | 32-bit x86 MinGW (Windows 10+, Pentium 4), LLVM ABI [^x86_32-floats-return-ABI] @@ -184,13 +184,13 @@ target | std | notes [`riscv32imc-unknown-none-elf`](platform-support/riscv32-unknown-none-elf.md) | * | Bare RISC-V (RV32IMC ISA) `riscv64gc-unknown-none-elf` | * | Bare RISC-V (RV64IMAFDC ISA) `riscv64imac-unknown-none-elf` | * | Bare RISC-V (RV64IMAC ISA) -`sparc64-unknown-linux-gnu` | ✓ | SPARC Linux (kernel 4.4, glibc 2.23) +`sparc64-unknown-linux-gnu` | ✓ | SPARC Linux (kernel 4.4+, glibc 2.23) [`thumbv6m-none-eabi`](platform-support/thumbv6m-none-eabi.md) | * | Bare Armv6-M [`thumbv7em-none-eabi`](platform-support/thumbv7em-none-eabi.md) | * | Bare Armv7E-M [`thumbv7em-none-eabihf`](platform-support/thumbv7em-none-eabi.md) | * | Bare Armv7E-M, hardfloat [`thumbv7m-none-eabi`](platform-support/thumbv7m-none-eabi.md) | * | Bare Armv7-M [`thumbv7neon-linux-androideabi`](platform-support/android.md) | ✓ | Thumb2-mode Armv7-A Android with NEON -`thumbv7neon-unknown-linux-gnueabihf` | ✓ | Thumb2-mode Armv7-A Linux with NEON (kernel 4.4, glibc 2.23) +`thumbv7neon-unknown-linux-gnueabihf` | ✓ | Thumb2-mode Armv7-A Linux with NEON (kernel 4.4+, glibc 2.23) [`thumbv8m.base-none-eabi`](platform-support/thumbv8m.base-none-eabi.md) | * | Bare Armv8-M Baseline [`thumbv8m.main-none-eabi`](platform-support/thumbv8m.main-none-eabi.md) | * | Bare Armv8-M Mainline [`thumbv8m.main-none-eabihf`](platform-support/thumbv8m.main-none-eabi.md) | * | Bare Armv8-M Mainline, hardfloat @@ -206,7 +206,7 @@ target | std | notes [`x86_64-linux-android`](platform-support/android.md) | ✓ | 64-bit x86 Android [`x86_64-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | ✓ | 64-bit x86 MinGW (Windows 10+), LLVM ABI [`x86_64-unknown-fuchsia`](platform-support/fuchsia.md) | ✓ | 64-bit x86 Fuchsia -`x86_64-unknown-linux-gnux32` | ✓ | 64-bit Linux (x32 ABI) (kernel 4.15, glibc 2.27) +`x86_64-unknown-linux-gnux32` | ✓ | 64-bit Linux (x32 ABI) (kernel 4.15+, glibc 2.27) [`x86_64-unknown-none`](platform-support/x86_64-unknown-none.md) | * | Freestanding/bare-metal x86_64, softfloat [`x86_64-unknown-redox`](platform-support/redox.md) | ✓ | Redox OS [`x86_64-unknown-uefi`](platform-support/unknown-uefi.md) | ? | 64-bit UEFI @@ -324,6 +324,8 @@ target | std | host | notes [`i686-win7-windows-msvc`](platform-support/win7-windows-msvc.md) | ✓ | | 32-bit Windows 7 support [^x86_32-floats-return-ABI] [^win32-msvc-alignment] [`i686-wrs-vxworks`](platform-support/vxworks.md) | ✓ | | [^x86_32-floats-return-ABI] [`loongarch64-unknown-linux-ohos`](platform-support/openharmony.md) | ✓ | | LoongArch64 OpenHarmony +[`loongarch32-unknown-none`](platform-support/loongarch-none.md) | * | LoongArch32 Bare-metal (ILP32D ABI) +[`loongarch32-unknown-none-softfloat`](platform-support/loongarch-none.md) | * | LoongArch32 Bare-metal (ILP32S ABI) [`m68k-unknown-linux-gnu`](platform-support/m68k-unknown-linux-gnu.md) | ? | | Motorola 680x0 Linux [`m68k-unknown-none-elf`](platform-support/m68k-unknown-none-elf.md) | | | Motorola 680x0 `mips-unknown-linux-gnu` | ✓ | ✓ | MIPS Linux (kernel 4.4, glibc 2.23) diff --git a/src/doc/rustc/src/platform-support/loongarch-none.md b/src/doc/rustc/src/platform-support/loongarch-none.md index a2bd6e5734c..7c3a7e6c8bc 100644 --- a/src/doc/rustc/src/platform-support/loongarch-none.md +++ b/src/doc/rustc/src/platform-support/loongarch-none.md @@ -1,13 +1,13 @@ # `loongarch*-unknown-none*` -**Tier: 2** +Freestanding/bare-metal LoongArch binaries in ELF format: firmware, kernels, etc. -Freestanding/bare-metal LoongArch64 binaries in ELF format: firmware, kernels, etc. - -| Target | Description | -|--------|-------------| -| `loongarch64-unknown-none` | LoongArch 64-bit, LP64D ABI (freestanding, hard-float) | -| `loongarch64-unknown-none-softfloat` | LoongArch 64-bit, LP64S ABI (freestanding, soft-float) | +| Target | Description | Tier | +|--------|-------------|------| +| `loongarch32-unknown-none` | LoongArch 32-bit, ILP32D ABI (freestanding, hard-float) | Tier 3 | +| `loongarch32-unknown-none-softfloat` | LoongArch 32-bit, ILP32S ABI (freestanding, soft-float) | Tier 3 | +| `loongarch64-unknown-none` | LoongArch 64-bit, LP64D ABI (freestanding, hard-float) | Tier 2 | +| `loongarch64-unknown-none-softfloat` | LoongArch 64-bit, LP64S ABI (freestanding, soft-float) | Tier 2 | ## Target maintainers @@ -29,13 +29,13 @@ additional CPU features via the `-C target-feature=` codegen options to rustc, o via the `#[target_feature]` mechanism within Rust code. By default, code generated with the soft-float target should run on any -LoongArch64 hardware, with the hard-float target additionally requiring an FPU; +LoongArch hardware, with the hard-float target additionally requiring an FPU; enabling additional target features may raise this baseline. Code generated with the targets will use the `medium` code model by default. You can change this using the `-C code-model=` option to rustc. -On `loongarch64-unknown-none*`, `extern "C"` uses the [architecture's standard calling convention][lapcs]. +On `loongarch*-unknown-none*`, `extern "C"` uses the [architecture's standard calling convention][lapcs]. [lapcs]: https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc @@ -52,6 +52,8 @@ list in `bootstrap.toml`: [build] build-stage = 1 target = [ + "loongarch32-unknown-none", + "loongarch32-unknown-none-softfloat", "loongarch64-unknown-none", "loongarch64-unknown-none-softfloat", ] @@ -64,13 +66,28 @@ As the targets support a variety of different environments and do not support ## Building Rust programs +### loongarch32-unknown-none* + +The `loongarch32-unknown-none*` targets are Tier 3, so you must build the Rust +compiler from source to use them. + +```sh +# target flag may be used with any cargo or rustc command +cargo build --target loongarch32-unknown-none +cargo build --target loongarch32-unknown-none-softfloat +``` + +### loongarch64-unknown-none* + Starting with Rust 1.74, precompiled artifacts are provided via `rustup`: ```sh # install cross-compile toolchain rustup target add loongarch64-unknown-none +rustup target add loongarch64-unknown-none-softfloat # target flag may be used with any cargo or rustc command cargo build --target loongarch64-unknown-none +cargo build --target loongarch64-unknown-none-softfloat ``` ## Cross-compilation toolchains and C code @@ -79,10 +96,10 @@ For cross builds, you will need an appropriate LoongArch C/C++ toolchain for linking, or if you want to compile C code along with Rust (such as for Rust crates with C dependencies). -Rust *may* be able to use an `loongarch64-unknown-linux-gnu-` toolchain with +Rust *may* be able to use an `loongarch{32,64}-unknown-linux-{gnu,musl}-` toolchain with appropriate standalone flags to build for this toolchain (depending on the assumptions of that toolchain, see below), or you may wish to use a separate -`loongarch64-unknown-none` toolchain. +`loongarch{32,64}-unknown-none` toolchain. On some LoongArch hosts that use ELF binaries, you *may* be able to use the host C toolchain, if it does not introduce assumptions about the host environment diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 138ac3c97f7..a91ea55bcae 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -11,7 +11,7 @@ use tracing::{debug, instrument}; use crate::clean::{ self, Lifetime, clean_generic_param_def, clean_middle_ty, clean_predicate, - clean_trait_ref_with_constraints, clean_ty_generics, simplify, + clean_trait_ref_with_constraints, clean_ty_generics_inner, simplify, }; use crate::core::DocContext; @@ -101,7 +101,7 @@ fn synthesize_auto_trait_impl<'tcx>( // Instead, we generate `impl !Send for Foo<T>`, which better // expresses the fact that `Foo<T>` never implements `Send`, // regardless of the choice of `T`. - let mut generics = clean_ty_generics( + let mut generics = clean_ty_generics_inner( cx, tcx.generics_of(item_def_id), ty::GenericPredicates::default(), diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs index 89245fee515..c889f52b789 100644 --- a/src/librustdoc/clean/blanket_impl.rs +++ b/src/librustdoc/clean/blanket_impl.rs @@ -90,11 +90,7 @@ pub(crate) fn synthesize_blanket_impls( stability: None, kind: clean::ImplItem(Box::new(clean::Impl { safety: hir::Safety::Safe, - generics: clean_ty_generics( - cx, - tcx.generics_of(impl_def_id), - tcx.explicit_predicates_of(impl_def_id), - ), + generics: clean_ty_generics(cx, impl_def_id), // FIXME(eddyb) compute both `trait_` and `for_` from // the post-inference `trait_ref`, as it's more accurate. trait_: Some(clean_trait_ref_with_constraints( diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index ebc276b38fb..a3762e4117d 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -508,6 +508,7 @@ impl fmt::Display for Display<'_> { (sym::target_arch, Some(arch)) => match arch.as_str() { "aarch64" => "AArch64", "arm" => "ARM", + "loongarch32" => "LoongArch LA32", "loongarch64" => "LoongArch LA64", "m68k" => "M68k", "csky" => "CSKY", diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 55a116a018a..9b5491310b4 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -264,16 +264,13 @@ pub(crate) fn build_trait(cx: &mut DocContext<'_>, did: DefId) -> clean::Trait { .map(|item| clean_middle_assoc_item(item, cx)) .collect(); - let predicates = cx.tcx.predicates_of(did); - let generics = clean_ty_generics(cx, cx.tcx.generics_of(did), predicates); - let generics = filter_non_trait_generics(did, generics); + let generics = clean_ty_generics(cx, did); let (generics, supertrait_bounds) = separate_self_bounds(generics); clean::Trait { def_id: did, generics, items: trait_items, bounds: supertrait_bounds } } fn build_trait_alias(cx: &mut DocContext<'_>, did: DefId) -> clean::TraitAlias { - let predicates = cx.tcx.predicates_of(did); - let generics = clean_ty_generics(cx, cx.tcx.generics_of(did), predicates); + let generics = clean_ty_generics(cx, did); let (generics, bounds) = separate_self_bounds(generics); clean::TraitAlias { generics, bounds } } @@ -281,8 +278,7 @@ fn build_trait_alias(cx: &mut DocContext<'_>, did: DefId) -> clean::TraitAlias { pub(super) fn build_function(cx: &mut DocContext<'_>, def_id: DefId) -> Box<clean::Function> { let sig = cx.tcx.fn_sig(def_id).instantiate_identity(); // The generics need to be cleaned before the signature. - let mut generics = - clean_ty_generics(cx, cx.tcx.generics_of(def_id), cx.tcx.explicit_predicates_of(def_id)); + let mut generics = clean_ty_generics(cx, def_id); let bound_vars = clean_bound_vars(sig.bound_vars()); // At the time of writing early & late-bound params are stored separately in rustc, @@ -311,30 +307,26 @@ pub(super) fn build_function(cx: &mut DocContext<'_>, def_id: DefId) -> Box<clea } fn build_enum(cx: &mut DocContext<'_>, did: DefId) -> clean::Enum { - let predicates = cx.tcx.explicit_predicates_of(did); - clean::Enum { - generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates), + generics: clean_ty_generics(cx, did), variants: cx.tcx.adt_def(did).variants().iter().map(|v| clean_variant_def(v, cx)).collect(), } } fn build_struct(cx: &mut DocContext<'_>, did: DefId) -> clean::Struct { - let predicates = cx.tcx.explicit_predicates_of(did); let variant = cx.tcx.adt_def(did).non_enum_variant(); clean::Struct { ctor_kind: variant.ctor_kind(), - generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates), + generics: clean_ty_generics(cx, did), fields: variant.fields.iter().map(|x| clean_middle_field(x, cx)).collect(), } } fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union { - let predicates = cx.tcx.explicit_predicates_of(did); let variant = cx.tcx.adt_def(did).non_enum_variant(); - let generics = clean_ty_generics(cx, cx.tcx.generics_of(did), predicates); + let generics = clean_ty_generics(cx, did); let fields = variant.fields.iter().map(|x| clean_middle_field(x, cx)).collect(); clean::Union { generics, fields } } @@ -344,14 +336,13 @@ fn build_type_alias( did: DefId, ret: &mut Vec<Item>, ) -> Box<clean::TypeAlias> { - let predicates = cx.tcx.explicit_predicates_of(did); let ty = cx.tcx.type_of(did).instantiate_identity(); let type_ = clean_middle_ty(ty::Binder::dummy(ty), cx, Some(did), None); let inner_type = clean_ty_alias_inner_type(ty, cx, ret); Box::new(clean::TypeAlias { type_, - generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates), + generics: clean_ty_generics(cx, did), inner_type, item_type: None, }) @@ -483,7 +474,6 @@ pub(crate) fn build_impl( } let document_hidden = cx.render_options.document_hidden; - let predicates = tcx.explicit_predicates_of(did); let (trait_items, generics) = match impl_item { Some(impl_) => ( impl_ @@ -549,9 +539,7 @@ pub(crate) fn build_impl( }) .map(|item| clean_middle_assoc_item(item, cx)) .collect::<Vec<_>>(), - clean::enter_impl_trait(cx, |cx| { - clean_ty_generics(cx, tcx.generics_of(did), predicates) - }), + clean::enter_impl_trait(cx, |cx| clean_ty_generics(cx, did)), ), }; let polarity = tcx.impl_polarity(did); @@ -713,8 +701,7 @@ pub(crate) fn print_inlined_const(tcx: TyCtxt<'_>, did: DefId) -> String { } fn build_const_item(cx: &mut DocContext<'_>, def_id: DefId) -> clean::Constant { - let mut generics = - clean_ty_generics(cx, cx.tcx.generics_of(def_id), cx.tcx.explicit_predicates_of(def_id)); + let mut generics = clean_ty_generics(cx, def_id); clean::simplify::move_bounds_to_generic_parameters(&mut generics); let ty = clean_middle_ty( ty::Binder::dummy(cx.tcx.type_of(def_id).instantiate_identity()), @@ -761,44 +748,6 @@ fn build_macro( } } -/// A trait's generics clause actually contains all of the predicates for all of -/// its associated types as well. We specifically move these clauses to the -/// associated types instead when displaying, so when we're generating the -/// generics for the trait itself we need to be sure to remove them. -/// We also need to remove the implied "recursive" Self: Trait bound. -/// -/// The inverse of this filtering logic can be found in the `Clean` -/// implementation for `AssociatedType` -fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean::Generics { - for pred in &mut g.where_predicates { - if let clean::WherePredicate::BoundPredicate { ty: clean::SelfTy, ref mut bounds, .. } = - *pred - { - bounds.retain(|bound| match bound { - clean::GenericBound::TraitBound(clean::PolyTrait { trait_, .. }, _) => { - trait_.def_id() != trait_did - } - _ => true, - }); - } - } - - g.where_predicates.retain(|pred| match pred { - clean::WherePredicate::BoundPredicate { - ty: - clean::QPath(box clean::QPathData { - self_type: clean::Generic(_), - trait_: Some(trait_), - .. - }), - bounds, - .. - } => !bounds.is_empty() && trait_.def_id() != trait_did, - _ => true, - }); - g -} - fn separate_self_bounds(mut g: clean::Generics) -> (clean::Generics, Vec<clean::GenericBound>) { let mut ty_bounds = Vec::new(); g.where_predicates.retain(|pred| match *pred { diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 7658e7ad35f..db4bcdaeb6c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -793,7 +793,11 @@ pub(crate) fn clean_generics<'tcx>( } } -fn clean_ty_generics<'tcx>( +fn clean_ty_generics<'tcx>(cx: &mut DocContext<'tcx>, def_id: DefId) -> Generics { + clean_ty_generics_inner(cx, cx.tcx.generics_of(def_id), cx.tcx.explicit_predicates_of(def_id)) +} + +fn clean_ty_generics_inner<'tcx>( cx: &mut DocContext<'tcx>, gens: &ty::Generics, preds: ty::GenericPredicates<'tcx>, @@ -1297,11 +1301,7 @@ pub(crate) fn clean_middle_assoc_item(assoc_item: &ty::AssocItem, cx: &mut DocCo None, ); - let mut generics = clean_ty_generics( - cx, - tcx.generics_of(assoc_item.def_id), - tcx.explicit_predicates_of(assoc_item.def_id), - ); + let mut generics = clean_ty_generics(cx, assoc_item.def_id); simplify::move_bounds_to_generic_parameters(&mut generics); match assoc_item.container { @@ -1389,7 +1389,7 @@ pub(crate) fn clean_middle_assoc_item(assoc_item: &ty::AssocItem, cx: &mut DocCo let bounds = tcx.explicit_item_bounds(assoc_item.def_id).iter_identity_copied(); predicates = tcx.arena.alloc_from_iter(bounds.chain(predicates.iter().copied())); } - let mut generics = clean_ty_generics( + let mut generics = clean_ty_generics_inner( cx, tcx.generics_of(assoc_item.def_id), ty::GenericPredicates { parent: None, predicates }, diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index f8209f2c8cd..4c53ea42793 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -113,6 +113,8 @@ static TARGETS: &[&str] = &[ "i686-unknown-uefi", "loongarch64-unknown-linux-gnu", "loongarch64-unknown-linux-musl", + "loongarch32-unknown-none", + "loongarch32-unknown-none-softfloat", "loongarch64-unknown-none", "loongarch64-unknown-none-softfloat", "m68k-unknown-linux-gnu", diff --git a/src/tools/cargo b/src/tools/cargo -Subproject 64a12460708cf146e16cc61f28aba5dc2463bbb +Subproject fc1518ef02b77327d70d4026b95ea719dd9b8c5 diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 4f93b498741..9b9d94bbead 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -495,6 +495,7 @@ impl Config { "arm64ec", "riscv32", "riscv64", + "loongarch32", "loongarch64", "s390x", // These targets require an additional asm_experimental_arch feature. diff --git a/src/tools/compiletest/src/directive-list.rs b/src/tools/compiletest/src/directive-list.rs index 5757e422ae2..2ecb4fc8652 100644 --- a/src/tools/compiletest/src/directive-list.rs +++ b/src/tools/compiletest/src/directive-list.rs @@ -73,6 +73,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-linux", "ignore-lldb", "ignore-llvm-version", + "ignore-loongarch32", "ignore-loongarch64", "ignore-macabi", "ignore-macos", @@ -165,6 +166,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "needs-subprocess", "needs-symlink", "needs-target-has-atomic", + "needs-target-std", "needs-threads", "needs-unwind", "needs-wasmtime", @@ -196,6 +198,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "only-i686-unknown-linux-gnu", "only-ios", "only-linux", + "only-loongarch32", "only-loongarch64", "only-loongarch64-unknown-linux-gnu", "only-macos", diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs index 2ace40c490b..b1165f4bb18 100644 --- a/src/tools/compiletest/src/header/needs.rs +++ b/src/tools/compiletest/src/header/needs.rs @@ -174,6 +174,11 @@ pub(super) fn handle_needs( condition: config.with_std_debug_assertions, ignore_reason: "ignored if std wasn't built with debug assertions", }, + Need { + name: "needs-target-std", + condition: build_helper::targets::target_supports_std(&config.target), + ignore_reason: "ignored if target does not support std", + }, ]; let (name, rest) = match ln.split_once([':', ' ']) { diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index e7e5ff0ab00..31b49b09bcd 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -945,3 +945,14 @@ fn test_ignore_auxiliary() { let config = cfg().build(); assert!(check_ignore(&config, "//@ ignore-auxiliary")); } + +#[test] +fn test_needs_target_std() { + // Cherry-picks two targets: + // 1. `x86_64-unknown-none`: Tier 2, intentionally never supports std. + // 2. `x86_64-unknown-linux-gnu`: Tier 1, always supports std. + let config = cfg().target("x86_64-unknown-none").build(); + assert!(check_ignore(&config, "//@ needs-target-std")); + let config = cfg().target("x86_64-unknown-linux-gnu").build(); + assert!(!check_ignore(&config, "//@ needs-target-std")); +} diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 6f4bd3eab51..192d4f444c2 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -555,7 +555,6 @@ dependencies = [ "tempfile", "tikv-jemalloc-sys", "ui_test", - "windows-sys", ] [[package]] diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index e4d7abdb0f7..a314488bb25 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -40,13 +40,6 @@ libc = "0.2" libffi = "4.0.0" libloading = "0.8" -[target.'cfg(target_family = "windows")'.dependencies] -windows-sys = { version = "0.59", features = [ - "Win32_Foundation", - "Win32_System_IO", - "Win32_Storage_FileSystem", -] } - [dev-dependencies] ui_test = "0.29.1" colored = "2" diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index de521393cd0..fc7942a49c0 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -396,18 +396,22 @@ to Miri failing to detect cases of undefined behavior in a program. * `-Zmiri-force-intrinsic-fallback` forces the use of the "fallback" body for all intrinsics that have one. This is useful to test the fallback bodies, but should not be used otherwise. It is **unsound** since the fallback body might not be checking for all UB. -* `-Zmiri-native-lib=<path to a shared object file>` is an experimental flag for providing support - for calling native functions from inside the interpreter via FFI. The flag is supported only on - Unix systems. Functions not provided by that file are still executed via the usual Miri shims. +* `-Zmiri-native-lib=<path to a shared object file or folder>` is an experimental flag for providing + support for calling native functions from inside the interpreter via FFI. The flag is supported + only on Unix systems. Functions not provided by that file are still executed via the usual Miri + shims. If a path to a directory is specified, all files in that directory are included + non-recursively. This flag can be passed multiple times to specify multiple files and/or + directories. **WARNING**: If an invalid/incorrect `.so` file is specified, this can cause Undefined Behavior in - Miri itself! And of course, Miri cannot do any checks on the actions taken by the native code. + Miri itself! And of course, Miri often cannot do any checks on the actions taken by the native code. Note that Miri has its own handling of file descriptors, so if you want to replace *some* functions working on file descriptors, you will have to replace *all* of them, or the two kinds of - file descriptors will be mixed up. This is **work in progress**; currently, only integer and - pointers arguments and return values are supported and memory allocated by the native code cannot - be accessed from Rust (only the other way around). Native code must not spawn threads that keep - running in the background after the call has returned to Rust and that access Rust-allocated - memory. Finally, the flag is **unsound** in the sense that Miri stops tracking details such as + file descriptors will be mixed up. + This is **work in progress**; currently, only integer and pointers arguments and return values are + supported and memory allocated by the native code cannot be accessed from Rust (only the other way + around). Native code must not spawn threads that keep running in the background after the call has + returned to Rust and that access Rust-allocated memory. + Finally, the flag is **unsound** in the sense that Miri stops tracking details such as initialization and provenance on memory shared with native code, so it is easily possible to write code that has UB which is missed by Miri. * `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program. @@ -458,6 +462,10 @@ to Miri failing to detect cases of undefined behavior in a program. This is much less likely with Stacked Borrows. Using Tree Borrows currently implies `-Zmiri-strict-provenance` because integer-to-pointer casts are not supported in this mode, but that may change in the future. +* `-Zmiri-tree-borrows-no-precise-interior-mut` makes Tree Borrows + track interior mutable data on the level of references instead of on the + byte-level as is done by default. Therefore, with this flag, Tree + Borrows will be more permissive. * `-Zmiri-force-page-size=<num>` overrides the default page size for an architecture, in multiples of 1k. `4` is default for most targets. This value should always be a power of 2 and nonzero. diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock index c1915ae617e..d37f8750bde 100644 --- a/src/tools/miri/cargo-miri/Cargo.lock +++ b/src/tools/miri/cargo-miri/Cargo.lock @@ -3,6 +3,15 @@ version = 4 [[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] name = "anyhow" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -207,12 +216,42 @@ dependencies = [ ] [[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] name = "rustc-build-sysroot" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10edc2e4393515193bd766e2f6c050b0536a68e56f2b6d56c07ababfdc114ff0" +checksum = "16d115ad7e26e0d1337f64ae6598f758194696afc2e9f34c8a6f24582529c3dc" dependencies = [ "anyhow", + "regex", "rustc_version", "tempfile", "walkdir", diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml index 5c579b2a77d..e08733959cc 100644 --- a/src/tools/miri/cargo-miri/Cargo.toml +++ b/src/tools/miri/cargo-miri/Cargo.toml @@ -18,7 +18,7 @@ directories = "6" rustc_version = "0.4" serde_json = "1.0.40" cargo_metadata = "0.19" -rustc-build-sysroot = "0.5.7" +rustc-build-sysroot = "0.5.8" # Enable some feature flags that dev-dependencies need but dependencies # do not. This makes `./miri install` after `./miri build` faster. diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 553d410b2bc..43632bf86a7 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -337c11e5932275e7d450c1f2e26f289f0ddfa717 +c6768de2d63de7a41124a0fb8fc78f9e26111c01 diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index 12a320b9676..4a038fe6487 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -132,7 +132,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { assert!(!matches!(info.kind, AllocKind::Dead)); // This allocation does not have a base address yet, pick or reuse one. - if this.machine.native_lib.is_some() { + if !this.machine.native_lib.is_empty() { // In native lib mode, we use the "real" address of the bytes for this allocation. // This ensures the interpreted program and native code have the same view of memory. let params = this.machine.get_default_alloc_params(); @@ -413,7 +413,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, MiriAllocBytes> { let this = self.eval_context_ref(); assert!(this.tcx.try_get_global_alloc(id).is_some()); - if this.machine.native_lib.is_some() { + if !this.machine.native_lib.is_empty() { // In native lib mode, MiriAllocBytes for global allocations are handled via `prepared_alloc_bytes`. // This additional call ensures that some `MiriAllocBytes` are always prepared, just in case // this function gets called before the first time `addr_from_alloc_id` gets called. diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 0121472d330..2faaec5a174 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -35,7 +35,7 @@ use std::sync::{Arc, Once}; use miri::{ BacktraceStyle, BorrowTrackerMethod, GenmcConfig, GenmcCtx, MiriConfig, MiriEntryFnType, - ProvenanceMode, RetagFields, ValidationMode, + ProvenanceMode, RetagFields, TreeBorrowsParams, ValidationMode, }; use rustc_abi::ExternAbi; use rustc_data_structures::sync; @@ -554,8 +554,21 @@ fn main() { } else if arg == "-Zmiri-disable-stacked-borrows" { miri_config.borrow_tracker = None; } else if arg == "-Zmiri-tree-borrows" { - miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows); + miri_config.borrow_tracker = + Some(BorrowTrackerMethod::TreeBorrows(TreeBorrowsParams { + precise_interior_mut: true, + })); miri_config.provenance_mode = ProvenanceMode::Strict; + } else if arg == "-Zmiri-tree-borrows-no-precise-interior-mut" { + match &mut miri_config.borrow_tracker { + Some(BorrowTrackerMethod::TreeBorrows(params)) => { + params.precise_interior_mut = false; + } + _ => + show_error!( + "`-Zmiri-tree-borrows` is required before `-Zmiri-tree-borrows-no-precise-interior-mut`" + ), + }; } else if arg == "-Zmiri-disable-data-race-detector" { miri_config.data_race_detector = false; miri_config.weak_memory_emulation = false; @@ -692,11 +705,18 @@ fn main() { }; } else if let Some(param) = arg.strip_prefix("-Zmiri-native-lib=") { let filename = param.to_string(); - if std::path::Path::new(&filename).exists() { - if let Some(other_filename) = miri_config.native_lib { - show_error!("-Zmiri-native-lib is already set to {}", other_filename.display()); + let file_path = std::path::Path::new(&filename); + if file_path.exists() { + // For directories, nonrecursively add all normal files inside + if let Ok(dir) = file_path.read_dir() { + for lib in dir.filter_map(|res| res.ok()) { + if lib.file_type().unwrap().is_file() { + miri_config.native_lib.push(lib.path().to_owned()); + } + } + } else { + miri_config.native_lib.push(filename.into()); } - miri_config.native_lib = Some(filename.into()); } else { show_error!("-Zmiri-native-lib `{}` does not exist", filename); } @@ -725,18 +745,19 @@ fn main() { } } // Tree Borrows implies strict provenance, and is not compatible with native calls. - if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows)) { + if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows { .. })) { if miri_config.provenance_mode != ProvenanceMode::Strict { show_error!( "Tree Borrows does not support integer-to-pointer casts, and hence requires strict provenance" ); } - if miri_config.native_lib.is_some() { + if !miri_config.native_lib.is_empty() { show_error!("Tree Borrows is not compatible with calling native functions"); } } + // Native calls and strict provenance are not compatible. - if miri_config.native_lib.is_some() && miri_config.provenance_mode == ProvenanceMode::Strict { + if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict { show_error!("strict provenance is not compatible with calling native functions"); } // You can set either one seed or many. diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs index b66c561d2b8..36c61053a32 100644 --- a/src/tools/miri/src/borrow_tracker/mod.rs +++ b/src/tools/miri/src/borrow_tracker/mod.rs @@ -226,7 +226,13 @@ pub enum BorrowTrackerMethod { /// Stacked Borrows, as implemented in borrow_tracker/stacked_borrows StackedBorrows, /// Tree borrows, as implemented in borrow_tracker/tree_borrows - TreeBorrows, + TreeBorrows(TreeBorrowsParams), +} + +/// Parameters that Tree Borrows can take. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TreeBorrowsParams { + pub precise_interior_mut: bool, } impl BorrowTrackerMethod { @@ -237,6 +243,13 @@ impl BorrowTrackerMethod { config.retag_fields, )) } + + pub fn get_tree_borrows_params(self) -> TreeBorrowsParams { + match self { + BorrowTrackerMethod::TreeBorrows(params) => params, + _ => panic!("can only be called when `BorrowTrackerMethod` is `TreeBorrows`"), + } + } } impl GlobalStateInner { @@ -252,7 +265,7 @@ impl GlobalStateInner { AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation( id, alloc_size, self, kind, machine, )))), - BorrowTrackerMethod::TreeBorrows => + BorrowTrackerMethod::TreeBorrows { .. } => AllocState::TreeBorrows(Box::new(RefCell::new(Tree::new_allocation( id, alloc_size, self, kind, machine, )))), @@ -271,7 +284,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method; match method { BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val), - BorrowTrackerMethod::TreeBorrows => this.tb_retag_ptr_value(kind, val), + BorrowTrackerMethod::TreeBorrows { .. } => this.tb_retag_ptr_value(kind, val), } } @@ -284,7 +297,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method; match method { BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place), - BorrowTrackerMethod::TreeBorrows => this.tb_retag_place_contents(kind, place), + BorrowTrackerMethod::TreeBorrows { .. } => this.tb_retag_place_contents(kind, place), } } @@ -293,7 +306,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method; match method { BorrowTrackerMethod::StackedBorrows => this.sb_protect_place(place), - BorrowTrackerMethod::TreeBorrows => this.tb_protect_place(place), + BorrowTrackerMethod::TreeBorrows { .. } => this.tb_protect_place(place), } } @@ -302,7 +315,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method; match method { BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag), - BorrowTrackerMethod::TreeBorrows => this.tb_expose_tag(alloc_id, tag), + BorrowTrackerMethod::TreeBorrows { .. } => this.tb_expose_tag(alloc_id, tag), } } @@ -319,7 +332,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.tcx.tcx.dcx().warn("Stacked Borrows does not support named pointers; `miri_pointer_name` is a no-op"); interp_ok(()) } - BorrowTrackerMethod::TreeBorrows => + BorrowTrackerMethod::TreeBorrows { .. } => this.tb_give_pointer_debug_name(ptr, nth_parent, name), } } @@ -333,7 +346,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let method = borrow_tracker.borrow().borrow_tracker_method; match method { BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id), - BorrowTrackerMethod::TreeBorrows => this.print_tree(alloc_id, show_unnamed), + BorrowTrackerMethod::TreeBorrows { .. } => this.print_tree(alloc_id, show_unnamed), } } diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 411ae89da90..ce8fe03ee47 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -312,8 +312,6 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let span = this.machine.current_span(); - let alloc_extra = this.get_alloc_extra(alloc_id)?; - let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut(); // Store initial permissions and their corresponding range. let mut perms_map: RangeMap<LocationState> = RangeMap::new( @@ -342,36 +340,83 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { assert!(new_perm.freeze_access); let protected = new_perm.protector.is_some(); - this.visit_freeze_sensitive(place, ptr_size, |range, frozen| { - has_unsafe_cell = has_unsafe_cell || !frozen; - - // We are only ever `Frozen` inside the frozen bits. - let (perm, access) = if frozen { + let precise_interior_mut = this + .machine + .borrow_tracker + .as_mut() + .unwrap() + .get_mut() + .borrow_tracker_method + .get_tree_borrows_params() + .precise_interior_mut; + + let default_perm = if !precise_interior_mut { + // NOTE: Using `ty_is_freeze` doesn't give the same result as going through the range + // and computing `has_unsafe_cell`. This is because of zero-sized `UnsafeCell`, for which + // `has_unsafe_cell` is false, but `!ty_is_freeze` is true. + let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env()); + let (perm, access) = if ty_is_freeze { (new_perm.freeze_perm, new_perm.freeze_access) } else { (new_perm.nonfreeze_perm, new_perm.nonfreeze_access) }; + let sifa = perm.strongest_idempotent_foreign_access(protected); + let new_loc = if access { + LocationState::new_accessed(perm, sifa) + } else { + LocationState::new_non_accessed(perm, sifa) + }; + + for (_loc_range, loc) in perms_map.iter_mut_all() { + *loc = new_loc; + } + + perm + } else { + this.visit_freeze_sensitive(place, ptr_size, |range, frozen| { + has_unsafe_cell = has_unsafe_cell || !frozen; - // Store initial permissions. - for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) { + // We are only ever `Frozen` inside the frozen bits. + let (perm, access) = if frozen { + (new_perm.freeze_perm, new_perm.freeze_access) + } else { + (new_perm.nonfreeze_perm, new_perm.nonfreeze_access) + }; let sifa = perm.strongest_idempotent_foreign_access(protected); // NOTE: Currently, `access` is false if and only if `perm` is Cell, so this `if` // doesn't not change whether any code is UB or not. We could just always use // `new_accessed` and everything would stay the same. But that seems conceptually // odd, so we keep the initial "accessed" bit of the `LocationState` in sync with whether // a read access is performed below. - if access { - *loc = LocationState::new_accessed(perm, sifa); + let new_loc = if access { + LocationState::new_accessed(perm, sifa) } else { - *loc = LocationState::new_non_accessed(perm, sifa); + LocationState::new_non_accessed(perm, sifa) + }; + + // Store initial permissions. + for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) { + *loc = new_loc; } - } - // Some reborrows incur a read access to the parent. - if access { + interp_ok(()) + })?; + + // Allow lazily writing to surrounding data if we found an `UnsafeCell`. + if has_unsafe_cell { new_perm.nonfreeze_perm } else { new_perm.freeze_perm } + }; + + let alloc_extra = this.get_alloc_extra(alloc_id)?; + let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut(); + + for (perm_range, perm) in perms_map.iter_mut_all() { + if perm.is_accessed() { + // Some reborrows incur a read access to the parent. // Adjust range to be relative to allocation start (rather than to `place`). - let mut range_in_alloc = range; - range_in_alloc.start += base_offset; + let range_in_alloc = AllocRange { + start: Size::from_bytes(perm_range.start) + base_offset, + size: Size::from_bytes(perm_range.end - perm_range.start), + }; tree_borrows.perform_access( orig_tag, @@ -382,7 +427,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; // Also inform the data race model (but only if any bytes are actually affected). - if range.size.bytes() > 0 { + if range_in_alloc.size.bytes() > 0 { if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() { data_race.read( alloc_id, @@ -394,8 +439,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } } - interp_ok(()) - })?; + } // Record the parent-child pair in the tree. tree_borrows.new_child( @@ -403,8 +447,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { orig_tag, new_tag, perms_map, - // Allow lazily writing to surrounding data if we found an `UnsafeCell`. - if has_unsafe_cell { new_perm.nonfreeze_perm } else { new_perm.freeze_perm }, + default_perm, protected, span, )?; diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 1728a9cfd6d..58bf163bfad 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -682,7 +682,10 @@ impl<'tcx> MiriMachine<'tcx> { ), ]; if self.borrow_tracker.as_ref().is_some_and(|b| { - matches!(b.borrow().borrow_tracker_method(), BorrowTrackerMethod::TreeBorrows) + matches!( + b.borrow().borrow_tracker_method(), + BorrowTrackerMethod::TreeBorrows { .. } + ) }) { v.push( note!("Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used") diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 5880e5fbc37..6f5f756e144 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -148,9 +148,8 @@ pub struct MiriConfig { pub report_progress: Option<u32>, /// Whether Stacked Borrows and Tree Borrows retagging should recurse into fields of datatypes. pub retag_fields: RetagFields, - /// The location of a shared object file to load when calling external functions - /// FIXME! consider allowing users to specify paths to multiple files, or to a directory - pub native_lib: Option<PathBuf>, + /// The location of the shared object files to load when calling external functions + pub native_lib: Vec<PathBuf>, /// Run a garbage collector for BorTags every N basic blocks. pub gc_interval: u32, /// The number of CPUs to be reported by miri. @@ -197,7 +196,7 @@ impl Default for MiriConfig { preemption_rate: 0.01, // 1% report_progress: None, retag_fields: RetagFields::Yes, - native_lib: None, + native_lib: vec![], gc_interval: 10_000, num_cpus: 1, page_size: None, diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index a4882a20148..9957e351ff1 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -3,17 +3,20 @@ mod atomic; mod simd; +use std::ops::Neg; + use rand::Rng; use rustc_abi::Size; -use rustc_apfloat::{Float, Round}; +use rustc_apfloat::ieee::{IeeeFloat, Semantics}; +use rustc_apfloat::{self, Float, Round}; use rustc_middle::mir; -use rustc_middle::ty::{self, FloatTy}; +use rustc_middle::ty::{self, FloatTy, ScalarInt}; use rustc_span::{Symbol, sym}; use self::atomic::EvalContextExt as _; use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count}; use self::simd::EvalContextExt as _; -use crate::math::apply_random_float_error_to_imm; +use crate::math::{IeeeExt, apply_random_float_error_ulp}; use crate::*; impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -187,31 +190,39 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; - // Using host floats (but it's fine, these operations do not have - // guaranteed precision). - let host = f.to_host(); - let res = match intrinsic_name { - "sinf32" => host.sin(), - "cosf32" => host.cos(), - "expf32" => host.exp(), - "exp2f32" => host.exp2(), - "logf32" => host.ln(), - "log10f32" => host.log10(), - "log2f32" => host.log2(), - _ => bug!(), - }; - let res = res.to_soft(); - // Apply a relative error of 16ULP to introduce some non-determinism - // simulating imprecise implementations and optimizations. - // FIXME: temporarily disabled as it breaks std tests. - // let res = apply_random_float_error_ulp( - // this, - // res, - // 4, // log2(16) - // ); + + let res = fixed_float_value(intrinsic_name, &[f]).unwrap_or_else(||{ + // Using host floats (but it's fine, these operations do not have + // guaranteed precision). + let host = f.to_host(); + let res = match intrinsic_name { + "sinf32" => host.sin(), + "cosf32" => host.cos(), + "expf32" => host.exp(), + "exp2f32" => host.exp2(), + "logf32" => host.ln(), + "log10f32" => host.log10(), + "log2f32" => host.log2(), + _ => bug!(), + }; + let res = res.to_soft(); + + // Apply a relative error of 4ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = apply_random_float_error_ulp( + this, + res, + 2, // log2(4) + ); + + // Clamp the result to the guaranteed range of this function according to the C standard, + // if any. + clamp_float_value(intrinsic_name, res) + }); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } + #[rustfmt::skip] | "sinf64" | "cosf64" @@ -223,28 +234,35 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; - // Using host floats (but it's fine, these operations do not have - // guaranteed precision). - let host = f.to_host(); - let res = match intrinsic_name { - "sinf64" => host.sin(), - "cosf64" => host.cos(), - "expf64" => host.exp(), - "exp2f64" => host.exp2(), - "logf64" => host.ln(), - "log10f64" => host.log10(), - "log2f64" => host.log2(), - _ => bug!(), - }; - let res = res.to_soft(); - // Apply a relative error of 16ULP to introduce some non-determinism - // simulating imprecise implementations and optimizations. - // FIXME: temporarily disabled as it breaks std tests. - // let res = apply_random_float_error_ulp( - // this, - // res, - // 4, // log2(16) - // ); + + let res = fixed_float_value(intrinsic_name, &[f]).unwrap_or_else(||{ + // Using host floats (but it's fine, these operations do not have + // guaranteed precision). + let host = f.to_host(); + let res = match intrinsic_name { + "sinf64" => host.sin(), + "cosf64" => host.cos(), + "expf64" => host.exp(), + "exp2f64" => host.exp2(), + "logf64" => host.ln(), + "log10f64" => host.log10(), + "log2f64" => host.log2(), + _ => bug!(), + }; + let res = res.to_soft(); + + // Apply a relative error of 4ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = apply_random_float_error_ulp( + this, + res, + 2, // log2(4) + ); + + // Clamp the result to the guaranteed range of this function according to the C standard, + // if any. + clamp_float_value(intrinsic_name, res) + }); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -302,43 +320,75 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "powf32" => { - // FIXME: apply random relative error but without altering behaviour of powf let [f1, f2] = check_intrinsic_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; - // Using host floats (but it's fine, this operation does not have guaranteed precision). - let res = f1.to_host().powf(f2.to_host()).to_soft(); + + let res = fixed_float_value(intrinsic_name, &[f1, f2]).unwrap_or_else(|| { + // Using host floats (but it's fine, this operation does not have guaranteed precision). + let res = f1.to_host().powf(f2.to_host()).to_soft(); + + // Apply a relative error of 4ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + apply_random_float_error_ulp( + this, res, 2, // log2(4) + ) + }); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; } "powf64" => { - // FIXME: apply random relative error but without altering behaviour of powf let [f1, f2] = check_intrinsic_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f64()?; let f2 = this.read_scalar(f2)?.to_f64()?; - // Using host floats (but it's fine, this operation does not have guaranteed precision). - let res = f1.to_host().powf(f2.to_host()).to_soft(); + + let res = fixed_float_value(intrinsic_name, &[f1, f2]).unwrap_or_else(|| { + // Using host floats (but it's fine, this operation does not have guaranteed precision). + let res = f1.to_host().powf(f2.to_host()).to_soft(); + + // Apply a relative error of 4ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + apply_random_float_error_ulp( + this, res, 2, // log2(4) + ) + }); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; } "powif32" => { - // FIXME: apply random relative error but without altering behaviour of powi let [f, i] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; let i = this.read_scalar(i)?.to_i32()?; - // Using host floats (but it's fine, this operation does not have guaranteed precision). - let res = f.to_host().powi(i).to_soft(); + + let res = fixed_powi_float_value(f, i).unwrap_or_else(|| { + // Using host floats (but it's fine, this operation does not have guaranteed precision). + let res = f.to_host().powi(i).to_soft(); + + // Apply a relative error of 4ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + apply_random_float_error_ulp( + this, res, 2, // log2(4) + ) + }); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } "powif64" => { - // FIXME: apply random relative error but without altering behaviour of powi let [f, i] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; let i = this.read_scalar(i)?.to_i32()?; - // Using host floats (but it's fine, this operation does not have guaranteed precision). - let res = f.to_host().powi(i).to_soft(); + + let res = fixed_powi_float_value(f, i).unwrap_or_else(|| { + // Using host floats (but it's fine, this operation does not have guaranteed precision). + let res = f.to_host().powi(i).to_soft(); + + // Apply a relative error of 4ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + apply_random_float_error_ulp( + this, res, 2, // log2(4) + ) + }); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -425,3 +475,97 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(EmulateItemResult::NeedsReturn) } } + +/// Applies a random ULP floating point error to `val` and returns the new value. +/// So if you want an X ULP error, `ulp_exponent` should be log2(X). +/// +/// Will fail if `val` is not a floating point number. +fn apply_random_float_error_to_imm<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, + val: ImmTy<'tcx>, + ulp_exponent: u32, +) -> InterpResult<'tcx, ImmTy<'tcx>> { + let scalar = val.to_scalar_int()?; + let res: ScalarInt = match val.layout.ty.kind() { + ty::Float(FloatTy::F16) => + apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(), + ty::Float(FloatTy::F32) => + apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(), + ty::Float(FloatTy::F64) => + apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(), + ty::Float(FloatTy::F128) => + apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(), + _ => bug!("intrinsic called with non-float input type"), + }; + + interp_ok(ImmTy::from_scalar_int(res, val.layout)) +} + +/// For the intrinsics: +/// - sinf32, sinf64 +/// - cosf32, cosf64 +/// - expf32, expf64, exp2f32, exp2f64 +/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64 +/// - powf32, powf64 +/// +/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard +/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error +/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying +/// implementation. Returns `None` if no specific value is guaranteed. +fn fixed_float_value<S: Semantics>( + intrinsic_name: &str, + args: &[IeeeFloat<S>], +) -> Option<IeeeFloat<S>> { + let one = IeeeFloat::<S>::one(); + match (intrinsic_name, args) { + // cos(+- 0) = 1 + ("cosf32" | "cosf64", [input]) if input.is_zero() => Some(one), + + // e^0 = 1 + ("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => Some(one), + + // 1^y = 1 for any y, even a NaN. + ("powf32" | "powf64", [base, _]) if *base == one => Some(one), + + // (-1)^(±INF) = 1 + ("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => Some(one), + + // FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate + // the NaN. We should return either 1 or the NaN non-deterministically here. + // But for now, just handle them all the same. + // x^(±0) = 1 for any x, even a NaN + ("powf32" | "powf64", [_, exp]) if exp.is_zero() => Some(one), + + // There are a lot of cases for fixed outputs according to the C Standard, but these are mainly INF or zero + // which are not affected by the applied error. + _ => None, + } +} + +/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the C standard +/// (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`. +fn fixed_powi_float_value<S: Semantics>(base: IeeeFloat<S>, exp: i32) -> Option<IeeeFloat<S>> { + match (base.category(), exp) { + // x^0 = 1, if x is not a Signaling NaN + // FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate + // the NaN. We should return either 1 or the NaN non-deterministically here. + // But for now, just handle them all the same. + (_, 0) => Some(IeeeFloat::<S>::one()), + + _ => None, + } +} + +/// Given an floating-point operation and a floating-point value, clamps the result to the output +/// range of the given operation. +fn clamp_float_value<S: Semantics>(intrinsic_name: &str, val: IeeeFloat<S>) -> IeeeFloat<S> { + match intrinsic_name { + // sin and cos: [-1, 1] + "sinf32" | "cosf32" | "sinf64" | "cosf64" => + val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()), + // exp: [0, +INF] + "expf32" | "exp2f32" | "expf64" | "exp2f64" => + IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO), + _ => val, + } +} diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 51ec19af52a..344e12e9fa3 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -16,6 +16,7 @@ #![feature(unqualified_local_imports)] #![feature(derive_coerce_pointee)] #![feature(arbitrary_self_types)] +#![feature(file_lock)] // Configure clippy and other lints #![allow( clippy::collapsible_else_if, @@ -113,7 +114,9 @@ pub use crate::borrow_tracker::stacked_borrows::{ EvalContextExt as _, Item, Permission, Stack, Stacks, }; pub use crate::borrow_tracker::tree_borrows::{EvalContextExt as _, Tree}; -pub use crate::borrow_tracker::{BorTag, BorrowTrackerMethod, EvalContextExt as _, RetagFields}; +pub use crate::borrow_tracker::{ + BorTag, BorrowTrackerMethod, EvalContextExt as _, RetagFields, TreeBorrowsParams, +}; pub use crate::clock::{Instant, MonotonicClock}; pub use crate::concurrency::cpu_affinity::MAX_CPUS; pub use crate::concurrency::data_race::{ diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 15b3653d7ae..b221dd85092 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -558,9 +558,9 @@ pub struct MiriMachine<'tcx> { /// Handle of the optional shared object file for native functions. #[cfg(unix)] - pub native_lib: Option<(libloading::Library, std::path::PathBuf)>, + pub native_lib: Vec<(libloading::Library, std::path::PathBuf)>, #[cfg(not(unix))] - pub native_lib: Option<!>, + pub native_lib: Vec<!>, /// Run a garbage collector for BorTags every N basic blocks. pub(crate) gc_interval: u32, @@ -720,7 +720,7 @@ impl<'tcx> MiriMachine<'tcx> { extern_statics: FxHashMap::default(), rng: RefCell::new(rng), #[cfg(target_os = "linux")] - allocator: if config.native_lib.is_some() { + allocator: if !config.native_lib.is_empty() { Some(Rc::new(RefCell::new(crate::alloc::isolated_alloc::IsolatedAlloc::new()))) } else { None }, tracked_alloc_ids: config.tracked_alloc_ids.clone(), @@ -732,7 +732,7 @@ impl<'tcx> MiriMachine<'tcx> { basic_block_count: 0, monotonic_clock: MonotonicClock::new(config.isolated_op == IsolatedOp::Allow), #[cfg(unix)] - native_lib: config.native_lib.as_ref().map(|lib_file_path| { + native_lib: config.native_lib.iter().map(|lib_file_path| { let host_triple = rustc_session::config::host_tuple(); let target_triple = tcx.sess.opts.target_triple.tuple(); // Check if host target == the session target. @@ -752,11 +752,11 @@ impl<'tcx> MiriMachine<'tcx> { }, lib_file_path.clone(), ) - }), + }).collect(), #[cfg(not(unix))] - native_lib: config.native_lib.as_ref().map(|_| { + native_lib: config.native_lib.iter().map(|_| { panic!("calling functions from native libraries via FFI is only supported on Unix") - }), + }).collect(), gc_interval: config.gc_interval, since_gc: 0, num_cpus: config.num_cpus, diff --git a/src/tools/miri/src/math.rs b/src/tools/miri/src/math.rs index 2ff29c7ac1a..d1355a21684 100644 --- a/src/tools/miri/src/math.rs +++ b/src/tools/miri/src/math.rs @@ -151,6 +151,20 @@ pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFl } } +/// Extend functionality of rustc_apfloat softfloats +pub trait IeeeExt: rustc_apfloat::Float { + #[inline] + fn one() -> Self { + Self::from_u128(1).value + } + + #[inline] + fn clamp(self, min: Self, max: Self) -> Self { + self.maximum(min).minimum(max) + } +} +impl<S: rustc_apfloat::ieee::Semantics> IeeeExt for IeeeFloat<S> {} + #[cfg(test)] mod tests { use rustc_apfloat::ieee::{DoubleS, HalfS, IeeeFloat, QuadS, SingleS}; diff --git a/src/tools/miri/src/shims/alloc.rs b/src/tools/miri/src/shims/alloc.rs index 323b95d5f5f..f05c5fbbe1d 100644 --- a/src/tools/miri/src/shims/alloc.rs +++ b/src/tools/miri/src/shims/alloc.rs @@ -13,12 +13,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // alignment requirement and size less than or equal to the size requested." // So first we need to figure out what the limits are for "fundamental alignment". // This is given by `alignof(max_align_t)`. The following list is taken from - // `library/std/src/sys/pal/common/alloc.rs` (where this is called `MIN_ALIGN`) and should + // `library/std/src/sys/alloc/mod.rs` (where this is called `MIN_ALIGN`) and should // be kept in sync. + let os = this.tcx.sess.target.os.as_ref(); let max_fundamental_align = match this.tcx.sess.target.arch.as_ref() { - "x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8, - "x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" => - 16, + "riscv32" if matches!(os, "espidf" | "zkvm") => 4, + "xtensa" if matches!(os, "espidf") => 4, + "x86" | "arm" | "m68k" | "csky" | "loongarch32" | "mips" | "mips32r6" | "powerpc" + | "powerpc64" | "sparc" | "wasm32" | "hexagon" | "riscv32" | "xtensa" => 8, + "x86_64" | "aarch64" | "arm64ec" | "loongarch64" | "mips64" | "mips64r6" | "s390x" + | "sparc64" | "riscv64" | "wasm64" => 16, arch => bug!("unsupported target architecture for malloc: `{}`", arch), }; // The C standard only requires sufficient alignment for any *type* with size less than or diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 8606735c913..feb83ca8829 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -169,8 +169,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => throw_unsup_format!("unknown `miri_resolve_frame` flags {}", flags), } - this.write_scalar(Scalar::from_u32(lineno), &this.project_field(dest, FieldIdx::from_u32(2))?)?; - this.write_scalar(Scalar::from_u32(colno), &this.project_field(dest, FieldIdx::from_u32(3))?)?; + this.write_scalar( + Scalar::from_u32(lineno), + &this.project_field(dest, FieldIdx::from_u32(2))?, + )?; + this.write_scalar( + Scalar::from_u32(colno), + &this.project_field(dest, FieldIdx::from_u32(3))?, + )?; // Support a 4-field struct for now - this is deprecated // and slated for removal. diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index ae04ca018ab..39b930fdeb9 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -238,7 +238,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // First deal with any external C functions in linked .so file. #[cfg(unix)] - if this.machine.native_lib.as_ref().is_some() { + if !this.machine.native_lib.is_empty() { use crate::shims::native_lib::EvalContextExt as _; // An Ok(false) here means that the function being called was not exported // by the specified `.so` file; we should continue and check if it corresponds to diff --git a/src/tools/miri/src/shims/native_lib.rs b/src/tools/miri/src/shims/native_lib.rs index 1e6c93333c1..acf258f4eed 100644 --- a/src/tools/miri/src/shims/native_lib.rs +++ b/src/tools/miri/src/shims/native_lib.rs @@ -87,31 +87,35 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } /// Get the pointer to the function of the specified name in the shared object file, - /// if it exists. The function must be in the shared object file specified: we do *not* - /// return pointers to functions in dependencies of the library. + /// if it exists. The function must be in one of the shared object files specified: + /// we do *not* return pointers to functions in dependencies of libraries. fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> { let this = self.eval_context_mut(); - // Try getting the function from the shared library. - let (lib, lib_path) = this.machine.native_lib.as_ref().unwrap(); - let func: libloading::Symbol<'_, unsafe extern "C" fn()> = - unsafe { lib.get(link_name.as_str().as_bytes()).ok()? }; - #[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`. - let fn_ptr = *func.deref() as *mut std::ffi::c_void; + // Try getting the function from one of the shared libraries. + for (lib, lib_path) in &this.machine.native_lib { + let Ok(func): Result<libloading::Symbol<'_, unsafe extern "C" fn()>, _> = + (unsafe { lib.get(link_name.as_str().as_bytes()) }) + else { + continue; + }; + #[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`. + let fn_ptr = *func.deref() as *mut std::ffi::c_void; - // FIXME: this is a hack! - // The `libloading` crate will automatically load system libraries like `libc`. - // On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202 - // and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the - // library if it can't find the symbol in the library itself. - // So, in order to check if the function was actually found in the specified - // `machine.external_so_lib` we need to check its `dli_fname` and compare it to - // the specified SO file path. - // This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`, - // from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411 - // using the `libc` crate where this interface is public. - let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed(); - unsafe { - if libc::dladdr(fn_ptr, info.as_mut_ptr()) != 0 { + // FIXME: this is a hack! + // The `libloading` crate will automatically load system libraries like `libc`. + // On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202 + // and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the + // library if it can't find the symbol in the library itself. + // So, in order to check if the function was actually found in the specified + // `machine.external_so_lib` we need to check its `dli_fname` and compare it to + // the specified SO file path. + // This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`, + // from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411 + // using the `libc` crate where this interface is public. + let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed(); + unsafe { + let res = libc::dladdr(fn_ptr, info.as_mut_ptr()); + assert!(res != 0, "failed to load info about function we already loaded"); let info = info.assume_init(); #[cfg(target_os = "cygwin")] let fname_ptr = info.dli_fname.as_ptr(); @@ -121,13 +125,15 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { if std::ffi::CStr::from_ptr(fname_ptr).to_str().unwrap() != lib_path.to_str().unwrap() { - return None; + // The function is not actually in this .so, check the next one. + continue; } } - } - // Return a pointer to the function. - Some(CodePtr(fn_ptr)) + // Return a pointer to the function. + return Some(CodePtr(fn_ptr)); + } + None } } diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 31cb269059c..0f7d453b296 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -2,7 +2,8 @@ use std::borrow::Cow; use std::fs::{ - DirBuilder, File, FileType, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename, + DirBuilder, File, FileType, OpenOptions, ReadDir, TryLockError, read_dir, remove_dir, + remove_file, rename, }; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; @@ -89,103 +90,27 @@ impl UnixFileDescription for FileHandle { communicate_allowed: bool, op: FlockOp, ) -> InterpResult<'tcx, io::Result<()>> { - // cfg(bootstrap) - macro_rules! cfg_select_dispatch { - ($($tokens:tt)*) => { - #[cfg(bootstrap)] - cfg_match! { $($tokens)* } - - #[cfg(not(bootstrap))] - cfg_select! { $($tokens)* } - }; - } - assert!(communicate_allowed, "isolation should have prevented even opening a file"); - cfg_select_dispatch! { - all(target_family = "unix", not(target_os = "solaris")) => { - use std::os::fd::AsRawFd; - - use FlockOp::*; - // We always use non-blocking call to prevent interpreter from being blocked - let (host_op, lock_nb) = match op { - SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking), - ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking), - Unlock => (libc::LOCK_UN, false), - }; - let fd = self.file.as_raw_fd(); - let ret = unsafe { libc::flock(fd, host_op) }; - let res = match ret { - 0 => Ok(()), - -1 => { - let err = io::Error::last_os_error(); - if !lock_nb && err.kind() == io::ErrorKind::WouldBlock { - throw_unsup_format!("blocking `flock` is not currently supported"); - } - Err(err) - } - ret => panic!("Unexpected return value from flock: {ret}"), - }; - interp_ok(res) + use FlockOp::*; + // We must not block the interpreter loop, so we always `try_lock`. + let (res, nonblocking) = match op { + SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking), + ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking), + Unlock => { + return interp_ok(self.file.unlock()); } - target_family = "windows" => { - use std::os::windows::io::AsRawHandle; - - use windows_sys::Win32::Foundation::{ - ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, TRUE, - }; - use windows_sys::Win32::Storage::FileSystem::{ - LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile, - }; - - let fh = self.file.as_raw_handle(); - - use FlockOp::*; - let (ret, lock_nb) = match op { - SharedLock { nonblocking } | ExclusiveLock { nonblocking } => { - // We always use non-blocking call to prevent interpreter from being blocked - let mut flags = LOCKFILE_FAIL_IMMEDIATELY; - if matches!(op, ExclusiveLock { .. }) { - flags |= LOCKFILE_EXCLUSIVE_LOCK; - } - let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) }; - (ret, nonblocking) - } - Unlock => { - let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) }; - (ret, false) - } - }; + }; - let res = match ret { - TRUE => Ok(()), - FALSE => { - let mut err = io::Error::last_os_error(); - // This only runs on Windows hosts so we can use `raw_os_error`. - // We have to be careful not to forward that error code to target code. - let code: u32 = err.raw_os_error().unwrap().try_into().unwrap(); - if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) { - if lock_nb { - // The io error mapping does not know about these error codes, - // so we translate it to `WouldBlock` manually. - let desc = format!("LockFileEx wouldblock error: {err}"); - err = io::Error::new(io::ErrorKind::WouldBlock, desc); - } else { - throw_unsup_format!("blocking `flock` is not currently supported"); - } - } - Err(err) - } - _ => panic!("Unexpected return value: {ret}"), - }; - interp_ok(res) - } - _ => { - let _ = op; - throw_unsup_format!( - "flock is supported only on UNIX (except Solaris) and Windows hosts" - ); - } + match res { + Ok(()) => interp_ok(Ok(())), + Err(TryLockError::Error(err)) => interp_ok(Err(err)), + Err(TryLockError::WouldBlock) => + if nonblocking { + interp_ok(Err(ErrorKind::WouldBlock.into())) + } else { + throw_unsup_format!("blocking `flock` is not currently supported"); + }, } } } diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs index f971fb10b19..d460abc783d 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -286,7 +286,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if op == epoll_ctl_add || op == epoll_ctl_mod { // Read event bitmask and data from epoll_event passed by caller. - let mut events = this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?; + let mut events = + this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?; let data = this.read_scalar(&this.project_field(&event, FieldIdx::ONE)?)?.to_u64()?; // Unset the flag we support to discover if any unsupported flags are used. diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 129b18d8e59..0ff48c389e8 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -88,16 +88,17 @@ fn test_dup() { let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>(); unsafe { let fd = libc::open(name_ptr, libc::O_RDONLY); + let new_fd = libc::dup(fd); + let new_fd2 = libc::dup2(fd, 8); + let mut first_buf = [0u8; 4]; libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4); assert_eq!(&first_buf, b"dup "); - let new_fd = libc::dup(fd); let mut second_buf = [0u8; 4]; libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4); assert_eq!(&second_buf, b"and "); - let new_fd2 = libc::dup2(fd, 8); let mut third_buf = [0u8; 4]; libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4); assert_eq!(&third_buf, b"dup2"); diff --git a/src/tools/miri/tests/pass/both_borrows/smallvec.rs b/src/tools/miri/tests/pass/both_borrows/smallvec.rs new file mode 100644 index 00000000000..f48815e37be --- /dev/null +++ b/src/tools/miri/tests/pass/both_borrows/smallvec.rs @@ -0,0 +1,99 @@ +//! This test represents a core part of `SmallVec`'s `extend_impl`. +//! What makes it interesting as a test is that it relies on Stacked Borrow's "quirk" +//! in a fundamental, hard-to-fix-without-full-trees way. + +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +use std::marker::PhantomData; +use std::mem::{ManuallyDrop, MaybeUninit}; +use std::ptr::NonNull; + +#[repr(C)] +pub union RawSmallVec<T, const N: usize> { + inline: ManuallyDrop<MaybeUninit<[T; N]>>, + heap: (NonNull<T>, usize), +} + +impl<T, const N: usize> RawSmallVec<T, N> { + const fn new() -> Self { + Self::new_inline(MaybeUninit::uninit()) + } + + const fn new_inline(inline: MaybeUninit<[T; N]>) -> Self { + Self { inline: ManuallyDrop::new(inline) } + } + + const fn as_mut_ptr_inline(&mut self) -> *mut T { + (unsafe { &raw mut self.inline }) as *mut T + } + + const unsafe fn as_mut_ptr_heap(&mut self) -> *mut T { + self.heap.0.as_ptr() + } +} + +#[repr(transparent)] +#[derive(Clone, Copy)] +struct TaggedLen(usize); + +impl TaggedLen { + pub const fn new(len: usize, on_heap: bool, is_zst: bool) -> Self { + if is_zst { + debug_assert!(!on_heap); + TaggedLen(len) + } else { + debug_assert!(len < isize::MAX as usize); + TaggedLen((len << 1) | on_heap as usize) + } + } + + pub const fn on_heap(self, is_zst: bool) -> bool { + if is_zst { false } else { (self.0 & 1_usize) == 1 } + } + + pub const fn value(self, is_zst: bool) -> usize { + if is_zst { self.0 } else { self.0 >> 1 } + } +} + +#[repr(C)] +pub struct SmallVec<T, const N: usize> { + len: TaggedLen, + raw: RawSmallVec<T, N>, + _marker: PhantomData<T>, +} + +impl<T, const N: usize> SmallVec<T, N> { + pub const fn new() -> SmallVec<T, N> { + Self { + len: TaggedLen::new(0, false, Self::is_zst()), + raw: RawSmallVec::new(), + _marker: PhantomData, + } + } + + const fn is_zst() -> bool { + size_of::<T>() == 0 + } + + pub const fn as_mut_ptr(&mut self) -> *mut T { + if self.len.on_heap(Self::is_zst()) { + // SAFETY: see above + unsafe { self.raw.as_mut_ptr_heap() } + } else { + self.raw.as_mut_ptr_inline() + } + } + + pub const fn len(&self) -> usize { + self.len.value(Self::is_zst()) + } +} + +fn main() { + let mut v = SmallVec::<i32, 4>::new(); + let ptr = v.as_mut_ptr(); + let _len = v.len(); // this call incurs a reborrow which just barely does not invalidate `ptr` + unsafe { ptr.write(0) }; +} diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 98a88cfd62d..383579198bb 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -45,6 +45,30 @@ macro_rules! assert_approx_eq { }; } +/// From IEEE 754 a Signaling NaN for single precision has the following representation: +/// ``` +/// s | 1111 1111 | 0x..x +/// ```` +/// Were at least one `x` is a 1. +/// +/// This sNaN has the following representation and is used for testing purposes.: +/// ``` +/// 0 | 1111111 | 01..0 +/// ``` +const SNAN_F32: f32 = f32::from_bits(0x7fa00000); + +/// From IEEE 754 a Signaling NaN for double precision has the following representation: +/// ``` +/// s | 1111 1111 111 | 0x..x +/// ```` +/// Were at least one `x` is a 1. +/// +/// This sNaN has the following representation and is used for testing purposes.: +/// ``` +/// 0 | 1111 1111 111 | 01..0 +/// ``` +const SNAN_F64: f64 = f64::from_bits(0x7ff4000000000000); + fn main() { basic(); casts(); @@ -1008,17 +1032,84 @@ pub fn libm() { assert_approx_eq!(25f32.powf(-2f32), 0.0016f32); assert_approx_eq!(400f64.powf(0.5f64), 20f64); + // Some inputs to powf and powi result in fixed outputs + // and thus must be exactly equal to that value. + // C standard says: + // 1^y = 1 for any y, even a NaN. + assert_eq!(1f32.powf(10.0), 1.0); + assert_eq!(1f64.powf(100.0), 1.0); + assert_eq!(1f32.powf(f32::INFINITY), 1.0); + assert_eq!(1f64.powf(f64::INFINITY), 1.0); + assert_eq!(1f32.powf(f32::NAN), 1.0); + assert_eq!(1f64.powf(f64::NAN), 1.0); + + // f*::NAN is a quiet NAN and should return 1 as well. + assert_eq!(f32::NAN.powf(0.0), 1.0); + assert_eq!(f64::NAN.powf(0.0), 1.0); + + assert_eq!(42f32.powf(0.0), 1.0); + assert_eq!(42f64.powf(0.0), 1.0); + assert_eq!(f32::INFINITY.powf(0.0), 1.0); + assert_eq!(f64::INFINITY.powf(0.0), 1.0); + + // f*::NAN is a quiet NAN and should return 1 as well. + assert_eq!(f32::NAN.powi(0), 1.0); + assert_eq!(f64::NAN.powi(0), 1.0); + + assert_eq!(10.0f32.powi(0), 1.0); + assert_eq!(10.0f64.powi(0), 1.0); + assert_eq!(f32::INFINITY.powi(0), 1.0); + assert_eq!(f64::INFINITY.powi(0), 1.0); + + assert_eq!((-1f32).powf(f32::INFINITY), 1.0); + assert_eq!((-1f64).powf(f64::INFINITY), 1.0); + assert_eq!((-1f32).powf(f32::NEG_INFINITY), 1.0); + assert_eq!((-1f64).powf(f64::NEG_INFINITY), 1.0); + + // For pow (powf in rust) the C standard says: + // x^0 = 1 for all x even a sNaN + // FIXME(#4286): this does not match the behavior of all implementations. + assert_eq!(SNAN_F32.powf(0.0), 1.0); + assert_eq!(SNAN_F64.powf(0.0), 1.0); + + // For pown (powi in rust) the C standard says: + // x^0 = 1 for all x even a sNaN + // FIXME(#4286): this does not match the behavior of all implementations. + assert_eq!(SNAN_F32.powi(0), 1.0); + assert_eq!(SNAN_F64.powi(0), 1.0); + + assert_eq!(0f32.powi(10), 0.0); + assert_eq!(0f64.powi(100), 0.0); + assert_eq!(0f32.powi(9), 0.0); + assert_eq!(0f64.powi(99), 0.0); + + assert_biteq((-0f32).powf(10.0), 0.0, "-0^x = +0 where x is positive"); + assert_biteq((-0f64).powf(100.0), 0.0, "-0^x = +0 where x is positive"); + assert_biteq((-0f32).powf(9.0), -0.0, "-0^x = -0 where x is negative"); + assert_biteq((-0f64).powf(99.0), -0.0, "-0^x = -0 where x is negative"); + + assert_biteq((-0f32).powi(10), 0.0, "-0^x = +0 where x is positive"); + assert_biteq((-0f64).powi(100), 0.0, "-0^x = +0 where x is positive"); + assert_biteq((-0f32).powi(9), -0.0, "-0^x = -0 where x is negative"); + assert_biteq((-0f64).powi(99), -0.0, "-0^x = -0 where x is negative"); + assert_approx_eq!(1f32.exp(), f32::consts::E); assert_approx_eq!(1f64.exp(), f64::consts::E); + assert_eq!(0f32.exp(), 1.0); + assert_eq!(0f64.exp(), 1.0); assert_approx_eq!(1f32.exp_m1(), f32::consts::E - 1.0); assert_approx_eq!(1f64.exp_m1(), f64::consts::E - 1.0); assert_approx_eq!(10f32.exp2(), 1024f32); assert_approx_eq!(50f64.exp2(), 1125899906842624f64); + assert_eq!(0f32.exp2(), 1.0); + assert_eq!(0f64.exp2(), 1.0); assert_approx_eq!(f32::consts::E.ln(), 1f32); - assert_approx_eq!(1f64.ln(), 0f64); + assert_approx_eq!(f64::consts::E.ln(), 1f64); + assert_eq!(1f32.ln(), 0.0); + assert_eq!(1f64.ln(), 0.0); assert_approx_eq!(0f32.ln_1p(), 0f32); assert_approx_eq!(0f64.ln_1p(), 0f64); @@ -1047,7 +1138,8 @@ pub fn libm() { // Trigonometric functions. - assert_approx_eq!(0f32.sin(), 0f32); + assert_eq!(0f32.sin(), 0f32); + assert_eq!(0f64.sin(), 0f64); assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64); assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5); assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5); @@ -1059,7 +1151,23 @@ pub fn libm() { assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32); assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64); - assert_approx_eq!(0f32.cos(), 1f32); + // Ensure `sin` always returns something that is a valid input for `asin`, and same for + // `cos` and `acos`. + let halve_pi_f32 = std::f32::consts::FRAC_PI_2; + let halve_pi_f64 = std::f64::consts::FRAC_PI_2; + let pi_f32 = std::f32::consts::PI; + let pi_f64 = std::f64::consts::PI; + for _ in 0..64 { + // sin() should be clamped to [-1, 1] so asin() can never return NaN + assert!(!halve_pi_f32.sin().asin().is_nan()); + assert!(!halve_pi_f64.sin().asin().is_nan()); + // cos() should be clamped to [-1, 1] so acos() can never return NaN + assert!(!pi_f32.cos().acos().is_nan()); + assert!(!pi_f64.cos().acos().is_nan()); + } + + assert_eq!(0f32.cos(), 1f32); + assert_eq!(0f64.cos(), 1f64); assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64); assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5); assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5); @@ -1278,7 +1386,6 @@ fn test_non_determinism() { frem_algebraic, frem_fast, fsub_algebraic, fsub_fast, }; use std::{f32, f64}; - // TODO: Also test powi and powf when the non-determinism is implemented for them /// Ensure that the operation is non-deterministic #[track_caller] @@ -1318,21 +1425,23 @@ fn test_non_determinism() { } pub fn test_operations_f32(a: f32, b: f32) { test_operations_f!(a, b); - // FIXME: temporarily disabled as it breaks std tests. - // ensure_nondet(|| a.log(b)); - // ensure_nondet(|| a.exp()); - // ensure_nondet(|| 10f32.exp2()); - // ensure_nondet(|| f32::consts::E.ln()); + // FIXME: some are temporarily disabled as it breaks std tests. + ensure_nondet(|| a.powf(b)); + ensure_nondet(|| a.powi(2)); + ensure_nondet(|| a.log(b)); + ensure_nondet(|| a.exp()); + ensure_nondet(|| 10f32.exp2()); + ensure_nondet(|| f32::consts::E.ln()); + ensure_nondet(|| 10f32.log10()); + ensure_nondet(|| 8f32.log2()); // ensure_nondet(|| 1f32.ln_1p()); - // ensure_nondet(|| 10f32.log10()); - // ensure_nondet(|| 8f32.log2()); // ensure_nondet(|| 27.0f32.cbrt()); // ensure_nondet(|| 3.0f32.hypot(4.0f32)); - // ensure_nondet(|| 1f32.sin()); - // ensure_nondet(|| 0f32.cos()); - // // On i686-pc-windows-msvc , these functions are implemented by calling the `f64` version, - // // which means the little rounding errors Miri introduces are discard by the cast down to `f32`. - // // Just skip the test for them. + ensure_nondet(|| 1f32.sin()); + ensure_nondet(|| 1f32.cos()); + // On i686-pc-windows-msvc , these functions are implemented by calling the `f64` version, + // which means the little rounding errors Miri introduces are discarded by the cast down to + // `f32`. Just skip the test for them. // if !cfg!(all(target_os = "windows", target_env = "msvc", target_arch = "x86")) { // ensure_nondet(|| 1.0f32.tan()); // ensure_nondet(|| 1.0f32.asin()); @@ -1353,18 +1462,20 @@ fn test_non_determinism() { } pub fn test_operations_f64(a: f64, b: f64) { test_operations_f!(a, b); - // FIXME: temporarily disabled as it breaks std tests. - // ensure_nondet(|| a.log(b)); - // ensure_nondet(|| a.exp()); - // ensure_nondet(|| 50f64.exp2()); - // ensure_nondet(|| 3f64.ln()); + // FIXME: some are temporarily disabled as it breaks std tests. + ensure_nondet(|| a.powf(b)); + ensure_nondet(|| a.powi(2)); + ensure_nondet(|| a.log(b)); + ensure_nondet(|| a.exp()); + ensure_nondet(|| 50f64.exp2()); + ensure_nondet(|| 3f64.ln()); + ensure_nondet(|| f64::consts::E.log10()); + ensure_nondet(|| f64::consts::E.log2()); // ensure_nondet(|| 1f64.ln_1p()); - // ensure_nondet(|| f64::consts::E.log10()); - // ensure_nondet(|| f64::consts::E.log2()); // ensure_nondet(|| 27.0f64.cbrt()); // ensure_nondet(|| 3.0f64.hypot(4.0f64)); - // ensure_nondet(|| 1f64.sin()); - // ensure_nondet(|| 0f64.cos()); + ensure_nondet(|| 1f64.sin()); + ensure_nondet(|| 1f64.cos()); // ensure_nondet(|| 1.0f64.tan()); // ensure_nondet(|| 1.0f64.asin()); // ensure_nondet(|| 5.0f64.acos()); diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 87df43ca7e5..2f30827c933 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -2,12 +2,12 @@ #![feature(io_error_more)] #![feature(io_error_uncategorized)] +#![feature(file_lock)] use std::collections::BTreeMap; use std::ffi::OsString; use std::fs::{ - File, OpenOptions, canonicalize, create_dir, read_dir, remove_dir, remove_dir_all, remove_file, - rename, + self, File, OpenOptions, create_dir, read_dir, remove_dir, remove_dir_all, remove_file, rename, }; use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write}; use std::path::Path; @@ -33,6 +33,8 @@ fn main() { test_canonicalize(); #[cfg(unix)] test_pread_pwrite(); + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + test_flock(); } } @@ -240,7 +242,7 @@ fn test_canonicalize() { let path = dir_path.join("test_file"); drop(File::create(&path).unwrap()); - let p = canonicalize(format!("{}/./test_file", dir_path.to_string_lossy())).unwrap(); + let p = fs::canonicalize(format!("{}/./test_file", dir_path.to_string_lossy())).unwrap(); assert_eq!(p.to_string_lossy().find("/./"), None); remove_dir_all(&dir_path).unwrap(); @@ -351,3 +353,28 @@ fn test_pread_pwrite() { f.read_exact(&mut buf1).unwrap(); assert_eq!(&buf1, b" m"); } + +// This function does seem to exist on Illumos but std does not expose it there. +#[cfg(not(any(target_os = "solaris", target_os = "illumos")))] +fn test_flock() { + let bytes = b"Hello, World!\n"; + let path = utils::prepare_with_content("miri_test_fs_flock.txt", bytes); + let file1 = OpenOptions::new().read(true).write(true).open(&path).unwrap(); + let file2 = OpenOptions::new().read(true).write(true).open(&path).unwrap(); + + // Test that we can apply many shared locks + file1.lock_shared().unwrap(); + file2.lock_shared().unwrap(); + // Test that shared lock prevents exclusive lock + assert!(matches!(file1.try_lock().unwrap_err(), fs::TryLockError::WouldBlock)); + // Unlock shared lock + file1.unlock().unwrap(); + file2.unlock().unwrap(); + // Take exclusive lock + file1.lock().unwrap(); + // Test that shared lock prevents exclusive and shared locks + assert!(matches!(file2.try_lock().unwrap_err(), fs::TryLockError::WouldBlock)); + assert!(matches!(file2.try_lock_shared().unwrap_err(), fs::TryLockError::WouldBlock)); + // Unlock exclusive lock + file1.unlock().unwrap(); +} diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs new file mode 100644 index 00000000000..fd68685a2f4 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs @@ -0,0 +1,34 @@ +//! The same as `tests/fail/tree-borrows/cell-inside-struct` but with +//! precise tracking of interior mutability disabled. +//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-no-precise-interior-mut +#[path = "../../utils/mod.rs"] +#[macro_use] +mod utils; + +use std::cell::Cell; + +struct Foo { + field1: u32, + field2: Cell<u32>, +} + +pub fn main() { + let root = Foo { field1: 42, field2: Cell::new(88) }; + unsafe { + let a = &root; + + name!(a as *const Foo, "a"); + + let a: *const Foo = a as *const Foo; + let a: *mut Foo = a as *mut Foo; + + let alloc_id = alloc_id!(a); + print_state!(alloc_id); + + // Writing to `field2`, which is interior mutable, should be allowed. + (*a).field2.set(10); + + // Writing to `field1` should be allowed because it also has the `Cell` permission. + (*a).field1 = 88; + } +} diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.stderr new file mode 100644 index 00000000000..1d939329040 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.stderr @@ -0,0 +1,6 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 8 +| Act | └─┬──<TAG=root of the allocation> +|?Cel | └────<TAG=a> +────────────────────────────────────────────────── diff --git a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml index d3f0499e434..6e2be7fd3da 100644 --- a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml +++ b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml @@ -28,7 +28,7 @@ jobs: run: rustup update --no-self-update stable - name: Install cargo-workspaces - run: cargo install cargo-workspaces + run: cargo install cargo-workspaces --version "0.3.6" - name: Publish Crates env: @@ -54,8 +54,8 @@ jobs: cargo workspaces rename --from project-model project_model cargo workspaces rename --from test-fixture test_fixture cargo workspaces rename --from test-utils test_utils - # Remove library crates from the workspaces so we don't auto-publish them as well - sed -i 's/ "lib\/\*",//' ./Cargo.toml + # Remove library crates and xtask from the workspaces so we don't auto-publish them as well + sed -i 's|^members = .*$|members = ["crates/*"]|' Cargo.toml cargo workspaces rename ra_ap_%n find crates/rust-analyzer -type f -name '*.rs' -exec sed -i 's/rust_analyzer/ra_ap_rust_analyzer/g' {} + cargo workspaces publish --yes --force '*' --exact --no-git-commit --allow-dirty --skip-published custom 0.0.$(($RUN_NUMBER + 133)) diff --git a/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml b/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml index 93ae5675a71..f2c8b6365b6 100644 --- a/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml +++ b/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml @@ -22,7 +22,7 @@ jobs: run: rustup update --no-self-update stable - name: Install cargo-workspaces - run: cargo install cargo-workspaces + run: cargo install cargo-workspaces --version "0.3.6" - name: Publish Crates env: diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 8c507189846..975fe277b22 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -21,6 +21,8 @@ smol_str.opt-level = 3 text-size.opt-level = 3 serde.opt-level = 3 salsa.opt-level = 3 +dissimilar.opt-level = 3 + # This speeds up `cargo xtask dist`. miniz_oxide.opt-level = 3 diff --git a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs index eed1490a7af..20018b61e5c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs @@ -67,8 +67,14 @@ pub mod keys { pub const PROC_MACRO: Key<ast::Fn, ProcMacroId> = Key::new(); pub const MACRO_CALL: Key<ast::MacroCall, MacroCallId> = Key::new(); pub const ATTR_MACRO_CALL: Key<ast::Item, MacroCallId> = Key::new(); - pub const DERIVE_MACRO_CALL: Key<ast::Attr, (AttrId, MacroCallId, Box<[Option<MacroCallId>]>)> = - Key::new(); + pub const DERIVE_MACRO_CALL: Key< + ast::Attr, + ( + AttrId, + /* derive() */ MacroCallId, + /* actual derive macros */ Box<[Option<MacroCallId>]>, + ), + > = Key::new(); /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are /// equal if they point to exactly the same object. diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index cb4fcd887d8..2cc3ca8c752 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -931,11 +931,12 @@ pub fn new() { // PATH_TYPE@23..26 // PATH@23..26 // PATH_SEGMENT@23..26 -// L_ANGLE@23..24 "<" -// PAREN_TYPE@24..26 -// L_PAREN@24..25 "(" -// ERROR@25..26 -// INT_NUMBER@25..26 "8" +// TYPE_ANCHOR@23..26 +// L_ANGLE@23..24 "<" +// PAREN_TYPE@24..26 +// L_PAREN@24..25 "(" +// ERROR@25..26 +// INT_NUMBER@25..26 "8" // PLUS@26..27 "+" // CONST_ARG@27..28 // LITERAL@27..28 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 980ee264b02..1e985dc604e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -31,6 +31,7 @@ use crate::{ #[query_group::query_group] pub trait HirDatabase: DefDatabase + std::fmt::Debug { #[salsa::invoke(crate::infer::infer_query)] + #[salsa::cycle(cycle_result = crate::infer::infer_cycle_result)] fn infer(&self, def: DefWithBodyId) -> Arc<InferenceResult>; // region:mir @@ -132,6 +133,7 @@ pub trait HirDatabase: DefDatabase + std::fmt::Debug { // FIXME: Make this a non-interned query. #[salsa::invoke_interned(crate::lower::const_param_ty_with_diagnostics_query)] + #[salsa::cycle(cycle_result = crate::lower::const_param_ty_with_diagnostics_cycle_result)] fn const_param_ty_with_diagnostics(&self, def: ConstParamId) -> (Ty, Diagnostics); #[salsa::invoke(crate::lower::const_param_ty_query)] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index e698fb201cb..14eb7160753 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -35,7 +35,8 @@ use chalk_ir::{ use either::Either; use hir_def::{ AdtId, AssocItemId, ConstId, DefWithBodyId, FieldId, FunctionId, GenericDefId, GenericParamId, - ImplId, ItemContainerId, Lookup, TraitId, TupleFieldId, TupleId, TypeAliasId, VariantId, + ImplId, ItemContainerId, LocalFieldId, Lookup, TraitId, TupleFieldId, TupleId, TypeAliasId, + VariantId, builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, expr_store::{Body, ExpressionStore, HygieneId, path::Path}, hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, PatId}, @@ -135,6 +136,10 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<Infer Arc::new(ctx.resolve_all()) } +pub(crate) fn infer_cycle_result(_: &dyn HirDatabase, _: DefWithBodyId) -> Arc<InferenceResult> { + Arc::new(InferenceResult { has_errors: true, ..Default::default() }) +} + /// Fully normalize all the types found within `ty` in context of `owner` body definition. /// /// This is appropriate to use only after type-check: it assumes @@ -203,7 +208,7 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>; pub enum InferenceDiagnostic { NoSuchField { field: ExprOrPatId, - private: bool, + private: Option<LocalFieldId>, variant: VariantId, }, PrivateField { @@ -558,6 +563,9 @@ impl InferenceResult { ExprOrPatId::PatId(id) => self.type_of_pat.get(id), } } + pub fn is_erroneous(&self) -> bool { + self.has_errors && self.type_of_expr.iter().count() == 0 + } } impl Index<ExprId> for InferenceResult { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index 87b7f3406ff..64031279296 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -554,7 +554,7 @@ impl InferenceContext<'_> { self.push_diagnostic( InferenceDiagnostic::NoSuchField { field: field.expr.into(), - private: true, + private: Some(local_id), variant: def, }, ); @@ -564,7 +564,7 @@ impl InferenceContext<'_> { None => { self.push_diagnostic(InferenceDiagnostic::NoSuchField { field: field.expr.into(), - private: false, + private: None, variant: def, }); None diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs index a9a3265858e..4bc3e167ebf 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs @@ -143,7 +143,7 @@ impl InferenceContext<'_> { { self.push_diagnostic(InferenceDiagnostic::NoSuchField { field: inner.into(), - private: true, + private: Some(local_id), variant: def, }); } @@ -157,7 +157,7 @@ impl InferenceContext<'_> { None => { self.push_diagnostic(InferenceDiagnostic::NoSuchField { field: inner.into(), - private: false, + private: None, variant: def, }); self.err_ty() diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index ea8e7cc2be9..0a546768dab 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -1604,6 +1604,14 @@ pub(crate) fn impl_self_ty_with_diagnostics_query( ) } +pub(crate) fn impl_self_ty_with_diagnostics_cycle_result( + db: &dyn HirDatabase, + impl_id: ImplId, +) -> (Binders<Ty>, Diagnostics) { + let generics = generics(db, impl_id.into()); + (make_binders(db, &generics, TyKind::Error.intern(Interner)), None) +} + pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty { db.const_param_ty_with_diagnostics(def).0 } @@ -1633,12 +1641,12 @@ pub(crate) fn const_param_ty_with_diagnostics_query( (ty, create_diagnostics(ctx.diagnostics)) } -pub(crate) fn impl_self_ty_with_diagnostics_cycle_result( - db: &dyn HirDatabase, - impl_id: ImplId, -) -> (Binders<Ty>, Diagnostics) { - let generics = generics(db, impl_id.into()); - (make_binders(db, &generics, TyKind::Error.intern(Interner)), None) +pub(crate) fn const_param_ty_with_diagnostics_cycle_result( + _: &dyn HirDatabase, + _: crate::db::HirDatabaseData, + _: ConstParamId, +) -> (Ty, Diagnostics) { + (TyKind::Error.intern(Interner), None) } pub(crate) fn impl_trait_query(db: &dyn HirDatabase, impl_id: ImplId) -> Option<Binders<TraitRef>> { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index e6caf2d8d97..99d93515303 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -2182,7 +2182,7 @@ pub fn lower_to_mir( // need to take this input explicitly. root_expr: ExprId, ) -> Result<MirBody> { - if infer.type_mismatches().next().is_some() { + if infer.type_mismatches().next().is_some() || infer.is_erroneous() { return Err(MirLowerError::HasErrors); } let mut ctx = MirLowerCtx::new(db, owner, body, infer); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs index 48474d2d26d..e8e3812c69d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs @@ -106,3 +106,256 @@ fn baz() -> i32 { assert_eq!(format!("{events:?}").matches("infer_shim").count(), 1, "{events:#?}") } } + +#[test] +fn adding_struct_invalidates_infer() { + let (mut db, pos) = TestDB::with_position( + " +//- /lib.rs +fn foo() -> i32 { + 1 + 1 +} + +fn bar() -> f32 { + 2.0 * 3.0 +} +$0", + ); + { + let events = db.log_executed(|| { + let module = db.module_for_file(pos.file_id.file_id(&db)); + let _crate_def_map = module.def_map(&db); + db.trait_impls_in_crate(module.krate()); + }); + assert!(format!("{events:?}").contains("trait_impls_in_crate_shim")) + } + + let new_text = " +fn foo() -> i32 { + 1 + 1 +} + +fn bar() -> f32 { + 2.0 * 3.0 +} + +pub struct NewStruct { + field: i32, +} +"; + + db.set_file_text(pos.file_id.file_id(&db), new_text); + + { + let actual = db.log_executed(|| { + let module = db.module_for_file(pos.file_id.file_id(&db)); + let _crate_def_map = module.def_map(&db); + db.trait_impls_in_crate(module.krate()); + }); + + let expected = vec![ + "parse_shim".to_owned(), + "ast_id_map_shim".to_owned(), + "file_item_tree_shim".to_owned(), + "real_span_map_shim".to_owned(), + "crate_local_def_map".to_owned(), + "trait_impls_in_crate_shim".to_owned(), + ]; + + assert_eq!(expected, actual); + } +} + +#[test] +fn adding_enum_query_log() { + let (mut db, pos) = TestDB::with_position( + " +//- /lib.rs +fn foo() -> i32 { + 1 + 1 +} + +fn bar() -> f32 { + 2.0 * 3.0 +} +$0", + ); + { + let events = db.log_executed(|| { + let module = db.module_for_file(pos.file_id.file_id(&db)); + let _crate_def_map = module.def_map(&db); + db.trait_impls_in_crate(module.krate()); + }); + assert!(format!("{events:?}").contains("trait_impls_in_crate_shim")) + } + + let new_text = " +fn foo() -> i32 { + 1 + 1 +} + +fn bar() -> f32 { + 2.0 * 3.0 +} + +pub enum SomeEnum { + A, + B +} +"; + + db.set_file_text(pos.file_id.file_id(&db), new_text); + + { + let actual = db.log_executed(|| { + let module = db.module_for_file(pos.file_id.file_id(&db)); + let _crate_def_map = module.def_map(&db); + db.trait_impls_in_crate(module.krate()); + }); + + let expected = vec![ + "parse_shim".to_owned(), + "ast_id_map_shim".to_owned(), + "file_item_tree_shim".to_owned(), + "real_span_map_shim".to_owned(), + "crate_local_def_map".to_owned(), + "trait_impls_in_crate_shim".to_owned(), + ]; + + assert_eq!(expected, actual); + } +} + +#[test] +fn adding_use_query_log() { + let (mut db, pos) = TestDB::with_position( + " +//- /lib.rs +fn foo() -> i32 { + 1 + 1 +} + +fn bar() -> f32 { + 2.0 * 3.0 +} +$0", + ); + { + let events = db.log_executed(|| { + let module = db.module_for_file(pos.file_id.file_id(&db)); + let _crate_def_map = module.def_map(&db); + db.trait_impls_in_crate(module.krate()); + }); + assert!(format!("{events:?}").contains("trait_impls_in_crate_shim")) + } + + let new_text = " +use std::collections::HashMap; + +fn foo() -> i32 { + 1 + 1 +} + +fn bar() -> f32 { + 2.0 * 3.0 +} +"; + + db.set_file_text(pos.file_id.file_id(&db), new_text); + + { + let actual = db.log_executed(|| { + let module = db.module_for_file(pos.file_id.file_id(&db)); + let _crate_def_map = module.def_map(&db); + db.trait_impls_in_crate(module.krate()); + }); + + let expected = vec![ + "parse_shim".to_owned(), + "ast_id_map_shim".to_owned(), + "file_item_tree_shim".to_owned(), + "real_span_map_shim".to_owned(), + "crate_local_def_map".to_owned(), + "trait_impls_in_crate_shim".to_owned(), + ]; + + assert_eq!(expected, actual); + } +} + +#[test] +fn adding_impl_query_log() { + let (mut db, pos) = TestDB::with_position( + " +//- /lib.rs +fn foo() -> i32 { + 1 + 1 +} + +fn bar() -> f32 { + 2.0 * 3.0 +} + +pub struct SomeStruct { + field: i32, +} +$0", + ); + { + let events = db.log_executed(|| { + let module = db.module_for_file(pos.file_id.file_id(&db)); + let _crate_def_map = module.def_map(&db); + db.trait_impls_in_crate(module.krate()); + }); + assert!(format!("{events:?}").contains("trait_impls_in_crate_shim")) + } + + let new_text = " +fn foo() -> i32 { + 1 + 1 +} + +fn bar() -> f32 { + 2.0 * 3.0 +} + +pub struct SomeStruct { + field: i32, +} + +impl SomeStruct { + pub fn new(value: i32) -> Self { + Self { field: value } + } +} +"; + + db.set_file_text(pos.file_id.file_id(&db), new_text); + + { + let actual = db.log_executed(|| { + let module = db.module_for_file(pos.file_id.file_id(&db)); + let _crate_def_map = module.def_map(&db); + db.trait_impls_in_crate(module.krate()); + }); + + let expected = vec![ + "parse_shim".to_owned(), + "ast_id_map_shim".to_owned(), + "file_item_tree_shim".to_owned(), + "real_span_map_shim".to_owned(), + "crate_local_def_map".to_owned(), + "trait_impls_in_crate_shim".to_owned(), + "attrs_shim".to_owned(), + "impl_trait_with_diagnostics_shim".to_owned(), + "impl_signature_shim".to_owned(), + "impl_signature_with_source_map_shim".to_owned(), + "impl_self_ty_with_diagnostics_shim".to_owned(), + "struct_signature_shim".to_owned(), + "struct_signature_with_source_map_shim".to_owned(), + "type_for_adt_tracked".to_owned(), + ]; + + assert_eq!(expected, actual); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index 47c695c6974..ff8adeef1db 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -2301,3 +2301,51 @@ trait Foo { "#]], ); } + +#[test] +fn no_panic_on_recursive_const() { + check_infer( + r#" +struct Foo<const N: usize> {} +impl<const N: Foo<N>> Foo<N> { + fn foo(self) {} +} + +fn test() { + let _ = N; +} +"#, + expect![[r#" + 72..76 'self': Foo<N> + 78..80 '{}': () + 94..112 '{ ...= N; }': () + 104..105 '_': {unknown} + 108..109 'N': {unknown} + "#]], + ); + + check_infer( + r#" +struct Foo<const N: usize>; +const N: Foo<N> = Foo; + +impl<const N: usize> Foo<N> { + fn foo(self) -> usize { + N + } +} + +fn test() { + let _ = N; +} +"#, + expect![[r#" + 93..97 'self': Foo<N> + 108..125 '{ ... }': usize + 118..119 'N': usize + 139..157 '{ ...= N; }': () + 149..150 '_': Foo<_> + 153..154 'N': Foo<_> + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index b6e3002ed5d..f7b140e03d4 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -224,7 +224,7 @@ pub struct MalformedDerive { #[derive(Debug)] pub struct NoSuchField { pub field: InFile<AstPtr<Either<ast::RecordExprField, ast::RecordPatField>>>, - pub private: bool, + pub private: Option<Field>, pub variant: VariantId, } @@ -648,6 +648,7 @@ impl AnyDiagnostic { } ExprOrPatId::PatId(pat) => source_map.pat_field_syntax(pat), }; + let private = private.map(|id| Field { id, parent: variant.into() }); NoSuchField { field: expr_or_pat, private, variant }.into() } &InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index e01774650b1..4a2e8e379fb 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -222,6 +222,21 @@ impl<DB: HirDatabase> Semantics<'_, DB> { self.imp.descend_node_at_offset(node, offset).filter_map(|mut it| it.find_map(N::cast)) } + // FIXME: Rethink this API + pub fn find_namelike_at_offset_with_descend<'slf>( + &'slf self, + node: &SyntaxNode, + offset: TextSize, + ) -> impl Iterator<Item = ast::NameLike> + 'slf { + node.token_at_offset(offset) + .map(move |token| self.descend_into_macros_no_opaque(token)) + .map(|descendants| descendants.into_iter().filter_map(move |it| it.value.parent())) + // re-order the tokens from token_at_offset by returning the ancestors with the smaller first nodes first + // See algo::ancestors_at_offset, which uses the same approach + .kmerge_by(|left, right| left.text_range().len().lt(&right.text_range().len())) + .filter_map(ast::NameLike::cast) + } + pub fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option<Struct> { self.imp.resolve_range_pat(range_pat).map(Struct::from) } @@ -535,7 +550,7 @@ impl<'db> SemanticsImpl<'db> { } pub fn is_derive_annotated(&self, adt: InFile<&ast::Adt>) -> bool { - self.with_ctx(|ctx| ctx.has_derives(adt)) + self.with_ctx(|ctx| ctx.file_of_adt_has_derives(adt)) } pub fn derive_helpers_in_scope(&self, adt: &ast::Adt) -> Option<Vec<(Symbol, Symbol)>> { @@ -644,7 +659,7 @@ impl<'db> SemanticsImpl<'db> { /// Checks if renaming `renamed` to `new_name` may introduce conflicts with other locals, /// and returns the conflicting locals. - pub fn rename_conflicts(&self, to_be_renamed: &Local, new_name: &str) -> Vec<Local> { + pub fn rename_conflicts(&self, to_be_renamed: &Local, new_name: &Name) -> Vec<Local> { let body = self.db.body(to_be_renamed.parent); let resolver = to_be_renamed.parent.resolver(self.db); let starting_expr = @@ -653,7 +668,7 @@ impl<'db> SemanticsImpl<'db> { body: &body, conflicts: FxHashSet::default(), db: self.db, - new_name: Symbol::intern(new_name), + new_name: new_name.symbol().clone(), old_name: to_be_renamed.name(self.db).symbol().clone(), owner: to_be_renamed.parent, to_be_renamed: to_be_renamed.binding_id, @@ -877,8 +892,9 @@ impl<'db> SemanticsImpl<'db> { if first == last { // node is just the token, so descend the token - self.descend_into_macros_impl( + self.descend_into_macros_all( InFile::new(file.file_id, first), + false, &mut |InFile { value, .. }, _ctx| { if let Some(node) = value .parent_ancestors() @@ -887,20 +903,21 @@ impl<'db> SemanticsImpl<'db> { { res.push(node) } - CONTINUE_NO_BREAKS }, ); } 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(InFile::new(file.file_id, first), &mut |token, _ctx| { - scratch.push(token); - CONTINUE_NO_BREAKS - }); + self.descend_into_macros_all( + InFile::new(file.file_id, first), + false, + &mut |token, _ctx| scratch.push(token), + ); let mut scratch = scratch.into_iter(); - self.descend_into_macros_impl( + self.descend_into_macros_all( InFile::new(file.file_id, last), + false, &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 { @@ -917,17 +934,18 @@ impl<'db> SemanticsImpl<'db> { } } } - CONTINUE_NO_BREAKS }, ); } res } - pub fn is_inside_macro_call(&self, token: InFile<&SyntaxToken>) -> bool { - // FIXME: Maybe `ancestors_with_macros()` is more suitable here? Currently - // this is only used on real (not macro) files so this is not a problem. - token.value.parent_ancestors().any(|ancestor| { + /// Returns true if the given input is within a macro call. + /// + /// Note that if this token itself is within the context of a macro expansion does not matter. + /// That is, we strictly check if it lies inside the input of a macro call. + pub fn is_inside_macro_call(&self, token @ InFile { value, .. }: InFile<&SyntaxToken>) -> bool { + value.parent_ancestors().any(|ancestor| { if ast::MacroCall::can_cast(ancestor.kind()) { return true; } @@ -952,7 +970,7 @@ impl<'db> SemanticsImpl<'db> { ast::Item::Union(it) => it.into(), _ => return false, }; - ctx.has_derives(token.with_value(&adt)) + ctx.file_of_adt_has_derives(token.with_value(&adt)) }) }) } @@ -962,18 +980,18 @@ impl<'db> SemanticsImpl<'db> { token: SyntaxToken, mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext), ) { - self.descend_into_macros_impl(self.wrap_token_infile(token), &mut |t, ctx| { - cb(t, ctx); - CONTINUE_NO_BREAKS + self.descend_into_macros_all(self.wrap_token_infile(token), false, &mut |t, ctx| { + cb(t, ctx) }); } pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { let mut res = smallvec![]; - self.descend_into_macros_impl(self.wrap_token_infile(token.clone()), &mut |t, _ctx| { - res.push(t.value); - CONTINUE_NO_BREAKS - }); + self.descend_into_macros_all( + self.wrap_token_infile(token.clone()), + false, + &mut |t, _ctx| res.push(t.value), + ); if res.is_empty() { res.push(token); } @@ -986,12 +1004,11 @@ impl<'db> SemanticsImpl<'db> { ) -> SmallVec<[InFile<SyntaxToken>; 1]> { let mut res = smallvec![]; let token = self.wrap_token_infile(token); - self.descend_into_macros_impl(token.clone(), &mut |t, ctx| { + self.descend_into_macros_all(token.clone(), true, &mut |t, ctx| { if !ctx.is_opaque(self.db) { // Don't descend into opaque contexts res.push(t); } - CONTINUE_NO_BREAKS }); if res.is_empty() { res.push(token); @@ -1004,7 +1021,7 @@ impl<'db> SemanticsImpl<'db> { token: InFile<SyntaxToken>, mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>, ) -> Option<T> { - self.descend_into_macros_impl(token, &mut cb) + self.descend_into_macros_impl(token, false, &mut cb) } /// Descends the token into expansions, returning the tokens that matches the input @@ -1074,44 +1091,56 @@ impl<'db> SemanticsImpl<'db> { .unwrap_or(token) } + fn descend_into_macros_all( + &self, + token: InFile<SyntaxToken>, + always_descend_into_derives: bool, + f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContext), + ) { + self.descend_into_macros_impl(token, always_descend_into_derives, &mut |tok, ctx| { + f(tok, ctx); + CONTINUE_NO_BREAKS + }); + } + fn descend_into_macros_impl<T>( &self, InFile { value: token, file_id }: InFile<SyntaxToken>, + always_descend_into_derives: bool, f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>, ) -> Option<T> { let _p = tracing::info_span!("descend_into_macros_impl").entered(); - let span = self.db.span_map(file_id).span_for_range(token.text_range()); + let db = self.db; + let span = db.span_map(file_id).span_for_range(token.text_range()); // 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| { - let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| { - Some( - ctx.cache - .get_or_insert_expansion(ctx.db, macro_file) - .map_range_down(span)? - .map(SmallVec::<[_; 2]>::from_iter), - ) - })?; - // we have found a mapping for the token if the vec is non-empty - let res = mapped_tokens.is_empty().not().then_some(()); - // requeue the tokens we got from mapping our current token down - stack.push((HirFileId::from(file_id), mapped_tokens)); - res - }; + let process_expansion_for_token = + |ctx: &mut SourceToDefCtx<'_, '_>, stack: &mut Vec<_>, macro_file| { + let InMacroFile { file_id, value: mapped_tokens } = ctx + .cache + .get_or_insert_expansion(ctx.db, macro_file) + .map_range_down(span)? + .map(SmallVec::<[_; 2]>::from_iter); + // we have found a mapping for the token if the vec is non-empty + let res = mapped_tokens.is_empty().not().then_some(()); + // requeue the tokens we got from mapping our current token down + stack.push((HirFileId::from(file_id), mapped_tokens)); + res + }; // A stack of tokens to process, along with the file they came from // 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![]; - let include = file_id.file_id().and_then(|file_id| { - self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id) - }); + let include = file_id + .file_id() + .and_then(|file_id| self.s2d_cache.borrow_mut().get_or_insert_include_for(db, file_id)); match include { Some(include) => { // include! inputs are always from real files, so they only need to be handled once upfront - process_expansion_for_token(&mut stack, include)?; + self.with_ctx(|ctx| process_expansion_for_token(ctx, &mut stack, include))?; } None => { stack.push((file_id, smallvec![(token, span.ctx)])); @@ -1133,62 +1162,120 @@ impl<'db> SemanticsImpl<'db> { tokens.reverse(); 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| { - token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| { - // Don't force populate the dyn cache for items that don't have an attribute anyways - item.attrs().next()?; - Some((ctx.item_to_macro_call(InFile::new(expansion, &item))?, item)) - }) - }); - if let Some((call_id, item)) = containing_attribute_macro_call { - let attr_id = match self.db.lookup_intern_macro_call(call_id).kind { - hir_expand::MacroCallKind::Attr { invoc_attr_index, .. } => { - invoc_attr_index.ast_index() - } - _ => 0, - }; - // FIXME: here, the attribute's text range is used to strip away all - // entries from the start of the attribute "list" up the invoking - // attribute. But in - // ``` - // mod foo { - // #![inner] - // } - // ``` - // we don't wanna strip away stuff in the `mod foo {` range, that is - // here if the id corresponds to an inner attribute we got strip all - // text ranges of the outer ones, and then all of the inner ones up - // to the invoking attribute so that the inbetween is ignored. - let text_range = item.syntax().text_range(); - let start = collect_attrs(&item) - .nth(attr_id) - .map(|attr| match attr.1 { - Either::Left(it) => it.syntax().text_range().start(), - Either::Right(it) => it.syntax().text_range().start(), + // First expand into attribute invocations, this is required to be handled + // upfront as any other macro call within will not semantically resolve unless + // also descended. + let res = self.with_ctx(|ctx| { + token + .parent_ancestors() + .filter_map(ast::Item::cast) + // FIXME: This might work incorrectly when we have a derive, followed by + // an attribute on an item, like: + // ``` + // #[derive(Debug$0)] + // #[my_attr] + // struct MyStruct; + // ``` + // here we should not consider the attribute at all, as our cursor + // technically lies outside of its expansion + .find_map(|item| { + // Don't force populate the dyn cache for items that don't have an attribute anyways + item.attrs().next()?; + ctx.item_to_macro_call(InFile::new(expansion, &item)) + .zip(Some(item)) }) - .unwrap_or_else(|| text_range.start()); - let text_range = TextRange::new(start, text_range.end()); - filter_duplicates(tokens, text_range); - return process_expansion_for_token(&mut stack, call_id); + .map(|(call_id, item)| { + let attr_id = match db.lookup_intern_macro_call(call_id).kind { + hir_expand::MacroCallKind::Attr { + invoc_attr_index, .. + } => invoc_attr_index.ast_index(), + _ => 0, + }; + // FIXME: here, the attribute's text range is used to strip away all + // entries from the start of the attribute "list" up the invoking + // attribute. But in + // ``` + // mod foo { + // #![inner] + // } + // ``` + // we don't wanna strip away stuff in the `mod foo {` range, that is + // here if the id corresponds to an inner attribute we got strip all + // text ranges of the outer ones, and then all of the inner ones up + // to the invoking attribute so that the inbetween is ignored. + let text_range = item.syntax().text_range(); + let start = collect_attrs(&item) + .nth(attr_id) + .map(|attr| match attr.1 { + Either::Left(it) => it.syntax().text_range().start(), + Either::Right(it) => it.syntax().text_range().start(), + }) + .unwrap_or_else(|| text_range.start()); + let text_range = TextRange::new(start, text_range.end()); + filter_duplicates(tokens, text_range); + process_expansion_for_token(ctx, &mut stack, call_id) + }) + }); + + if let Some(res) = res { + return res; } + if always_descend_into_derives { + let res = self.with_ctx(|ctx| { + let (derives, adt) = token + .parent_ancestors() + .filter_map(ast::Adt::cast) + .find_map(|adt| { + Some(( + ctx.derive_macro_calls(InFile::new(expansion, &adt))? + .map(|(a, b, c)| (a, b, c.to_owned())) + .collect::<SmallVec<[_; 2]>>(), + adt, + )) + })?; + let mut res = None; + for (_, derive_attr, derives) in derives { + // as there may be multiple derives registering the same helper + // name, we gotta make sure to call this for all of them! + // FIXME: We need to call `f` for all of them as well though! + res = res.or(process_expansion_for_token( + ctx, + &mut stack, + derive_attr, + )); + for derive in derives.into_iter().flatten() { + res = res + .or(process_expansion_for_token(ctx, &mut stack, derive)); + } + } + // remove all tokens that are within the derives expansion + filter_duplicates(tokens, adt.syntax().text_range()); + Some(res) + }); + // if we found derives, we can early exit. There is no way we can be in any + // macro call at this point given we are not in a token tree + if let Some(res) = res { + return res; + } + } // Then check for token trees, that means we are either in a function-like macro or // secondary attribute inputs let tt = token .parent_ancestors() .map_while(Either::<ast::TokenTree, ast::Meta>::cast) .last()?; + match tt { // function-like macro call Either::Left(tt) => { + let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; if tt.left_delimiter_token().map_or(false, |it| it == token) { return None; } if tt.right_delimiter_token().map_or(false, |it| it == token) { return None; } - let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; let mcall = InFile::new(expansion, macro_call); let file_id = match m_cache.get(&mcall) { Some(&it) => it, @@ -1201,13 +1288,16 @@ impl<'db> SemanticsImpl<'db> { let text_range = tt.syntax().text_range(); filter_duplicates(tokens, text_range); - process_expansion_for_token(&mut stack, file_id).or(file_id - .eager_arg(self.db) - .and_then(|arg| { - // also descend into eager expansions - process_expansion_for_token(&mut stack, arg) - })) + self.with_ctx(|ctx| { + process_expansion_for_token(ctx, &mut stack, file_id).or(file_id + .eager_arg(db) + .and_then(|arg| { + // also descend into eager expansions + process_expansion_for_token(ctx, &mut stack, arg) + })) + }) } + Either::Right(_) if always_descend_into_derives => None, // derive or derive helper Either::Right(meta) => { // attribute we failed expansion for earlier, this might be a derive invocation @@ -1216,31 +1306,33 @@ impl<'db> SemanticsImpl<'db> { let adt = match attr.syntax().parent().and_then(ast::Adt::cast) { Some(adt) => { // this might be a derive on an ADT - let derive_call = self.with_ctx(|ctx| { + let res = self.with_ctx(|ctx| { // so try downmapping the token into the pseudo derive expansion // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works - ctx.attr_to_derive_macro_call( - InFile::new(expansion, &adt), - InFile::new(expansion, attr.clone()), - ) - .map(|(_, call_id, _)| call_id) + let derive_call = ctx + .attr_to_derive_macro_call( + InFile::new(expansion, &adt), + InFile::new(expansion, attr.clone()), + )? + .1; + + // resolved to a derive + 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, _)| { + !text_range.contains_range(t.text_range()) + }); + Some(process_expansion_for_token( + ctx, + &mut stack, + derive_call, + )) }); - - match derive_call { - Some(call_id) => { - // resolved to a derive - 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, _)| { - !text_range.contains_range(t.text_range()) - }); - return process_expansion_for_token( - &mut stack, call_id, - ); - } - None => Some(adt), + if let Some(res) = res { + return res; } + Some(adt) } None => { // Otherwise this could be a derive helper on a variant or field @@ -1254,12 +1346,9 @@ impl<'db> SemanticsImpl<'db> { ) } }?; - if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(expansion, &adt))) { - return None; - } let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name(); - // Not an attribute, nor a derive, so it's either an intert attribute or a derive helper + // Not an attribute, nor a derive, so it's either an inert attribute or a derive helper // Try to resolve to a derive helper and downmap let resolver = &token .parent() @@ -1267,7 +1356,7 @@ impl<'db> SemanticsImpl<'db> { self.analyze_impl(InFile::new(expansion, &parent), None, false) })? .resolver; - let id = self.db.ast_id_map(expansion).ast_id(&adt); + let id = db.ast_id_map(expansion).ast_id(&adt); let helpers = resolver .def_map() .derive_helpers_in_scope(InFile::new(expansion, id))?; @@ -1278,20 +1367,22 @@ impl<'db> SemanticsImpl<'db> { } let mut res = None; - for (.., derive) in - helpers.iter().filter(|(helper, ..)| *helper == attr_name) - { - // as there may be multiple derives registering the same helper - // name, we gotta make sure to call this for all of them! - // FIXME: We need to call `f` for all of them as well though! - res = res.or(process_expansion_for_token(&mut stack, *derive)); - } - res + self.with_ctx(|ctx| { + for (.., derive) in + helpers.iter().filter(|(helper, ..)| *helper == attr_name) + { + // as there may be multiple derives registering the same helper + // name, we gotta make sure to call this for all of them! + // FIXME: We need to call `f` for all of them as well though! + res = res + .or(process_expansion_for_token(ctx, &mut stack, *derive)); + } + res + }) } } })() .is_none(); - if was_not_remapped { if let ControlFlow::Break(b) = f(InFile::new(expansion, token), ctx) { return Some(b); @@ -1380,25 +1471,31 @@ impl<'db> SemanticsImpl<'db> { } /// Iterates the ancestors of the given node, climbing up macro expansions while doing so. + // FIXME: Replace with `ancestors_with_macros_file` when all usages are updated. pub fn ancestors_with_macros( &self, node: SyntaxNode, ) -> impl Iterator<Item = SyntaxNode> + Clone + '_ { let node = self.find_file(&node); - iter::successors(Some(node.cloned()), move |&InFile { file_id, ref value }| { - match value.parent() { - Some(parent) => Some(InFile::new(file_id, parent)), - None => { - let macro_file = file_id.macro_file()?; - - self.with_ctx(|ctx| { - let expansion_info = ctx.cache.get_or_insert_expansion(ctx.db, macro_file); - expansion_info.arg().map(|node| node?.parent()).transpose() - }) - } + self.ancestors_with_macros_file(node.cloned()).map(|it| it.value) + } + + /// Iterates the ancestors of the given node, climbing up macro expansions while doing so. + pub fn ancestors_with_macros_file( + &self, + node: InFile<SyntaxNode>, + ) -> impl Iterator<Item = InFile<SyntaxNode>> + Clone + '_ { + iter::successors(Some(node), move |&InFile { file_id, ref value }| match value.parent() { + Some(parent) => Some(InFile::new(file_id, parent)), + None => { + let macro_file = file_id.macro_file()?; + + self.with_ctx(|ctx| { + let expansion_info = ctx.cache.get_or_insert_expansion(ctx.db, macro_file); + expansion_info.arg().map(|node| node?.parent()).transpose() + }) } }) - .map(|it| it.value) } pub fn ancestors_at_offset_with_macros( @@ -1851,18 +1948,12 @@ impl<'db> SemanticsImpl<'db> { ChildContainer::TraitId(it) => { return Some(SourceAnalyzer::new_generic_def(self.db, it.into(), node, offset)); } - ChildContainer::TraitAliasId(it) => { - return Some(SourceAnalyzer::new_generic_def(self.db, it.into(), node, offset)); - } ChildContainer::ImplId(it) => { return Some(SourceAnalyzer::new_generic_def(self.db, it.into(), node, offset)); } ChildContainer::EnumId(it) => { return Some(SourceAnalyzer::new_generic_def(self.db, it.into(), node, offset)); } - ChildContainer::TypeAliasId(it) => { - return Some(SourceAnalyzer::new_generic_def(self.db, it.into(), node, offset)); - } ChildContainer::GenericDefId(it) => { return Some(SourceAnalyzer::new_generic_def(self.db, it, node, offset)); } diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs index 6accf9b2e9c..1a6d63c88c6 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs @@ -17,6 +17,7 @@ use hir_def::{ DynMap, keys::{self, Key}, }, + hir::generics::GenericParams, item_scope::ItemScope, item_tree::ItemTreeNode, nameres::DefMap, @@ -49,6 +50,12 @@ impl ChildBySource for TraitId { data.items.iter().for_each(|&(_, item)| { add_assoc_item(db, res, file_id, item); }); + let (_, source_map) = db.trait_signature_with_source_map(*self); + source_map.expansions().filter(|(ast, _)| ast.file_id == file_id).for_each( + |(ast, &exp_id)| { + res[keys::MACRO_CALL].insert(ast.value, exp_id); + }, + ); } } @@ -68,6 +75,12 @@ impl ChildBySource for ImplId { data.items.iter().for_each(|&(_, item)| { add_assoc_item(db, res, file_id, item); }); + let (_, source_map) = db.impl_signature_with_source_map(*self); + source_map.expansions().filter(|(ast, _)| ast.file_id == file_id).for_each( + |(ast, &exp_id)| { + res[keys::MACRO_CALL].insert(ast.value, exp_id); + }, + ); } } @@ -178,6 +191,8 @@ impl ChildBySource for VariantId { Either::Right(source) => res[keys::RECORD_FIELD].insert(AstPtr::new(&source), id), } } + let (_, sm) = db.variant_fields_with_source_map(*self); + sm.expansions().for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id)); } } @@ -195,6 +210,11 @@ impl ChildBySource for EnumId { res[keys::ENUM_VARIANT] .insert(ast_id_map.get(tree[variant.lookup(db).id.value].ast_id), variant); }); + let (_, source_map) = db.enum_signature_with_source_map(*self); + source_map + .expansions() + .filter(|(ast, _)| ast.file_id == file_id) + .for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id)); } } @@ -225,7 +245,8 @@ impl ChildBySource for GenericDefId { return; } - let generic_params = db.generic_params(*self); + let (generic_params, _, source_map) = + GenericParams::generic_params_and_store_and_source_map(db, *self); let mut toc_idx_iter = generic_params.iter_type_or_consts().map(|(idx, _)| idx); let lts_idx_iter = generic_params.iter_lt().map(|(idx, _)| idx); @@ -253,6 +274,11 @@ impl ChildBySource for GenericDefId { res[keys::LIFETIME_PARAM].insert(AstPtr::new(&ast_param), id); } } + + source_map + .expansions() + .filter(|(ast, _)| ast.file_id == file_id) + .for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id)); } } 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 7f6c9af4740..71ee0f69389 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 @@ -108,7 +108,7 @@ use span::FileId; use stdx::impl_from; use syntax::{ AstNode, AstPtr, SyntaxNode, - ast::{self, HasName}, + ast::{self, HasAttrs, HasName}, }; use tt::TextRange; @@ -411,10 +411,25 @@ impl SourceToDefCtx<'_, '_> { .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids)) } - pub(super) fn has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool { + // FIXME: Make this more fine grained! This should be a `adt_has_derives`! + pub(super) fn file_of_adt_has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool { self.dyn_map(adt).as_ref().is_some_and(|map| !map[keys::DERIVE_MACRO_CALL].is_empty()) } + pub(super) fn derive_macro_calls<'slf>( + &'slf mut self, + adt: InFile<&ast::Adt>, + ) -> Option<impl Iterator<Item = (AttrId, MacroCallId, &'slf [Option<MacroCallId>])> + use<'slf>> + { + self.dyn_map(adt).as_ref().map(|&map| { + let dyn_map = &map[keys::DERIVE_MACRO_CALL]; + adt.value + .attrs() + .filter_map(move |attr| dyn_map.get(&AstPtr::new(&attr))) + .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids)) + }) + } + fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>( &mut self, src: InFile<&Ast>, @@ -616,14 +631,14 @@ impl SourceToDefCtx<'_, '_> { match &item { ast::Item::Module(it) => self.module_to_def(container.with_value(it))?.into(), ast::Item::Trait(it) => self.trait_to_def(container.with_value(it))?.into(), - ast::Item::TraitAlias(it) => { - self.trait_alias_to_def(container.with_value(it))?.into() - } ast::Item::Impl(it) => self.impl_to_def(container.with_value(it))?.into(), ast::Item::Enum(it) => self.enum_to_def(container.with_value(it))?.into(), - ast::Item::TypeAlias(it) => { - self.type_alias_to_def(container.with_value(it))?.into() - } + ast::Item::TypeAlias(it) => ChildContainer::GenericDefId( + self.type_alias_to_def(container.with_value(it))?.into(), + ), + ast::Item::TraitAlias(it) => ChildContainer::GenericDefId( + self.trait_alias_to_def(container.with_value(it))?.into(), + ), ast::Item::Struct(it) => { let def = self.struct_to_def(container.with_value(it))?; let is_in_body = it.field_list().is_some_and(|it| { @@ -723,11 +738,9 @@ pub(crate) enum ChildContainer { DefWithBodyId(DefWithBodyId), ModuleId(ModuleId), TraitId(TraitId), - TraitAliasId(TraitAliasId), ImplId(ImplId), EnumId(EnumId), VariantId(VariantId), - TypeAliasId(TypeAliasId), /// XXX: this might be the same def as, for example an `EnumId`. However, /// here the children are generic parameters, and not, eg enum variants. GenericDefId(GenericDefId), @@ -736,11 +749,9 @@ impl_from! { DefWithBodyId, ModuleId, TraitId, - TraitAliasId, ImplId, EnumId, VariantId, - TypeAliasId, GenericDefId for ChildContainer } @@ -752,11 +763,9 @@ impl ChildContainer { ChildContainer::DefWithBodyId(it) => it.child_by_source(db, file_id), ChildContainer::ModuleId(it) => it.child_by_source(db, file_id), ChildContainer::TraitId(it) => it.child_by_source(db, file_id), - ChildContainer::TraitAliasId(_) => DynMap::default(), ChildContainer::ImplId(it) => it.child_by_source(db, file_id), ChildContainer::EnumId(it) => it.child_by_source(db, file_id), ChildContainer::VariantId(it) => it.child_by_source(db, file_id), - ChildContainer::TypeAliasId(_) => DynMap::default(), ChildContainer::GenericDefId(it) => it.child_by_source(db, file_id), } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 5d75e445861..32c4ae2e869 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -56,14 +56,22 @@ pub(crate) fn convert_named_struct_to_tuple_struct( // XXX: We don't currently provide this assist for struct definitions inside macros, but if we // are to lift this limitation, don't forget to make `edit_struct_def()` consider macro files // too. - let name = ctx.find_node_at_offset::<ast::Name>()?; - let strukt = name.syntax().parent().and_then(<Either<ast::Struct, ast::Variant>>::cast)?; - let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?; + let strukt_or_variant = ctx + .find_node_at_offset::<ast::Struct>() + .map(Either::Left) + .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?; + let field_list = strukt_or_variant.as_ref().either(|s| s.field_list(), |v| v.field_list())?; + + if ctx.offset() > field_list.syntax().text_range().start() { + // Assist could be distracting after the braces + return None; + } + let record_fields = match field_list { ast::FieldList::RecordFieldList(it) => it, ast::FieldList::TupleFieldList(_) => return None, }; - let strukt_def = match &strukt { + let strukt_def = match &strukt_or_variant { Either::Left(s) => Either::Left(ctx.sema.to_def(s)?), Either::Right(v) => Either::Right(ctx.sema.to_def(v)?), }; @@ -71,11 +79,11 @@ pub(crate) fn convert_named_struct_to_tuple_struct( acc.add( AssistId::refactor_rewrite("convert_named_struct_to_tuple_struct"), "Convert to tuple struct", - strukt.syntax().text_range(), + strukt_or_variant.syntax().text_range(), |edit| { edit_field_references(ctx, edit, record_fields.fields()); edit_struct_references(ctx, edit, strukt_def); - edit_struct_def(ctx, edit, &strukt, record_fields); + edit_struct_def(ctx, edit, &strukt_or_variant, record_fields); }, ) } @@ -294,6 +302,88 @@ impl A { } #[test] + fn convert_simple_struct_cursor_on_struct_keyword() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct Inner; +struct$0 A { inner: Inner } + +impl A { + fn new(inner: Inner) -> A { + A { inner } + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.inner + } +}"#, + r#" +struct Inner; +struct A(Inner); + +impl A { + fn new(inner: Inner) -> A { + A(inner) + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.0 + } +}"#, + ); + } + + #[test] + fn convert_simple_struct_cursor_on_visibility_keyword() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct Inner; +pub$0 struct A { inner: Inner } + +impl A { + fn new(inner: Inner) -> A { + A { inner } + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.inner + } +}"#, + r#" +struct Inner; +pub struct A(Inner); + +impl A { + fn new(inner: Inner) -> A { + A(inner) + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.0 + } +}"#, + ); + } + + #[test] fn convert_struct_referenced_via_self_kw() { check_assist( convert_named_struct_to_tuple_struct, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 0c0b93bcfbc..80756197fb7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -51,18 +51,26 @@ pub(crate) fn convert_tuple_struct_to_named_struct( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { - let name = ctx.find_node_at_offset::<ast::Name>()?; - let strukt = name.syntax().parent().and_then(<Either<ast::Struct, ast::Variant>>::cast)?; - let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?; + let strukt_or_variant = ctx + .find_node_at_offset::<ast::Struct>() + .map(Either::Left) + .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?; + let field_list = strukt_or_variant.as_ref().either(|s| s.field_list(), |v| v.field_list())?; + + if ctx.offset() > field_list.syntax().text_range().start() { + // Assist could be distracting after the braces + return None; + } + let tuple_fields = match field_list { ast::FieldList::TupleFieldList(it) => it, ast::FieldList::RecordFieldList(_) => return None, }; - let strukt_def = match &strukt { + let strukt_def = match &strukt_or_variant { Either::Left(s) => Either::Left(ctx.sema.to_def(s)?), Either::Right(v) => Either::Right(ctx.sema.to_def(v)?), }; - let target = strukt.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range(); + let target = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range(); acc.add( AssistId::refactor_rewrite("convert_tuple_struct_to_named_struct"), @@ -72,7 +80,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct( let names = generate_names(tuple_fields.fields()); edit_field_references(ctx, edit, tuple_fields.fields(), &names); edit_struct_references(ctx, edit, strukt_def, &names); - edit_struct_def(ctx, edit, &strukt, tuple_fields, names); + edit_struct_def(ctx, edit, &strukt_or_variant, tuple_fields, names); }, ) } @@ -317,6 +325,88 @@ impl A { } #[test] + fn convert_simple_struct_cursor_on_struct_keyword() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +struct Inner; +struct$0 A(Inner); + +impl A { + fn new(inner: Inner) -> A { + A(inner) + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.0 + } +}"#, + r#" +struct Inner; +struct A { field1: Inner } + +impl A { + fn new(inner: Inner) -> A { + A { field1: inner } + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.field1 + } +}"#, + ); + } + + #[test] + fn convert_simple_struct_cursor_on_visibility_keyword() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +struct Inner; +pub$0 struct A(Inner); + +impl A { + fn new(inner: Inner) -> A { + A(inner) + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.0 + } +}"#, + r#" +struct Inner; +pub struct A { field1: Inner } + +impl A { + fn new(inner: Inner) -> A { + A { field1: inner } + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.field1 + } +}"#, + ); + } + + #[test] fn convert_struct_referenced_via_self_kw() { check_assist( convert_tuple_struct_to_named_struct, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs index 19e0a73f333..3badc17d01a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs @@ -7,10 +7,10 @@ use syntax::{ use crate::{AssistContext, AssistId, Assists}; -// FIXME: this really should be a fix for diagnostic, rather than an assist. - // Assist: fix_visibility // +// Note that there is some duplication between this and the no_such_field diagnostic. +// // Makes inaccessible item public. // // ``` @@ -32,7 +32,6 @@ use crate::{AssistContext, AssistId, Assists}; // ``` pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { add_vis_to_referenced_module_def(acc, ctx) - .or_else(|| add_vis_to_referenced_record_field(acc, ctx)) } fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { @@ -88,59 +87,6 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) }) } -fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let record_field: ast::RecordExprField = ctx.find_node_at_offset()?; - let (record_field_def, _, _) = ctx.sema.resolve_record_field(&record_field)?; - - let current_module = ctx.sema.scope(record_field.syntax())?.module(); - let current_edition = current_module.krate().edition(ctx.db()); - let visibility = record_field_def.visibility(ctx.db()); - if visibility.is_visible_from(ctx.db(), current_module.into()) { - return None; - } - - let parent = record_field_def.parent_def(ctx.db()); - let parent_name = parent.name(ctx.db()); - let target_module = parent.module(ctx.db()); - - let in_file_source = record_field_def.source(ctx.db())?; - let (vis_owner, target) = match in_file_source.value { - hir::FieldSource::Named(it) => { - let range = it.syntax().text_range(); - (ast::AnyHasVisibility::new(it), range) - } - hir::FieldSource::Pos(it) => { - let range = it.syntax().text_range(); - (ast::AnyHasVisibility::new(it), range) - } - }; - - let missing_visibility = if current_module.krate() == target_module.krate() { - make::visibility_pub_crate() - } else { - make::visibility_pub() - }; - let target_file = in_file_source.file_id.original_file(ctx.db()); - - let target_name = record_field_def.name(ctx.db()); - let assist_label = format!( - "Change visibility of {}.{} to {missing_visibility}", - parent_name.display(ctx.db(), current_edition), - target_name.display(ctx.db(), current_edition) - ); - - acc.add(AssistId::quick_fix("fix_visibility"), assist_label, target, |edit| { - edit.edit_file(target_file.file_id(ctx.db())); - - let vis_owner = edit.make_mut(vis_owner); - vis_owner.set_visibility(Some(missing_visibility.clone_for_update())); - - if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) { - edit.add_tabstop_before(cap, vis); - } - }) -} - fn target_data_for_def( db: &dyn HirDatabase, def: hir::ModuleDef, @@ -294,44 +240,6 @@ struct Foo; } #[test] - fn fix_visibility_of_struct_field() { - check_assist( - fix_visibility, - r"mod foo { pub struct Foo { bar: (), } } - fn main() { foo::Foo { $0bar: () }; } ", - r"mod foo { pub struct Foo { $0pub(crate) bar: (), } } - fn main() { foo::Foo { bar: () }; } ", - ); - check_assist( - fix_visibility, - r" -//- /lib.rs -mod foo; -fn main() { foo::Foo { $0bar: () }; } -//- /foo.rs -pub struct Foo { bar: () } -", - r"pub struct Foo { $0pub(crate) bar: () } -", - ); - check_assist_not_applicable( - fix_visibility, - r"mod foo { pub struct Foo { pub bar: (), } } - fn main() { foo::Foo { $0bar: () }; } ", - ); - check_assist_not_applicable( - fix_visibility, - r" -//- /lib.rs -mod foo; -fn main() { foo::Foo { $0bar: () }; } -//- /foo.rs -pub struct Foo { pub bar: () } -", - ); - } - - #[test] fn fix_visibility_of_enum_variant_field() { // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc // rejects any visibility specifiers on them, so this assist should never fire on them. @@ -368,44 +276,6 @@ pub struct Foo { pub bar: () } } #[test] - fn fix_visibility_of_union_field() { - check_assist( - fix_visibility, - r"mod foo { pub union Foo { bar: (), } } - fn main() { foo::Foo { $0bar: () }; } ", - r"mod foo { pub union Foo { $0pub(crate) bar: (), } } - fn main() { foo::Foo { bar: () }; } ", - ); - check_assist( - fix_visibility, - r" -//- /lib.rs -mod foo; -fn main() { foo::Foo { $0bar: () }; } -//- /foo.rs -pub union Foo { bar: () } -", - r"pub union Foo { $0pub(crate) bar: () } -", - ); - check_assist_not_applicable( - fix_visibility, - r"mod foo { pub union Foo { pub bar: (), } } - fn main() { foo::Foo { $0bar: () }; } ", - ); - check_assist_not_applicable( - fix_visibility, - r" -//- /lib.rs -mod foo; -fn main() { foo::Foo { $0bar: () }; } -//- /foo.rs -pub union Foo { pub bar: () } -", - ); - } - - #[test] fn fix_visibility_of_const() { check_assist( fix_visibility, @@ -572,19 +442,6 @@ pub(crate) struct Bar; r"$0pub struct Bar; ", ); - check_assist( - fix_visibility, - r" -//- /main.rs crate:a deps:foo -fn main() { - foo::Foo { $0bar: () }; -} -//- /lib.rs crate:foo -pub struct Foo { pub(crate) bar: () } -", - r"pub struct Foo { $0pub bar: () } -", - ); } #[test] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_underscore.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_underscore.rs index 912e1936b59..a8e27416d5c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_underscore.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_underscore.rs @@ -1,6 +1,7 @@ use ide_db::{ assists::AssistId, defs::{Definition, NameClass, NameRefClass}, + rename::RenameDefinition, }; use syntax::{AstNode, ast}; @@ -61,7 +62,7 @@ pub(crate) fn remove_underscore(acc: &mut Assists, ctx: &AssistContext<'_>) -> O "Remove underscore from a used variable", text_range, |builder| { - let changes = def.rename(&ctx.sema, new_name).unwrap(); + let changes = def.rename(&ctx.sema, new_name, RenameDefinition::Yes).unwrap(); builder.source_change = changes; }, ) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs index 3c195f80fea..705402c785a 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs @@ -25,6 +25,7 @@ use crate::{ mod cfg; mod derive; +mod diagnostic; mod lint; mod macro_use; mod repr; @@ -40,23 +41,22 @@ pub(crate) fn complete_known_attribute_input( extern_crate: Option<&ast::ExternCrate>, ) -> Option<()> { let attribute = fake_attribute_under_caret; - let name_ref = match attribute.path() { - Some(p) => Some(p.as_single_name_ref()?), - None => None, - }; - let (path, tt) = name_ref.zip(attribute.token_tree())?; - tt.l_paren_token()?; + let path = attribute.path()?; + let segments = path.segments().map(|s| s.name_ref()).collect::<Option<Vec<_>>>()?; + let segments = segments.iter().map(|n| n.text()).collect::<Vec<_>>(); + let segments = segments.iter().map(|t| t.as_str()).collect::<Vec<_>>(); + let tt = attribute.token_tree()?; - match path.text().as_str() { - "repr" => repr::complete_repr(acc, ctx, tt), - "feature" => lint::complete_lint( + match segments.as_slice() { + ["repr"] => repr::complete_repr(acc, ctx, tt), + ["feature"] => lint::complete_lint( acc, ctx, colon_prefix, &parse_tt_as_comma_sep_paths(tt, ctx.edition)?, FEATURES, ), - "allow" | "expect" | "deny" | "forbid" | "warn" => { + ["allow"] | ["expect"] | ["deny"] | ["forbid"] | ["warn"] => { let existing_lints = parse_tt_as_comma_sep_paths(tt, ctx.edition)?; let lints: Vec<Lint> = CLIPPY_LINT_GROUPS @@ -70,13 +70,14 @@ pub(crate) fn complete_known_attribute_input( lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints); } - "cfg" => cfg::complete_cfg(acc, ctx), - "macro_use" => macro_use::complete_macro_use( + ["cfg"] => cfg::complete_cfg(acc, ctx), + ["macro_use"] => macro_use::complete_macro_use( acc, ctx, extern_crate, &parse_tt_as_comma_sep_paths(tt, ctx.edition)?, ), + ["diagnostic", "on_unimplemented"] => diagnostic::complete_on_unimplemented(acc, ctx, tt), _ => (), } Some(()) @@ -139,6 +140,8 @@ pub(crate) fn complete_attribute_path( } Qualified::TypeAnchor { .. } | Qualified::With { .. } => {} } + let qualifier_path = + if let Qualified::With { path, .. } = qualified { Some(path) } else { None }; let attributes = annotated_item_kind.and_then(|kind| { if ast::Expr::can_cast(kind) { @@ -149,18 +152,33 @@ pub(crate) fn complete_attribute_path( }); let add_completion = |attr_completion: &AttrCompletion| { - let mut item = CompletionItem::new( - SymbolKind::Attribute, - ctx.source_range(), - attr_completion.label, - ctx.edition, - ); + // if we don't already have the qualifiers of the completion, then + // add the missing parts to the label and snippet + let mut label = attr_completion.label.to_owned(); + let mut snippet = attr_completion.snippet.map(|s| s.to_owned()); + let segments = qualifier_path.iter().flat_map(|q| q.segments()).collect::<Vec<_>>(); + let qualifiers = attr_completion.qualifiers; + let matching_qualifiers = segments + .iter() + .zip(qualifiers) + .take_while(|(s, q)| s.name_ref().is_some_and(|t| t.text() == **q)) + .count(); + if matching_qualifiers != qualifiers.len() { + let prefix = qualifiers[matching_qualifiers..].join("::"); + label = format!("{prefix}::{label}"); + if let Some(s) = snippet.as_mut() { + *s = format!("{prefix}::{s}"); + } + } + + let mut item = + CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label, ctx.edition); if let Some(lookup) = attr_completion.lookup { item.lookup_by(lookup); } - if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) { + if let Some((snippet, cap)) = snippet.zip(ctx.config.snippet_cap) { item.insert_snippet(cap, snippet); } @@ -184,6 +202,7 @@ struct AttrCompletion { label: &'static str, lookup: Option<&'static str>, snippet: Option<&'static str>, + qualifiers: &'static [&'static str], prefer_inner: bool, } @@ -192,6 +211,10 @@ impl AttrCompletion { self.lookup.unwrap_or(self.label) } + const fn qualifiers(self, qualifiers: &'static [&'static str]) -> AttrCompletion { + AttrCompletion { qualifiers, ..self } + } + const fn prefer_inner(self) -> AttrCompletion { AttrCompletion { prefer_inner: true, ..self } } @@ -202,7 +225,7 @@ const fn attr( lookup: Option<&'static str>, snippet: Option<&'static str>, ) -> AttrCompletion { - AttrCompletion { label, lookup, snippet, prefer_inner: false } + AttrCompletion { label, lookup, snippet, qualifiers: &[], prefer_inner: false } } macro_rules! attrs { @@ -264,14 +287,14 @@ static KIND_TO_ATTRIBUTES: LazyLock<FxHashMap<SyntaxKind, &[&str]>> = LazyLock:: FN, attrs!( item, linkable, - "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro", + "cold", "ignore", "inline", "panic_handler", "proc_macro", "proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature", "test", "track_caller" ), ), (STATIC, attrs!(item, linkable, "global_allocator", "used")), - (TRAIT, attrs!(item, "must_use")), - (IMPL, attrs!(item, "automatically_derived")), + (TRAIT, attrs!(item, "diagnostic::on_unimplemented")), + (IMPL, attrs!(item, "automatically_derived", "diagnostic::do_not_recommend")), (ASSOC_ITEM_LIST, attrs!(item)), (EXTERN_BLOCK, attrs!(item, "link")), (EXTERN_ITEM_LIST, attrs!(item, "link")), @@ -311,6 +334,14 @@ const ATTRIBUTES: &[AttrCompletion] = &[ attr("deny(…)", Some("deny"), Some("deny(${0:lint})")), attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)), attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), + attr("do_not_recommend", Some("diagnostic::do_not_recommend"), None) + .qualifiers(&["diagnostic"]), + attr( + "on_unimplemented", + Some("diagnostic::on_unimplemented"), + Some(r#"on_unimplemented(${0:keys})"#), + ) + .qualifiers(&["diagnostic"]), attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)), attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)), attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)), diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/diagnostic.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/diagnostic.rs new file mode 100644 index 00000000000..8adc9742390 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/diagnostic.rs @@ -0,0 +1,60 @@ +//! Completion for diagnostic attributes. + +use ide_db::SymbolKind; +use syntax::ast; + +use crate::{CompletionItem, Completions, context::CompletionContext}; + +use super::AttrCompletion; + +pub(super) fn complete_on_unimplemented( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + input: ast::TokenTree, +) { + if let Some(existing_keys) = super::parse_comma_sep_expr(input) { + for attr in ATTRIBUTE_ARGS { + let already_annotated = existing_keys + .iter() + .filter_map(|expr| match expr { + ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(), + ast::Expr::BinExpr(bin) + if bin.op_kind() == Some(ast::BinaryOp::Assignment { op: None }) => + { + match bin.lhs()? { + ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(), + _ => None, + } + } + _ => None, + }) + .any(|it| { + let text = it.text(); + attr.key() == text && text != "note" + }); + if already_annotated { + continue; + } + + let mut item = CompletionItem::new( + SymbolKind::BuiltinAttr, + ctx.source_range(), + attr.label, + ctx.edition, + ); + if let Some(lookup) = attr.lookup { + item.lookup_by(lookup); + } + if let Some((snippet, cap)) = attr.snippet.zip(ctx.config.snippet_cap) { + item.insert_snippet(cap, snippet); + } + item.add_to(acc, ctx.db); + } + } +} + +const ATTRIBUTE_ARGS: &[AttrCompletion] = &[ + super::attr(r#"label = "…""#, Some("label"), Some(r#"label = "${0:label}""#)), + super::attr(r#"message = "…""#, Some("message"), Some(r#"message = "${0:message}""#)), + super::attr(r#"note = "…""#, Some("note"), Some(r#"note = "${0:note}""#)), +]; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs index 32d3b50f237..411902f1117 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs @@ -30,6 +30,8 @@ pub struct Foo(#[m$0] i32); at deprecated at derive macro derive at derive(…) + at diagnostic::do_not_recommend + at diagnostic::on_unimplemented at doc = "…" at doc(alias = "…") at doc(hidden) @@ -472,13 +474,13 @@ fn attr_on_trait() { at cfg_attr(…) at deny(…) at deprecated + at diagnostic::on_unimplemented at doc = "…" at doc(alias = "…") at doc(hidden) at expect(…) at forbid(…) at must_use - at must_use at no_mangle at warn(…) kw crate:: @@ -498,6 +500,7 @@ fn attr_on_impl() { at cfg_attr(…) at deny(…) at deprecated + at diagnostic::do_not_recommend at doc = "…" at doc(alias = "…") at doc(hidden) @@ -533,6 +536,76 @@ fn attr_on_impl() { } #[test] +fn attr_with_qualifier() { + check( + r#"#[diagnostic::$0] impl () {}"#, + expect![[r#" + at allow(…) + at automatically_derived + at cfg(…) + at cfg_attr(…) + at deny(…) + at deprecated + at do_not_recommend + at doc = "…" + at doc(alias = "…") + at doc(hidden) + at expect(…) + at forbid(…) + at must_use + at no_mangle + at warn(…) + "#]], + ); + check( + r#"#[diagnostic::$0] trait Foo {}"#, + expect![[r#" + at allow(…) + at cfg(…) + at cfg_attr(…) + at deny(…) + at deprecated + at doc = "…" + at doc(alias = "…") + at doc(hidden) + at expect(…) + at forbid(…) + at must_use + at no_mangle + at on_unimplemented + at warn(…) + "#]], + ); +} + +#[test] +fn attr_diagnostic_on_unimplemented() { + check( + r#"#[diagnostic::on_unimplemented($0)] trait Foo {}"#, + expect![[r#" + ba label = "…" + ba message = "…" + ba note = "…" + "#]], + ); + check( + r#"#[diagnostic::on_unimplemented(message = "foo", $0)] trait Foo {}"#, + expect![[r#" + ba label = "…" + ba note = "…" + "#]], + ); + check( + r#"#[diagnostic::on_unimplemented(note = "foo", $0)] trait Foo {}"#, + expect![[r#" + ba label = "…" + ba message = "…" + ba note = "…" + "#]], + ); +} + +#[test] fn attr_on_extern_block() { check( r#"#[$0] extern {}"#, @@ -619,7 +692,6 @@ fn attr_on_fn() { at link_name = "…" at link_section = "…" at must_use - at must_use at no_mangle at panic_handler at proc_macro @@ -649,6 +721,8 @@ fn attr_in_source_file_end() { at deny(…) at deprecated at derive(…) + at diagnostic::do_not_recommend + at diagnostic::on_unimplemented at doc = "…" at doc(alias = "…") at doc(hidden) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs index fa2a46a0f7c..4e737e27f05 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs @@ -20,7 +20,7 @@ //! //! The correct behavior in such cases is probably to show a dialog to the user. //! Our current behavior is ¯\_(ツ)_/¯. -use std::fmt; +use std::fmt::{self, Display}; use crate::{ source_change::ChangeAnnotation, @@ -28,13 +28,12 @@ use crate::{ }; use base_db::AnchoredPathBuf; use either::Either; -use hir::{EditionedFileId, FieldSource, FileRange, InFile, ModuleSource, Semantics}; +use hir::{FieldSource, FileRange, InFile, ModuleSource, Name, Semantics, sym}; use span::{Edition, FileId, SyntaxContext}; use stdx::{TupleExt, never}; use syntax::{ AstNode, SyntaxKind, T, TextRange, ast::{self, HasName}, - utils::is_raw_identifier, }; use crate::{ @@ -70,26 +69,33 @@ macro_rules! _bail { } pub use _bail as bail; +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum RenameDefinition { + Yes, + No, +} + impl Definition { pub fn rename( &self, sema: &Semantics<'_, RootDatabase>, new_name: &str, + rename_definition: RenameDefinition, ) -> Result<SourceChange> { - // We append `r#` if needed. - let new_name = new_name.trim_start_matches("r#"); - // self.krate() returns None if // self is a built-in attr, built-in type or tool module. // it is not allowed for these defs to be renamed. // cases where self.krate() is None is handled below. - if let Some(krate) = self.krate(sema.db) { + let edition = if let Some(krate) = self.krate(sema.db) { // Can we not rename non-local items? // Then bail if non-local if !krate.origin(sema.db).is_local() { bail!("Cannot rename a non-local definition") } - } + krate.edition(sema.db) + } else { + Edition::LATEST + }; match *self { Definition::Module(module) => rename_mod(sema, module, new_name), @@ -103,8 +109,10 @@ impl Definition { bail!("Cannot rename a builtin attr.") } Definition::SelfType(_) => bail!("Cannot rename `Self`"), - Definition::Macro(mac) => rename_reference(sema, Definition::Macro(mac), new_name), - def => rename_reference(sema, def, new_name), + Definition::Macro(mac) => { + rename_reference(sema, Definition::Macro(mac), new_name, rename_definition, edition) + } + def => rename_reference(sema, def, new_name, rename_definition, edition), } } @@ -237,10 +245,6 @@ fn rename_mod( module: hir::Module, new_name: &str, ) -> Result<SourceChange> { - if IdentifierKind::classify(new_name)? != IdentifierKind::Ident { - bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); - } - let mut source_change = SourceChange::default(); if module.is_crate_root() { @@ -248,6 +252,14 @@ fn rename_mod( } let InFile { file_id, value: def_source } = module.definition_source(sema.db); + let edition = file_id.edition(sema.db); + let (new_name, kind) = IdentifierKind::classify(edition, new_name)?; + if kind != IdentifierKind::Ident { + bail!( + "Invalid name `{0}`: cannot rename module to {0}", + new_name.display(sema.db, edition) + ); + } if let ModuleSource::SourceFile(..) = def_source { let anchor = file_id.original_file(sema.db).file_id(sema.db); @@ -256,7 +268,7 @@ fn rename_mod( // Module exists in a named file if !is_mod_rs { - let path = format!("{new_name}.rs"); + let path = format!("{}.rs", new_name.as_str()); let dst = AnchoredPathBuf { anchor, path }; source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst }) } @@ -267,11 +279,11 @@ fn rename_mod( let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) { // Go up one level since the anchor is inside the dir we're trying to rename (true, _, Some(mod_name)) => { - Some((format!("../{}", mod_name.as_str()), format!("../{new_name}"))) + Some((format!("../{}", mod_name.as_str()), format!("../{}", new_name.as_str()))) } // The anchor is on the same level as target dir (false, true, Some(mod_name)) => { - Some((mod_name.as_str().to_owned(), new_name.to_owned())) + Some((mod_name.as_str().to_owned(), new_name.as_str().to_owned())) } _ => None, }; @@ -296,11 +308,7 @@ fn rename_mod( .original_file_range_opt(sema.db) .map(TupleExt::head) { - let new_name = if is_raw_identifier(new_name, file_id.edition(sema.db)) { - format!("r#{new_name}") - } else { - new_name.to_owned() - }; + let new_name = new_name.display(sema.db, edition).to_string(); source_change.insert_source_edit( file_id.file_id(sema.db), TextEdit::replace(file_range.range, new_name), @@ -314,9 +322,10 @@ fn rename_mod( let def = Definition::Module(module); let usages = def.usages(sema).all(); let ref_edits = usages.iter().map(|(file_id, references)| { + let edition = file_id.edition(sema.db); ( file_id.file_id(sema.db), - source_edit_from_references(references, def, new_name, file_id.edition(sema.db)), + source_edit_from_references(sema.db, references, def, &new_name, edition), ) }); source_change.extend(ref_edits); @@ -328,8 +337,10 @@ fn rename_reference( sema: &Semantics<'_, RootDatabase>, def: Definition, new_name: &str, + rename_definition: RenameDefinition, + edition: Edition, ) -> Result<SourceChange> { - let ident_kind = IdentifierKind::classify(new_name)?; + let (mut new_name, ident_kind) = IdentifierKind::classify(edition, new_name)?; if matches!( def, @@ -337,18 +348,34 @@ fn rename_reference( ) { match ident_kind { IdentifierKind::Underscore => { - bail!("Invalid name `{}`: not a lifetime identifier", new_name); + bail!( + "Invalid name `{}`: not a lifetime identifier", + new_name.display(sema.db, edition) + ); + } + IdentifierKind::Ident => { + new_name = Name::new_lifetime(&format!("'{}", new_name.as_str())) } - _ => cov_mark::hit!(rename_lifetime), + IdentifierKind::Lifetime => (), + IdentifierKind::LowercaseSelf => bail!( + "Invalid name `{}`: not a lifetime identifier", + new_name.display(sema.db, edition) + ), } } else { match ident_kind { IdentifierKind::Lifetime => { cov_mark::hit!(rename_not_an_ident_ref); - bail!("Invalid name `{}`: not an identifier", new_name); + bail!("Invalid name `{}`: not an identifier", new_name.display(sema.db, edition)); } IdentifierKind::Ident => cov_mark::hit!(rename_non_local), IdentifierKind::Underscore => (), + IdentifierKind::LowercaseSelf => { + bail!( + "Invalid name `{}`: cannot rename to `self`", + new_name.display(sema.db, edition) + ); + } } } @@ -361,30 +388,29 @@ fn rename_reference( } let mut source_change = SourceChange::default(); source_change.extend(usages.iter().map(|(file_id, references)| { + let edition = file_id.edition(sema.db); ( file_id.file_id(sema.db), - source_edit_from_references(references, def, new_name, file_id.edition(sema.db)), + source_edit_from_references(sema.db, references, def, &new_name, edition), ) })); - - // This needs to come after the references edits, because we change the annotation of existing edits - // if a conflict is detected. - let (file_id, edit) = source_edit_from_def(sema, def, new_name, &mut source_change)?; - source_change.insert_source_edit(file_id, edit); + if rename_definition == RenameDefinition::Yes { + // This needs to come after the references edits, because we change the annotation of existing edits + // if a conflict is detected. + let (file_id, edit) = source_edit_from_def(sema, def, &new_name, &mut source_change)?; + source_change.insert_source_edit(file_id, edit); + } Ok(source_change) } pub fn source_edit_from_references( + db: &RootDatabase, references: &[FileReference], def: Definition, - new_name: &str, + new_name: &Name, edition: Edition, ) -> TextEdit { - let new_name = if is_raw_identifier(new_name, edition) { - format!("r#{new_name}") - } else { - new_name.to_owned() - }; + let name_display = new_name.display(db, edition); let mut edit = TextEdit::builder(); // macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far let mut edited_ranges = Vec::new(); @@ -395,23 +421,15 @@ pub fn source_edit_from_references( // to make special rewrites like shorthand syntax and such, so just rename the node in // the macro input FileReferenceNode::NameRef(name_ref) if name_range == range => { - source_edit_from_name_ref(&mut edit, name_ref, &new_name, def) + source_edit_from_name_ref(&mut edit, name_ref, &name_display, def) } FileReferenceNode::Name(name) if name_range == range => { - source_edit_from_name(&mut edit, name, &new_name) + source_edit_from_name(&mut edit, name, &name_display) } _ => false, }; if !has_emitted_edit && !edited_ranges.contains(&range.start()) { - let (range, new_name) = match name { - FileReferenceNode::Lifetime(_) => ( - TextRange::new(range.start() + syntax::TextSize::from(1), range.end()), - new_name.strip_prefix('\'').unwrap_or(&new_name).to_owned(), - ), - _ => (range, new_name.to_owned()), - }; - - edit.replace(range, new_name); + edit.replace(range, name_display.to_string()); edited_ranges.push(range.start()); } } @@ -419,7 +437,11 @@ pub fn source_edit_from_references( edit.finish() } -fn source_edit_from_name(edit: &mut TextEditBuilder, name: &ast::Name, new_name: &str) -> bool { +fn source_edit_from_name( + edit: &mut TextEditBuilder, + name: &ast::Name, + new_name: &dyn Display, +) -> bool { if ast::RecordPatField::for_field_name(name).is_some() { if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { cov_mark::hit!(rename_record_pat_field_name_split); @@ -439,7 +461,7 @@ fn source_edit_from_name(edit: &mut TextEditBuilder, name: &ast::Name, new_name: fn source_edit_from_name_ref( edit: &mut TextEditBuilder, name_ref: &ast::NameRef, - new_name: &str, + new_name: &dyn Display, def: Definition, ) -> bool { if name_ref.super_token().is_some() { @@ -452,6 +474,7 @@ fn source_edit_from_name_ref( match &(rcf_name_ref, rcf_expr.and_then(|it| expr_as_name_ref(&it))) { // field: init-expr, check if we can use a field init shorthand (Some(field_name), Some(init)) => { + let new_name = new_name.to_string(); if field_name == name_ref { if init.text() == new_name { cov_mark::hit!(test_rename_field_put_init_shorthand); @@ -507,6 +530,7 @@ fn source_edit_from_name_ref( { // field name is being renamed if let Some(name) = pat.name() { + let new_name = new_name.to_string(); if name.text() == new_name { cov_mark::hit!(test_rename_field_put_init_shorthand_pat); // Foo { field: ref mut local } -> Foo { ref mut field } @@ -518,7 +542,7 @@ fn source_edit_from_name_ref( let s = field_name.syntax().text_range().start(); let e = pat.syntax().text_range().start(); edit.delete(TextRange::new(s, e)); - edit.replace(name.syntax().text_range(), new_name.to_owned()); + edit.replace(name.syntax().text_range(), new_name); return true; } } @@ -532,16 +556,9 @@ fn source_edit_from_name_ref( fn source_edit_from_def( sema: &Semantics<'_, RootDatabase>, def: Definition, - new_name: &str, + new_name: &Name, source_change: &mut SourceChange, ) -> Result<(FileId, TextEdit)> { - let new_name_edition_aware = |new_name: &str, file_id: EditionedFileId| { - if is_raw_identifier(new_name, file_id.edition(sema.db)) { - format!("r#{new_name}") - } else { - new_name.to_owned() - } - }; let mut edit = TextEdit::builder(); if let Definition::Local(local) = def { let mut file_id = None; @@ -573,7 +590,10 @@ fn source_edit_from_def( { Some(FileRange { file_id: file_id2, range }) => { file_id = Some(file_id2); - edit.replace(range, new_name_edition_aware(new_name, file_id2)); + edit.replace( + range, + new_name.display(sema.db, file_id2.edition(sema.db)).to_string(), + ); continue; } None => { @@ -587,7 +607,7 @@ fn source_edit_from_def( // special cases required for renaming fields/locals in Record patterns if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) { if let Some(name_ref) = pat_field.name_ref() { - if new_name == name_ref.text().as_str().trim_start_matches("r#") + if new_name.as_str() == name_ref.text().as_str().trim_start_matches("r#") && pat.at_token().is_none() { // Foo { field: ref mut local } -> Foo { ref mut field } @@ -607,7 +627,9 @@ fn source_edit_from_def( // ^^^^^ replace this with `new_name` edit.replace( name_range, - new_name_edition_aware(new_name, source.file_id), + new_name + .display(sema.db, source.file_id.edition(sema.db)) + .to_string(), ); } } else { @@ -618,10 +640,16 @@ fn source_edit_from_def( pat.syntax().text_range().start(), format!("{}: ", pat_field.field_name().unwrap()), ); - edit.replace(name_range, new_name_edition_aware(new_name, source.file_id)); + edit.replace( + name_range, + new_name.display(sema.db, source.file_id.edition(sema.db)).to_string(), + ); } } else { - edit.replace(name_range, new_name_edition_aware(new_name, source.file_id)); + edit.replace( + name_range, + new_name.display(sema.db, source.file_id.edition(sema.db)).to_string(), + ); } } } @@ -639,16 +667,13 @@ fn source_edit_from_def( .range_for_rename(sema) .ok_or_else(|| format_err!("No identifier available to rename"))?; let (range, new_name) = match def { - Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) => ( - TextRange::new(range.start() + syntax::TextSize::from(1), range.end()), - new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(), + Definition::ExternCrateDecl(decl) if decl.alias(sema.db).is_none() => ( + TextRange::empty(range.end()), + format!(" as {}", new_name.display(sema.db, file_id.edition(sema.db)),), ), - Definition::ExternCrateDecl(decl) if decl.alias(sema.db).is_none() => { - (TextRange::empty(range.end()), format!(" as {new_name}")) - } - _ => (range, new_name.to_owned()), + _ => (range, new_name.display(sema.db, file_id.edition(sema.db)).to_string()), }; - edit.replace(range, new_name_edition_aware(&new_name, file_id)); + edit.replace(range, new_name); Ok((file_id.file_id(sema.db), edit.finish())) } @@ -657,26 +682,27 @@ pub enum IdentifierKind { Ident, Lifetime, Underscore, + LowercaseSelf, } impl IdentifierKind { - pub fn classify(new_name: &str) -> Result<IdentifierKind> { - let new_name = new_name.trim_start_matches("r#"); - match parser::LexedStr::single_token(Edition::LATEST, new_name) { + pub fn classify(edition: Edition, new_name: &str) -> Result<(Name, IdentifierKind)> { + match parser::LexedStr::single_token(edition, new_name) { Some(res) => match res { - (SyntaxKind::IDENT, _) => { - if let Some(inner) = new_name.strip_prefix("r#") { - if matches!(inner, "self" | "crate" | "super" | "Self") { - bail!("Invalid name: `{}` cannot be a raw identifier", inner); - } - } - Ok(IdentifierKind::Ident) + (SyntaxKind::IDENT, _) => Ok((Name::new_root(new_name), IdentifierKind::Ident)), + (T![_], _) => { + Ok((Name::new_symbol_root(sym::underscore), IdentifierKind::Underscore)) } - (T![_], _) => Ok(IdentifierKind::Underscore), (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { - Ok(IdentifierKind::Lifetime) + Ok((Name::new_lifetime(new_name), IdentifierKind::Lifetime)) } - _ if is_raw_identifier(new_name, Edition::LATEST) => Ok(IdentifierKind::Ident), + _ if SyntaxKind::from_keyword(new_name, edition).is_some() => match new_name { + "self" => Ok((Name::new_root(new_name), IdentifierKind::LowercaseSelf)), + "crate" | "super" | "Self" => { + bail!("Invalid name `{}`: cannot rename to a keyword", new_name) + } + _ => Ok((Name::new_root(new_name), IdentifierKind::Ident)), + }, (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), }, 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 38f10c778d6..519ff192799 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 @@ -1,5 +1,5 @@ use hir::{CaseType, InFile, db::ExpandDatabase}; -use ide_db::{assists::Assist, defs::NameClass}; +use ide_db::{assists::Assist, defs::NameClass, rename::RenameDefinition}; use syntax::AstNode; use crate::{ @@ -44,7 +44,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Ass let label = format!("Rename to {}", d.suggested_text); let mut res = unresolved_fix("change_case", &label, frange.range); if ctx.resolve.should_resolve(&res.id) { - let source_change = def.rename(&ctx.sema, &d.suggested_text); + let source_change = def.rename(&ctx.sema, &d.suggested_text, RenameDefinition::Yes); res.source_change = Some(source_change.ok().unwrap_or_default()); } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs index 87c9397fb77..bf7dddacd8c 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs @@ -135,6 +135,7 @@ pub(crate) fn json_in_items( "JSON syntax is not valid as a Rust item", FileRange { file_id: vfs_file_id, range }, ) + .stable() .with_fixes(Some(vec![{ let mut scb = SourceChangeBuilder::new(vfs_file_id); let scope = scb.make_import_scope_mut(import_scope); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs index 84fb467a5ce..ef42f2dc744 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs @@ -1,4 +1,5 @@ use either::Either; +use hir::{Field, HasCrate}; use hir::{HasSource, HirDisplay, Semantics, VariantId, db::ExpandDatabase}; use ide_db::text_edit::TextEdit; use ide_db::{EditionedFileId, RootDatabase, source_change::SourceChange}; @@ -13,44 +14,69 @@ use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix}; // // This diagnostic is triggered if created structure does not have field provided in record. pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { - let node = d.field.map(Into::into); - if d.private { - // FIXME: quickfix to add required visibility - Diagnostic::new_with_syntax_node_ptr( - ctx, - DiagnosticCode::RustcHardError("E0451"), - "field is private", - node, - ) - .stable() + let (code, message) = if d.private.is_some() { + ("E0451", "field is private") + } else if let VariantId::EnumVariantId(_) = d.variant { + ("E0559", "no such field") } else { - Diagnostic::new_with_syntax_node_ptr( - ctx, - match d.variant { - VariantId::EnumVariantId(_) => DiagnosticCode::RustcHardError("E0559"), - _ => DiagnosticCode::RustcHardError("E0560"), - }, - "no such field", - node, - ) + ("E0560", "no such field") + }; + + let node = d.field.map(Into::into); + Diagnostic::new_with_syntax_node_ptr(ctx, DiagnosticCode::RustcHardError(code), message, node) .stable() .with_fixes(fixes(ctx, d)) - } } fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> { // FIXME: quickfix for pattern let root = ctx.sema.db.parse_or_expand(d.field.file_id); match &d.field.value.to_node(&root) { - Either::Left(node) => missing_record_expr_field_fixes( - &ctx.sema, - d.field.file_id.original_file(ctx.sema.db), - node, - ), + Either::Left(node) => { + if let Some(private_field) = d.private { + field_is_private_fixes( + &ctx.sema, + d.field.file_id.original_file(ctx.sema.db), + node, + private_field, + ) + } else { + missing_record_expr_field_fixes( + &ctx.sema, + d.field.file_id.original_file(ctx.sema.db), + node, + ) + } + } _ => None, } } +fn field_is_private_fixes( + sema: &Semantics<'_, RootDatabase>, + usage_file_id: EditionedFileId, + record_expr_field: &ast::RecordExprField, + private_field: Field, +) -> Option<Vec<Assist>> { + let def_crate = private_field.krate(sema.db); + let usage_crate = sema.file_to_module_def(usage_file_id.file_id(sema.db))?.krate(); + let visibility = if usage_crate == def_crate { "pub(crate) " } else { "pub " }; + + let source = private_field.source(sema.db)?; + let (range, _) = source.syntax().original_file_range_opt(sema.db)?; + let source_change = SourceChange::from_text_edit( + range.file_id.file_id(sema.db), + TextEdit::insert(range.range.start(), visibility.into()), + ); + + Some(vec![fix( + "increase_field_visibility", + "Increase field visibility", + source_change, + sema.original_range(record_expr_field.syntax()).range, + )]) +} + fn missing_record_expr_field_fixes( sema: &Semantics<'_, RootDatabase>, usage_file_id: EditionedFileId, @@ -118,7 +144,7 @@ fn missing_record_expr_field_fixes( "create_field", "Create field", source_change, - record_expr_field.syntax().text_range(), + sema.original_range(record_expr_field.syntax()).range, )]); fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { @@ -387,15 +413,15 @@ fn f(s@m::Struct { // assignee expression m::Struct { field: 0, - //^^^^^^^^ error: field is private + //^^^^^^^^ 💡 error: field is private field2 - //^^^^^^ error: field is private + //^^^^^^ 💡 error: field is private } = s; m::Struct { field: 0, - //^^^^^^^^ error: field is private + //^^^^^^^^ 💡 error: field is private field2 - //^^^^^^ error: field is private + //^^^^^^ 💡 error: field is private }; } "#, @@ -403,6 +429,77 @@ fn f(s@m::Struct { } #[test] + fn test_struct_field_private_same_crate_fix() { + check_diagnostics( + r#" +mod m { + pub struct Struct { + field: u32, + } +} +fn f() { + let _ = m::Struct { + field: 0, + //^^^^^^^^ 💡 error: field is private + }; +} +"#, + ); + + check_fix( + r#" +mod m { + pub struct Struct { + field: u32, + } +} +fn f() { + let _ = m::Struct { + field$0: 0, + }; +} +"#, + r#" +mod m { + pub struct Struct { + pub(crate) field: u32, + } +} +fn f() { + let _ = m::Struct { + field: 0, + }; +} +"#, + ); + } + + #[test] + fn test_struct_field_private_other_crate_fix() { + check_fix( + r#" +//- /lib.rs crate:another_crate +pub struct Struct { + field: u32, +} +//- /lib.rs crate:this_crate deps:another_crate +use another_crate; + +fn f() { + let _ = another_crate::Struct { + field$0: 0, + }; +} +"#, + r#" +pub struct Struct { + pub field: u32, +} +"#, + ); + } + + #[test] fn editions_between_macros() { check_diagnostics( r#" diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs index af9126c8933..d96c658d7b0 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs @@ -69,6 +69,7 @@ pub(crate) fn unlinked_file( FileRange { file_id, range }, ) .with_unused(unused) + .stable() .with_fixes(fixes), ); } diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs index 7c396339c14..f31886b9697 100644 --- a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs +++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs @@ -800,4 +800,65 @@ foo(); foo();"#]], ); } + + #[test] + fn works_in_sig() { + check( + r#" +macro_rules! foo { + () => { u32 }; +} +fn foo() -> foo$0!() { + 42 +} +"#, + expect![[r#" + foo! + u32"#]], + ); + check( + r#" +macro_rules! foo { + () => { u32 }; +} +fn foo(_: foo$0!() ) {} +"#, + expect![[r#" + foo! + u32"#]], + ); + } + + #[test] + fn works_in_generics() { + check( + r#" +trait Trait {} +macro_rules! foo { + () => { Trait }; +} +impl<const C: foo$0!()> Trait for () {} +"#, + expect![[r#" + foo! + Trait"#]], + ); + } + + #[test] + fn works_in_fields() { + check( + r#" +macro_rules! foo { + () => { u32 }; +} +struct S { + field: foo$0!(), +} +"#, + expect![[r#" + foo! + u32"#]], + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs index 82704af647d..b094b098462 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -6,7 +6,7 @@ use std::{ use either::Either; use hir::{ ClosureStyle, DisplayTarget, EditionedFileId, HasVisibility, HirDisplay, HirDisplayError, - HirWrite, ModuleDef, ModuleDefId, Semantics, sym, + HirWrite, InRealFile, ModuleDef, ModuleDefId, Semantics, sym, }; use ide_db::{FileRange, RootDatabase, famous_defs::FamousDefs, text_edit::TextEditBuilder}; use ide_db::{FxHashSet, text_edit::TextEdit}; @@ -34,6 +34,7 @@ mod extern_block; mod generic_param; mod implicit_drop; mod implicit_static; +mod implied_dyn_trait; mod lifetime; mod param_name; mod range_exclusive; @@ -95,16 +96,16 @@ pub(crate) fn inlay_hints( return acc; }; let famous_defs = FamousDefs(&sema, scope.krate()); + let display_target = famous_defs.1.to_display_target(sema.db); let ctx = &mut InlayHintCtx::default(); let mut hints = |event| { if let Some(node) = handle_event(ctx, event) { - hints(&mut acc, ctx, &famous_defs, config, file_id, node); + hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node); } }; let mut preorder = file.preorder(); while let Some(event) = preorder.next() { - // FIXME: This can miss some hints that require the parent of the range to calculate if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none()) { preorder.skip_subtree(); @@ -144,10 +145,12 @@ pub(crate) fn inlay_hints_resolve( let famous_defs = FamousDefs(&sema, scope.krate()); let mut acc = Vec::new(); + let display_target = famous_defs.1.to_display_target(sema.db); + let ctx = &mut InlayHintCtx::default(); let mut hints = |event| { if let Some(node) = handle_event(ctx, event) { - hints(&mut acc, ctx, &famous_defs, config, file_id, node); + hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node); } }; @@ -202,17 +205,19 @@ fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<S fn hints( hints: &mut Vec<InlayHint>, ctx: &mut InlayHintCtx, - famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, + famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>, config: &InlayHintsConfig, file_id: EditionedFileId, + display_target: DisplayTarget, node: SyntaxNode, ) { - let file_id = file_id.editioned_file_id(sema.db); - let Some(krate) = sema.first_crate(file_id.file_id()) else { - return; - }; - let display_target = krate.to_display_target(sema.db); - closing_brace::hints(hints, sema, config, file_id, display_target, node.clone()); + closing_brace::hints( + hints, + sema, + config, + display_target, + InRealFile { file_id, value: node.clone() }, + ); if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) { generic_param::hints(hints, famous_defs, config, any_has_generic_args); } @@ -231,18 +236,18 @@ fn hints( closure_captures::hints(hints, famous_defs, config, it.clone()); closure_ret::hints(hints, famous_defs, config, display_target, it) }, - ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, file_id, it), + ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it), _ => Some(()), } }, ast::Pat(it) => { - binding_mode::hints(hints, famous_defs, config, file_id, &it); + binding_mode::hints(hints, famous_defs, config, &it); match it { ast::Pat::IdentPat(it) => { bind_pat::hints(hints, famous_defs, config, display_target, &it); } ast::Pat::RangePat(it) => { - range_exclusive::hints(hints, famous_defs, config, file_id, it); + range_exclusive::hints(hints, famous_defs, config, it); } _ => {} } @@ -250,30 +255,38 @@ fn hints( }, ast::Item(it) => match it { ast::Item::Fn(it) => { - implicit_drop::hints(hints, famous_defs, config, file_id, &it); + implicit_drop::hints(hints, famous_defs, config, display_target, &it); if let Some(extern_block) = &ctx.extern_block_parent { - extern_block::fn_hints(hints, famous_defs, config, file_id, &it, extern_block); + extern_block::fn_hints(hints, famous_defs, config, &it, extern_block); } - lifetime::fn_hints(hints, ctx, famous_defs, config, file_id, it) + lifetime::fn_hints(hints, ctx, famous_defs, config, it) }, ast::Item::Static(it) => { if let Some(extern_block) = &ctx.extern_block_parent { - extern_block::static_hints(hints, famous_defs, config, file_id, &it, extern_block); + extern_block::static_hints(hints, famous_defs, config, &it, extern_block); } - implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)) + implicit_static::hints(hints, famous_defs, config, Either::Left(it)) }, - ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Right(it)), - ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it), - ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, file_id, it), + ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)), + ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it), + ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it), _ => None, }, // FIXME: trait object type elisions ast::Type(ty) => match ty { - ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config, file_id, ptr), - ast::Type::PathType(path) => lifetime::fn_path_hints(hints, ctx, famous_defs, config, file_id, path), + ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config, ptr), + ast::Type::PathType(path) => { + lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path); + implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path)); + Some(()) + }, + ast::Type::DynTraitType(dyn_) => { + implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_)); + Some(()) + }, _ => Some(()), }, - ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, file_id, it), + ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, it), _ => Some(()), } }; @@ -438,6 +451,7 @@ pub enum InlayKind { Parameter, GenericParameter, Type, + Dyn, Drop, RangeExclusive, ExternUnsafety, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs index d2917320688..169ab92342b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs @@ -8,7 +8,6 @@ use hir::Mutability; use ide_db::famous_defs::FamousDefs; use ide_db::text_edit::TextEditBuilder; -use span::EditionedFileId; use syntax::ast::{self, AstNode}; use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind}; @@ -17,7 +16,6 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, pat: &ast::Pat, ) -> Option<()> { if !config.binding_mode_hints { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bounds.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bounds.rs index 8ddbfaeffe8..b9a98f88be7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bounds.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bounds.rs @@ -3,7 +3,6 @@ //! Currently this renders the implied `Sized` bound. use ide_db::{FileRange, famous_defs::FamousDefs}; -use span::EditionedFileId; use syntax::ast::{self, AstNode, HasTypeBounds}; use crate::{ @@ -15,7 +14,6 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, params: ast::GenericParamList, ) -> Option<()> { if !config.sized_bound { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs index 2ec85da4a42..ca3a982760f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs @@ -3,9 +3,8 @@ //! fn g() { //! } /* fn g */ //! ``` -use hir::{DisplayTarget, HirDisplay, Semantics}; +use hir::{DisplayTarget, HirDisplay, InRealFile, Semantics}; use ide_db::{FileRange, RootDatabase}; -use span::EditionedFileId; use syntax::{ SyntaxKind, SyntaxNode, T, ast::{self, AstNode, HasLoopBody, HasName}, @@ -21,15 +20,14 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, sema: &Semantics<'_, RootDatabase>, config: &InlayHintsConfig, - file_id: EditionedFileId, display_target: DisplayTarget, - original_node: SyntaxNode, + InRealFile { file_id, value: node }: InRealFile<SyntaxNode>, ) -> Option<()> { let min_lines = config.closing_brace_hints_min_lines?; let name = |it: ast::Name| it.syntax().text_range(); - let mut node = original_node.clone(); + let mut node = node.clone(); let mut closing_token; let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) { closing_token = item_list.r_curly_token()?; @@ -44,7 +42,7 @@ pub(super) fn hints( let hint_text = match trait_ { Some(tr) => format!( "impl {} for {}", - tr.name(sema.db).display(sema.db, file_id.edition()), + tr.name(sema.db).display(sema.db, display_target.edition), ty.display_truncated(sema.db, config.max_length, display_target, )), None => format!("impl {}", ty.display_truncated(sema.db, config.max_length, display_target)), @@ -142,7 +140,8 @@ pub(super) fn hints( return None; } - let linked_location = name_range.map(|range| FileRange { file_id: file_id.into(), range }); + let linked_location = + name_range.map(|range| FileRange { file_id: file_id.file_id(sema.db), range }); acc.push(InlayHint { range: closing_token.text_range(), kind: InlayKind::ClosingBrace, @@ -151,7 +150,7 @@ pub(super) fn hints( position: InlayHintPosition::After, pad_left: true, pad_right: false, - resolve_parent: Some(original_node.text_range()), + resolve_parent: Some(node.text_range()), }); None diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs index 827a0438dd0..a2a702835a7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs @@ -7,7 +7,6 @@ use hir::Semantics; use ide_db::text_edit::TextEdit; use ide_db::{RootDatabase, famous_defs::FamousDefs}; -use span::EditionedFileId; use syntax::ast::{self, AstNode, HasName}; use crate::{ @@ -19,7 +18,6 @@ pub(super) fn enum_hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _: EditionedFileId, enum_: ast::Enum, ) -> Option<()> { if let DiscriminantHints::Never = config.discriminant_hints { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/extern_block.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/extern_block.rs index 20f54b2cd19..88152bf3e38 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/extern_block.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/extern_block.rs @@ -1,6 +1,5 @@ //! Extern block hints use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; -use span::EditionedFileId; use syntax::{AstNode, SyntaxToken, ast}; use crate::{InlayHint, InlayHintsConfig}; @@ -9,7 +8,6 @@ pub(super) fn extern_block_hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, extern_block: ast::ExternBlock, ) -> Option<()> { if extern_block.unsafe_token().is_some() { @@ -36,7 +34,6 @@ pub(super) fn fn_hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, fn_: &ast::Fn, extern_block: &ast::ExternBlock, ) -> Option<()> { @@ -55,7 +52,6 @@ pub(super) fn static_hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, static_: &ast::Static, extern_block: &ast::ExternBlock, ) -> Option<()> { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs index f52e27946ff..bf4688e9d82 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs @@ -12,7 +12,6 @@ use hir::{ }; use ide_db::{FileRange, famous_defs::FamousDefs}; -use span::EditionedFileId; use syntax::{ ToSmolStr, ast::{self, AstNode}, @@ -25,7 +24,7 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: EditionedFileId, + display_target: hir::DisplayTarget, node: &ast::Fn, ) -> Option<()> { if !config.implicit_drop_hints { @@ -94,7 +93,7 @@ pub(super) fn hints( MirSpan::Unknown => continue, }; let binding = &hir.bindings[binding_idx]; - let name = binding.name.display_no_db(file_id.edition()).to_smolstr(); + let name = binding.name.display_no_db(display_target.edition).to_smolstr(); if name.starts_with("<ra@") { continue; // Ignore desugared variables } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs index f3be09f30a1..7212efd954e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs @@ -5,7 +5,6 @@ use either::Either; use ide_db::famous_defs::FamousDefs; use ide_db::text_edit::TextEdit; -use span::EditionedFileId; use syntax::{ SyntaxKind, ast::{self, AstNode}, @@ -17,7 +16,6 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, FamousDefs(_sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, statik_or_const: Either<ast::Static, ast::Const>, ) -> Option<()> { if config.lifetime_elision_hints != LifetimeElisionHints::Always { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs new file mode 100644 index 00000000000..32d130503a4 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs @@ -0,0 +1,133 @@ +//! Implementation of trait bound hints. +//! +//! Currently this renders the implied `Sized` bound. +use either::Either; +use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; + +use syntax::ast::{self, AstNode}; + +use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind}; + +pub(super) fn hints( + acc: &mut Vec<InlayHint>, + FamousDefs(sema, _): &FamousDefs<'_, '_>, + config: &InlayHintsConfig, + path: Either<ast::PathType, ast::DynTraitType>, +) -> Option<()> { + let parent = path.syntax().parent()?; + let range = match path { + Either::Left(path) => { + let paren = + parent.ancestors().take_while(|it| ast::ParenType::can_cast(it.kind())).last(); + let parent = paren.as_ref().and_then(|it| it.parent()).unwrap_or(parent); + if ast::TypeBound::can_cast(parent.kind()) + || ast::TypeAnchor::can_cast(parent.kind()) + || ast::Impl::cast(parent) + .and_then(|it| it.trait_()) + .is_some_and(|it| it.syntax() == path.syntax()) + { + return None; + } + sema.resolve_trait(&path.path()?)?; + paren.map_or_else(|| path.syntax().text_range(), |it| it.text_range()) + } + Either::Right(dyn_) => { + if dyn_.dyn_token().is_some() { + return None; + } + + dyn_.syntax().text_range() + } + }; + + acc.push(InlayHint { + range, + kind: InlayKind::Dyn, + label: InlayHintLabel::simple("dyn", None, None), + text_edit: Some( + config.lazy_text_edit(|| TextEdit::insert(range.start(), "dyn ".to_owned())), + ), + position: InlayHintPosition::Before, + pad_left: false, + pad_right: true, + resolve_parent: Some(range), + }); + + Some(()) +} + +#[cfg(test)] +mod tests { + + use expect_test::expect; + + use crate::inlay_hints::InlayHintsConfig; + + use crate::inlay_hints::tests::{DISABLED_CONFIG, check_edit, check_with_config}; + + #[track_caller] + fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + check_with_config(InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG }, ra_fixture); + } + + #[test] + fn path_works() { + check( + r#" +struct S {} +trait T {} +fn foo(_: T, _: dyn T, _: S) {} + // ^ dyn +fn foo(_: &T, _: for<'a> T) {} + // ^ dyn + // ^ dyn +impl T {} + // ^ dyn +impl T for (T) {} + // ^^^ dyn +"#, + ); + } + + #[test] + fn missing_dyn_bounds() { + check( + r#" +trait T {} +fn foo( + _: T + T, + // ^^^^^ dyn + _: T + 'a, + // ^^^^^^ dyn + _: 'a + T, + // ^^^^^^ dyn + _: &(T + T) + // ^^^^^ dyn + _: &mut (T + T) + // ^^^^^ dyn + _: *mut (T), + // ^^^ dyn +) {} +"#, + ); + } + + #[test] + fn edit() { + check_edit( + DISABLED_CONFIG, + r#" +trait T {} +fn foo( + _: &mut T +) {} +"#, + expect![[r#" + trait T {} + fn foo( + _: &mut dyn T + ) {} + "#]], + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs index baba49a427d..0069452e7b9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs @@ -6,7 +6,6 @@ use std::iter; use ide_db::{FxHashMap, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty}; use itertools::Itertools; -use span::EditionedFileId; use syntax::{SmolStr, format_smolstr}; use syntax::{ SyntaxKind, SyntaxToken, @@ -23,7 +22,6 @@ pub(super) fn fn_hints( ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: EditionedFileId, func: ast::Fn, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { @@ -40,7 +38,6 @@ pub(super) fn fn_hints( ctx, fd, config, - file_id, param_list.params().filter_map(|it| { Some(( it.pat().and_then(|it| match it { @@ -74,7 +71,6 @@ pub(super) fn fn_ptr_hints( ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: EditionedFileId, func: ast::FnPtrType, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { @@ -97,7 +93,6 @@ pub(super) fn fn_ptr_hints( ctx, fd, config, - file_id, param_list.params().filter_map(|it| { Some(( it.pat().and_then(|it| match it { @@ -140,8 +135,7 @@ pub(super) fn fn_path_hints( ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: EditionedFileId, - func: ast::PathType, + func: &ast::PathType, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { return None; @@ -163,7 +157,6 @@ pub(super) fn fn_path_hints( ctx, fd, config, - file_id, param_list.type_args().filter_map(|it| Some((None, it.ty()?))), generic_param_list, ret_type, @@ -202,7 +195,6 @@ fn hints_( ctx: &mut InlayHintCtx, FamousDefs(_, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, params: impl Iterator<Item = (Option<ast::Name>, ast::Type)>, generic_param_list: Option<ast::GenericParamList>, ret_type: Option<ast::RetType>, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs index d67d8458840..47bd6d737f8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs @@ -4,7 +4,6 @@ //! if let ../* < */100 = 50 {} //! ``` use ide_db::famous_defs::FamousDefs; -use span::EditionedFileId; use syntax::{SyntaxToken, T, ast}; use crate::{InlayHint, InlayHintsConfig}; @@ -13,7 +12,6 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, FamousDefs(_sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, range: impl ast::RangeItem, ) -> Option<()> { (config.range_exclusive_hints && range.end().is_some()) diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index 0423e3da2c8..fb84e8e6b47 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs @@ -4,11 +4,11 @@ //! tests. This module also implements a couple of magic tricks, like renaming //! `self` and to `self` (to switch between associated function and method). -use hir::{AsAssocItem, InFile, Semantics}; +use hir::{AsAssocItem, InFile, Name, Semantics, sym}; use ide_db::{ FileId, FileRange, RootDatabase, defs::{Definition, NameClass, NameRefClass}, - rename::{IdentifierKind, bail, format_err, source_edit_from_references}, + rename::{IdentifierKind, RenameDefinition, bail, format_err, source_edit_from_references}, source_change::SourceChangeBuilder, }; use itertools::Itertools; @@ -33,8 +33,8 @@ pub(crate) fn prepare_rename( let source_file = sema.parse_guess_edition(position.file_id); let syntax = source_file.syntax(); - let res = find_definitions(&sema, syntax, position)? - .map(|(frange, kind, def)| { + let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))? + .map(|(frange, kind, def, _, _)| { // ensure all ranges are valid if def.range_for_rename(&sema).is_none() { @@ -88,22 +88,28 @@ pub(crate) fn rename( let source_file = sema.parse(file_id); let syntax = source_file.syntax(); - let defs = find_definitions(&sema, syntax, position)?; - let alias_fallback = alias_fallback(syntax, position, new_name); + let edition = file_id.edition(db); + let (new_name, kind) = IdentifierKind::classify(edition, new_name)?; + + let defs = find_definitions(&sema, syntax, position, &new_name)?; + let alias_fallback = + alias_fallback(syntax, position, &new_name.display(db, edition).to_string()); let ops: RenameResult<Vec<SourceChange>> = match alias_fallback { Some(_) => defs // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can // properly find "direct" usages/references. - .map(|(.., def)| { - match IdentifierKind::classify(new_name)? { + .map(|(.., def, new_name, _)| { + match kind { IdentifierKind::Ident => (), IdentifierKind::Lifetime => { bail!("Cannot alias reference to a lifetime identifier") } IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"), + IdentifierKind::LowercaseSelf => { + bail!("Cannot rename alias reference to `self`") + } }; - let mut usages = def.usages(&sema).all(); // FIXME: hack - removes the usage that triggered this rename operation. @@ -120,7 +126,7 @@ pub(crate) fn rename( source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| { ( position.file_id, - source_edit_from_references(refs, def, new_name, file_id.edition(db)), + source_edit_from_references(db, refs, def, &new_name, edition), ) })); @@ -128,18 +134,18 @@ pub(crate) fn rename( }) .collect(), None => defs - .map(|(.., def)| { + .map(|(.., def, new_name, rename_def)| { if let Definition::Local(local) = def { if let Some(self_param) = local.as_self_param(sema.db) { cov_mark::hit!(rename_self_to_param); - return rename_self_to_param(&sema, local, self_param, new_name); + return rename_self_to_param(&sema, local, self_param, &new_name, kind); } - if new_name == "self" { + if kind == IdentifierKind::LowercaseSelf { cov_mark::hit!(rename_to_self); return rename_to_self(&sema, local); } } - def.rename(&sema, new_name) + def.rename(&sema, new_name.as_str(), rename_def) }) .collect(), }; @@ -159,7 +165,7 @@ pub(crate) fn will_rename_file( let sema = Semantics::new(db); let module = sema.file_to_module_def(file_id)?; let def = Definition::Module(module); - let mut change = def.rename(&sema, new_name_stem).ok()?; + let mut change = def.rename(&sema, new_name_stem, RenameDefinition::Yes).ok()?; change.file_system_edits.clear(); Some(change) } @@ -200,22 +206,40 @@ fn find_definitions( sema: &Semantics<'_, RootDatabase>, syntax: &SyntaxNode, FilePosition { file_id, offset }: FilePosition, -) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition)>> { - let token = syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING)); + new_name: &Name, +) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition, Name, RenameDefinition)>> +{ + let maybe_format_args = + syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING)); if let Some((range, _, _, Some(resolution))) = - token.and_then(|token| sema.check_for_format_args_template(token, offset)) + maybe_format_args.and_then(|token| sema.check_for_format_args_template(token, offset)) { return Ok(vec![( FileRange { file_id, range }, SyntaxKind::STRING, Definition::from(resolution), + new_name.clone(), + RenameDefinition::Yes, )] .into_iter()); } + let original_ident = syntax + .token_at_offset(offset) + .max_by_key(|t| { + t.kind().is_any_identifier() || matches!(t.kind(), SyntaxKind::LIFETIME_IDENT) + }) + .map(|t| { + if t.kind() == SyntaxKind::LIFETIME_IDENT { + Name::new_lifetime(t.text()) + } else { + Name::new_root(t.text()) + } + }) + .ok_or_else(|| format_err!("No references found at position"))?; let symbols = - sema.find_nodes_at_offset_with_descend::<ast::NameLike>(syntax, offset).map(|name_like| { + sema.find_namelike_at_offset_with_descend(syntax, offset).map(|name_like| { let kind = name_like.syntax().kind(); let range = sema .original_range_opt(name_like.syntax()) @@ -284,23 +308,28 @@ fn find_definitions( .ok_or_else(|| format_err!("No references found at position")) } }; - res.map(|def| (range, kind, def)) + res.map(|def| { + let n = def.name(sema.db)?; + if n == original_ident { + Some((range, kind, def, new_name.clone(), RenameDefinition::Yes)) + } else if let Some(suffix) = n.as_str().strip_prefix(original_ident.as_str()) { + Some((range, kind, def, Name::new_root(&format!("{}{suffix}", new_name.as_str())), RenameDefinition::No)) + } else { + n.as_str().strip_suffix(original_ident.as_str().trim_start_matches('\'')) + .map(|prefix| (range, kind, def, Name::new_root(&format!("{prefix}{}", new_name.as_str())), RenameDefinition::No)) + } + }) }); - let res: RenameResult<Vec<_>> = symbols.collect(); + let res: RenameResult<Vec<_>> = symbols.filter_map(Result::transpose).collect(); match res { Ok(v) => { - if v.is_empty() { - // FIXME: some semantic duplication between "empty vec" and "Err()" - Err(format_err!("No references found at position")) - } else { - // remove duplicates, comparing `Definition`s - Ok(v.into_iter() - .unique_by(|&(.., def)| def) - .map(|(a, b, c)| (a.into_file_id(sema.db), b, c)) - .collect::<Vec<_>>() - .into_iter()) - } + // remove duplicates, comparing `Definition`s + Ok(v.into_iter() + .unique_by(|&(.., def, _, _)| def) + .map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e)) + .collect::<Vec<_>>() + .into_iter()) } Err(e) => Err(e), } @@ -370,7 +399,13 @@ fn rename_to_self( source_change.extend(usages.iter().map(|(file_id, references)| { ( file_id.file_id(sema.db), - source_edit_from_references(references, def, "self", file_id.edition(sema.db)), + source_edit_from_references( + sema.db, + references, + def, + &Name::new_symbol_root(sym::self_), + file_id.edition(sema.db), + ), ) })); source_change.insert_source_edit( @@ -384,23 +419,25 @@ fn rename_self_to_param( sema: &Semantics<'_, RootDatabase>, local: hir::Local, self_param: hir::SelfParam, - new_name: &str, + new_name: &Name, + identifier_kind: IdentifierKind, ) -> RenameResult<SourceChange> { - if new_name == "self" { + if identifier_kind == IdentifierKind::LowercaseSelf { // Let's do nothing rather than complain. cov_mark::hit!(rename_self_to_self); return Ok(SourceChange::default()); } - let identifier_kind = IdentifierKind::classify(new_name)?; - let InFile { file_id, value: self_param } = sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?; let def = Definition::Local(local); let usages = def.usages(sema).all(); - let edit = text_edit_from_self_param(&self_param, new_name) - .ok_or_else(|| format_err!("No target type found"))?; + let edit = text_edit_from_self_param( + &self_param, + new_name.display(sema.db, file_id.edition(sema.db)).to_string(), + ) + .ok_or_else(|| format_err!("No target type found"))?; if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { bail!("Cannot rename reference to `_` as it is being referenced multiple times"); } @@ -409,13 +446,19 @@ fn rename_self_to_param( source_change.extend(usages.iter().map(|(file_id, references)| { ( file_id.file_id(sema.db), - source_edit_from_references(references, def, new_name, file_id.edition(sema.db)), + source_edit_from_references( + sema.db, + references, + def, + new_name, + file_id.edition(sema.db), + ), ) })); Ok(source_change) } -fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { +fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> { fn target_type_name(impl_def: &ast::Impl) -> Option<String> { if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { return Some(p.path()?.segment()?.name_ref()?.text().to_string()); @@ -427,7 +470,7 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt Some(impl_def) => { let type_name = target_type_name(&impl_def)?; - let mut replacement_text = String::from(new_name); + let mut replacement_text = new_name; replacement_text.push_str(": "); match (self_param.amp_token(), self_param.mut_token()) { (Some(_), None) => replacement_text.push('&'), @@ -440,7 +483,7 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt } None => { cov_mark::hit!(rename_self_outside_of_methods); - let mut replacement_text = String::from(new_name); + let mut replacement_text = new_name; replacement_text.push_str(": _"); Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) } @@ -710,7 +753,7 @@ impl Foo { check( "super", r#"fn main() { let i$0 = 1; }"#, - "error: Invalid name `super`: not an identifier", + "error: Invalid name `super`: cannot rename to a keyword", ); } @@ -759,7 +802,11 @@ impl Foo { #[test] fn test_rename_mod_invalid_raw_ident() { - check("r#self", r#"mod foo$0 {}"#, "error: Invalid name `self`: not an identifier"); + check( + "r#self", + r#"mod foo$0 {}"#, + "error: Invalid name `self`: cannot rename module to self", + ); } #[test] @@ -2359,7 +2406,6 @@ fn foo(foo: Foo) { #[test] fn test_rename_lifetimes() { - cov_mark::check!(rename_lifetime); check( "'yeeee", r#" @@ -2536,7 +2582,7 @@ fn baz() { x.0$0 = 5; } "#, - "error: No identifier available to rename", + "error: No references found at position", ); } @@ -2566,7 +2612,7 @@ impl Foo { } } "#, - "error: Cannot rename `Self`", + "error: No references found at position", ); } @@ -3262,4 +3308,100 @@ trait Trait<U> { "#, ); } + + #[test] + fn rename_macro_generated_type_from_type_with_a_suffix() { + check( + "Bar", + r#" +//- proc_macros: generate_suffixed_type +#[proc_macros::generate_suffixed_type] +struct Foo$0; +fn usage(_: FooSuffix) {} +usage(FooSuffix); +"#, + r#" +#[proc_macros::generate_suffixed_type] +struct Bar; +fn usage(_: BarSuffix) {} +usage(BarSuffix); +"#, + ); + } + + #[test] + // FIXME + #[should_panic] + fn rename_macro_generated_type_from_type_usage_with_a_suffix() { + check( + "Bar", + r#" +//- proc_macros: generate_suffixed_type +#[proc_macros::generate_suffixed_type] +struct Foo; +fn usage(_: FooSuffix) {} +usage(FooSuffix); +fn other_place() { Foo$0; } +"#, + r#" +#[proc_macros::generate_suffixed_type] +struct Bar; +fn usage(_: BarSuffix) {} +usage(BarSuffix); +fn other_place() { Bar; } +"#, + ); + } + + #[test] + fn rename_macro_generated_type_from_variant_with_a_suffix() { + check( + "Bar", + r#" +//- proc_macros: generate_suffixed_type +#[proc_macros::generate_suffixed_type] +enum Quux { + Foo$0, +} +fn usage(_: FooSuffix) {} +usage(FooSuffix); +"#, + r#" +#[proc_macros::generate_suffixed_type] +enum Quux { + Bar, +} +fn usage(_: BarSuffix) {} +usage(BarSuffix); +"#, + ); + } + + #[test] + // FIXME + #[should_panic] + fn rename_macro_generated_type_from_variant_usage_with_a_suffix() { + check( + "Bar", + r#" +//- proc_macros: generate_suffixed_type +#[proc_macros::generate_suffixed_type] +enum Quux { + Foo, +} +fn usage(_: FooSuffix) {} +usage(FooSuffix); +fn other_place() { Quux::Foo$0; } +"#, + r#" +#[proc_macros::generate_suffixed_type] +enum Quux { + Bar, +} +fn usage(_: BarSuffix) {} +usage(BartSuffix); +fn other_place() { Quux::Bar$0; } +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs index 8cc332d4633..8ed0fc6729f 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs @@ -562,8 +562,12 @@ fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker { let m = p.start(); + // test closure_binder + // fn main() { for<'a> || (); } if p.at(T![for]) { + let b = p.start(); types::for_binder(p); + b.complete(p, CLOSURE_BINDER); } // test const_closure // fn main() { let cl = const || _ = 0; } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs index 9d4fdbfaf2e..ea5a3bc8593 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs @@ -201,6 +201,17 @@ fn type_bound(p: &mut Parser<'_>) -> bool { } if paths::is_use_path_start(p) { types::path_type_bounds(p, false); + // test_err type_bounds_macro_call_recovery + // fn foo<T: T![], T: T!, T: T!{}>() -> Box<T! + T!{}> {} + if p.at(T![!]) { + let m = p.start(); + p.bump(T![!]); + p.error("unexpected `!` in type path, macro calls are not allowed here"); + if p.at_ts(TokenSet::new(&[T!['{'], T!['['], T!['(']])) { + items::token_tree(p); + } + m.complete(p, ERROR); + } } else { m.abandon(p); return false; diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs index 770827c6b0d..dfe7cb57d24 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs @@ -89,19 +89,22 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option<Completed // test qual_paths // type X = <A as B>::Output; // fn foo() { <usize as Default>::default(); } - if first && p.eat(T![<]) { + if first && p.at(T![<]) { + let m = p.start(); + p.bump(T![<]); // test_err angled_path_without_qual // type X = <()>; // type Y = <A as B>; types::type_(p); if p.eat(T![as]) { if is_use_path_start(p) { - types::path_type(p); + types::path_type_bounds(p, true); } else { p.error("expected a trait"); } } p.expect(T![>]); + m.complete(p, TYPE_ANCHOR); if !p.at(T![::]) { p.error("expected `::`"); } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs index 9d31e435cf9..908440b5d05 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs @@ -330,15 +330,6 @@ fn bare_dyn_trait_type(p: &mut Parser<'_>) { m.complete(p, DYN_TRAIT_TYPE); } -// test path_type -// type A = Foo; -// type B = ::Foo; -// type C = self::Foo; -// type D = super::Foo; -pub(super) fn path_type(p: &mut Parser<'_>) { - path_type_bounds(p, true); -} - // test macro_call_type // type A = foo!(); // type B = crate::foo!(); @@ -365,6 +356,11 @@ fn path_or_macro_type(p: &mut Parser<'_>, allow_bounds: bool) { } } +// test path_type +// type A = Foo; +// type B = ::Foo; +// type C = self::Foo; +// type D = super::Foo; pub(super) fn path_type_bounds(p: &mut Parser<'_>, allow_bounds: bool) { assert!(paths::is_path_start(p)); let m = p.start(); diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs index b1727509b13..f534546ea07 100644 --- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs +++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs @@ -291,6 +291,7 @@ pub enum SyntaxKind { TUPLE_STRUCT_PAT, TUPLE_TYPE, TYPE_ALIAS, + TYPE_ANCHOR, TYPE_ARG, TYPE_BOUND, TYPE_BOUND_LIST, @@ -463,6 +464,7 @@ impl SyntaxKind { | TUPLE_STRUCT_PAT | TUPLE_TYPE | TYPE_ALIAS + | TYPE_ANCHOR | TYPE_ARG | TYPE_BOUND | TYPE_BOUND_LIST diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs index 030d8e0f04d..6ec4192830b 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs +++ b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs @@ -83,6 +83,10 @@ mod ok { #[test] fn cast_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/cast_expr.rs"); } #[test] + fn closure_binder() { + run_and_expect_no_errors("test_data/parser/inline/ok/closure_binder.rs"); + } + #[test] fn closure_body_underscore_assignment() { run_and_expect_no_errors( "test_data/parser/inline/ok/closure_body_underscore_assignment.rs", @@ -872,6 +876,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/tuple_pat_leading_comma.rs"); } #[test] + fn type_bounds_macro_call_recovery() { + run_and_expect_errors("test_data/parser/inline/err/type_bounds_macro_call_recovery.rs"); + } + #[test] fn type_in_array_recover() { run_and_expect_errors("test_data/parser/inline/err/type_in_array_recover.rs"); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast index f0dbc9b1027..025c12e4c2a 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast @@ -186,13 +186,14 @@ SOURCE_FILE TUPLE_EXPR L_PAREN "(" CLOSURE_EXPR - FOR_KW "for" - GENERIC_PARAM_LIST - L_ANGLE "<" - LIFETIME_PARAM - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" + CLOSURE_BINDER + FOR_KW "for" + GENERIC_PARAM_LIST + L_ANGLE "<" + LIFETIME_PARAM + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" WHITESPACE " " BIN_EXPR BIN_EXPR diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/angled_path_without_qual.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/angled_path_without_qual.rast index 0529e9750e7..53fbe0b615e 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/angled_path_without_qual.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/angled_path_without_qual.rast @@ -10,11 +10,12 @@ SOURCE_FILE PATH_TYPE PATH PATH_SEGMENT - L_ANGLE "<" - TUPLE_TYPE - L_PAREN "(" - R_PAREN ")" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" TYPE_ALIAS @@ -28,21 +29,22 @@ SOURCE_FILE PATH_TYPE PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "A" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "B" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "B" + R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" error 13: expected `::` diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/type_bounds_macro_call_recovery.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/type_bounds_macro_call_recovery.rast new file mode 100644 index 00000000000..4722beb6192 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/type_bounds_macro_call_recovery.rast @@ -0,0 +1,112 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + GENERIC_PARAM_LIST + L_ANGLE "<" + TYPE_PARAM + NAME + IDENT "T" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + ERROR + BANG "!" + TOKEN_TREE + L_BRACK "[" + R_BRACK "]" + COMMA "," + WHITESPACE " " + TYPE_PARAM + NAME + IDENT "T" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + ERROR + BANG "!" + COMMA "," + WHITESPACE " " + TYPE_PARAM + NAME + IDENT "T" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + ERROR + BANG "!" + TOKEN_TREE + L_CURLY "{" + R_CURLY "}" + R_ANGLE ">" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + DYN_TRAIT_TYPE + TYPE_BOUND_LIST + TYPE_BOUND + MACRO_TYPE + MACRO_CALL + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + BANG "!" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + ERROR + BANG "!" + TOKEN_TREE + L_CURLY "{" + R_CURLY "}" + R_ANGLE ">" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" +error 12: unexpected `!` in type path, macro calls are not allowed here +error 21: unexpected `!` in type path, macro calls are not allowed here +error 28: unexpected `!` in type path, macro calls are not allowed here +error 43: expected `{`, `[`, `(` +error 48: unexpected `!` in type path, macro calls are not allowed here diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/type_bounds_macro_call_recovery.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/type_bounds_macro_call_recovery.rs new file mode 100644 index 00000000000..517404fdb0e --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/type_bounds_macro_call_recovery.rs @@ -0,0 +1 @@ +fn foo<T: T![], T: T!, T: T!{}>() -> Box<T! + T!{}> {} diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/call_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/call_expr.rast index 19cc8d5ac7c..7c1d894f7e4 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/call_expr.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/call_expr.rast @@ -88,13 +88,14 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF @@ -119,21 +120,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Trait" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Trait" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rast new file mode 100644 index 00000000000..c04dbe1ea0a --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rast @@ -0,0 +1,36 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "main" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + EXPR_STMT + CLOSURE_EXPR + CLOSURE_BINDER + FOR_KW "for" + GENERIC_PARAM_LIST + L_ANGLE "<" + LIFETIME_PARAM + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" + WHITESPACE " " + PARAM_LIST + PIPE "|" + PIPE "|" + WHITESPACE " " + TUPLE_EXPR + L_PAREN "(" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rs new file mode 100644 index 00000000000..a6d8aafb08a --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rs @@ -0,0 +1 @@ +fn main() { for<'a> || (); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast index c25ad8430d0..ea401d224e6 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast @@ -202,13 +202,14 @@ SOURCE_FILE WHITESPACE "\n " EXPR_STMT CLOSURE_EXPR - FOR_KW "for" - GENERIC_PARAM_LIST - L_ANGLE "<" - LIFETIME_PARAM - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" + CLOSURE_BINDER + FOR_KW "for" + GENERIC_PARAM_LIST + L_ANGLE "<" + LIFETIME_PARAM + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" WHITESPACE " " PARAM_LIST PIPE "|" @@ -222,13 +223,14 @@ SOURCE_FILE WHITESPACE "\n " EXPR_STMT CLOSURE_EXPR - FOR_KW "for" - GENERIC_PARAM_LIST - L_ANGLE "<" - LIFETIME_PARAM - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" + CLOSURE_BINDER + FOR_KW "for" + GENERIC_PARAM_LIST + L_ANGLE "<" + LIFETIME_PARAM + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" WHITESPACE " " MOVE_KW "move" WHITESPACE " " diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/qual_paths.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/qual_paths.rast index 8c66cfe599f..10f8a6a7516 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/qual_paths.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/qual_paths.rast @@ -11,21 +11,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "A" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "B" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "B" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF @@ -51,21 +52,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "usize" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Default" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "usize" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Default" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/type_path_in_pattern.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/type_path_in_pattern.rast index 297f7575ca6..3d27afa5ecd 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/type_path_in_pattern.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/type_path_in_pattern.rast @@ -19,10 +19,11 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - INFER_TYPE - UNDERSCORE "_" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + INFER_TYPE + UNDERSCORE "_" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_clause.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_clause.rast index a3cbe457e1a..9adfe2caa73 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_clause.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_clause.rast @@ -84,21 +84,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "T" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Iterator" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Iterator" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0036_fully_qualified.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0036_fully_qualified.rast index 9382020e2f6..2fecb1cc47d 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0036_fully_qualified.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0036_fully_qualified.rast @@ -45,21 +45,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Iterator" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Iterator" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast index a536b0e881f..d1d1ffacf0d 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast @@ -107,13 +107,14 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast index cd3b21ae94f..8bf1090f9cf 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast @@ -288,26 +288,27 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - REF_TYPE - AMP "&" - LIFETIME - LIFETIME_IDENT "'a" + TYPE_ANCHOR + L_ANGLE "<" + REF_TYPE + AMP "&" + LIFETIME + LIFETIME_IDENT "'a" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + WHITESPACE " " + AS_KW "as" WHITESPACE " " PATH_TYPE PATH PATH_SEGMENT NAME_REF - IDENT "T" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Baz" - R_ANGLE ">" + IDENT "Baz" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl.rs index 11dbd920091..ad285990334 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl.rs @@ -10,6 +10,7 @@ use std::fmt; +use intern::Symbol; use proc_macro::bridge; mod token_stream; @@ -112,3 +113,135 @@ fn literal_kind_to_internal(kind: bridge::LitKind) -> tt::LitKind { bridge::LitKind::ErrWithGuar => tt::LitKind::Err(()), } } + +pub(super) fn literal_from_str<Span: Copy>( + s: &str, + span: Span, +) -> Result<bridge::Literal<Span, Symbol>, ()> { + use proc_macro::bridge::LitKind; + use rustc_lexer::{LiteralKind, Token, TokenKind}; + + let mut tokens = rustc_lexer::tokenize(s); + let minus_or_lit = tokens.next().unwrap_or(Token { kind: TokenKind::Eof, len: 0 }); + + let lit = if minus_or_lit.kind == TokenKind::Minus { + let lit = tokens.next().ok_or(())?; + if !matches!( + lit.kind, + TokenKind::Literal { kind: LiteralKind::Int { .. } | LiteralKind::Float { .. }, .. } + ) { + return Err(()); + } + lit + } else { + minus_or_lit + }; + + if tokens.next().is_some() { + return Err(()); + } + + let TokenKind::Literal { kind, suffix_start } = lit.kind else { return Err(()) }; + let (kind, start_offset, end_offset) = match kind { + LiteralKind::Int { .. } => (LitKind::Integer, 0, 0), + LiteralKind::Float { .. } => (LitKind::Float, 0, 0), + LiteralKind::Char { terminated } => (LitKind::Char, 1, terminated as usize), + LiteralKind::Byte { terminated } => (LitKind::Byte, 2, terminated as usize), + LiteralKind::Str { terminated } => (LitKind::Str, 1, terminated as usize), + LiteralKind::ByteStr { terminated } => (LitKind::ByteStr, 2, terminated as usize), + LiteralKind::CStr { terminated } => (LitKind::CStr, 2, terminated as usize), + LiteralKind::RawStr { n_hashes } => ( + LitKind::StrRaw(n_hashes.unwrap_or_default()), + 2 + n_hashes.unwrap_or_default() as usize, + 1 + n_hashes.unwrap_or_default() as usize, + ), + LiteralKind::RawByteStr { n_hashes } => ( + LitKind::ByteStrRaw(n_hashes.unwrap_or_default()), + 3 + n_hashes.unwrap_or_default() as usize, + 1 + n_hashes.unwrap_or_default() as usize, + ), + LiteralKind::RawCStr { n_hashes } => ( + LitKind::CStrRaw(n_hashes.unwrap_or_default()), + 3 + n_hashes.unwrap_or_default() as usize, + 1 + n_hashes.unwrap_or_default() as usize, + ), + }; + + let (lit, suffix) = s.split_at(suffix_start as usize); + let lit = &lit[start_offset..lit.len() - end_offset]; + let suffix = match suffix { + "" | "_" => None, + suffix => Some(Symbol::intern(suffix)), + }; + + Ok(bridge::Literal { kind, symbol: Symbol::intern(lit), suffix, span }) +} + +pub(super) fn from_token_tree<Span: Copy>( + tree: bridge::TokenTree<TokenStream<Span>, Span, Symbol>, +) -> TokenStream<Span> { + match tree { + bridge::TokenTree::Group(group) => { + let group = TopSubtree::from_bridge(group); + TokenStream { token_trees: group.0 } + } + + bridge::TokenTree::Ident(ident) => { + let text = ident.sym; + let ident: tt::Ident<Span> = tt::Ident { + sym: text, + span: ident.span, + is_raw: if ident.is_raw { tt::IdentIsRaw::Yes } else { tt::IdentIsRaw::No }, + }; + let leaf = tt::Leaf::from(ident); + let tree = tt::TokenTree::from(leaf); + TokenStream { token_trees: vec![tree] } + } + + bridge::TokenTree::Literal(literal) => { + let token_trees = + if let Some((_minus, symbol)) = literal.symbol.as_str().split_once('-') { + let punct = tt::Punct { + spacing: tt::Spacing::Alone, + span: literal.span, + char: '-' as char, + }; + let leaf: tt::Leaf<Span> = tt::Leaf::from(punct); + let minus_tree = tt::TokenTree::from(leaf); + + let literal = tt::Literal { + symbol: Symbol::intern(symbol), + suffix: literal.suffix, + span: literal.span, + kind: literal_kind_to_internal(literal.kind), + }; + let leaf: tt::Leaf<Span> = tt::Leaf::from(literal); + let tree = tt::TokenTree::from(leaf); + vec![minus_tree, tree] + } else { + let literal = tt::Literal { + symbol: literal.symbol, + suffix: literal.suffix, + span: literal.span, + kind: literal_kind_to_internal(literal.kind), + }; + + let leaf: tt::Leaf<Span> = tt::Leaf::from(literal); + let tree = tt::TokenTree::from(leaf); + vec![tree] + }; + TokenStream { token_trees } + } + + bridge::TokenTree::Punct(p) => { + let punct = tt::Punct { + char: p.ch as char, + spacing: if p.joint { tt::Spacing::Joint } else { tt::Spacing::Alone }, + span: p.span, + }; + let leaf = tt::Leaf::from(punct); + let tree = tt::TokenTree::from(leaf); + TokenStream { token_trees: vec![tree] } + } + } +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index e0c6e68f803..5d1271ba81e 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -14,16 +14,7 @@ use proc_macro::bridge::{self, server}; use span::{FIXUP_ERASED_FILE_AST_ID_MARKER, Span}; use tt::{TextRange, TextSize}; -use crate::server_impl::{TopSubtree, literal_kind_to_internal, token_stream::TokenStreamBuilder}; -mod tt { - pub use tt::*; - - pub type TokenTree = ::tt::TokenTree<super::Span>; - pub type Leaf = ::tt::Leaf<super::Span>; - pub type Literal = ::tt::Literal<super::Span>; - pub type Punct = ::tt::Punct<super::Span>; - pub type Ident = ::tt::Ident<super::Span>; -} +use crate::server_impl::{from_token_tree, literal_from_str, token_stream::TokenStreamBuilder}; type TokenStream = crate::server_impl::TokenStream<Span>; @@ -62,66 +53,7 @@ impl server::FreeFunctions for RaSpanServer { &mut self, s: &str, ) -> Result<bridge::Literal<Self::Span, Self::Symbol>, ()> { - use proc_macro::bridge::LitKind; - use rustc_lexer::{LiteralKind, Token, TokenKind}; - - let mut tokens = rustc_lexer::tokenize(s); - let minus_or_lit = tokens.next().unwrap_or(Token { kind: TokenKind::Eof, len: 0 }); - - let lit = if minus_or_lit.kind == TokenKind::Minus { - let lit = tokens.next().ok_or(())?; - if !matches!( - lit.kind, - TokenKind::Literal { - kind: LiteralKind::Int { .. } | LiteralKind::Float { .. }, - .. - } - ) { - return Err(()); - } - lit - } else { - minus_or_lit - }; - - if tokens.next().is_some() { - return Err(()); - } - - let TokenKind::Literal { kind, suffix_start } = lit.kind else { return Err(()) }; - let (kind, start_offset, end_offset) = match kind { - LiteralKind::Int { .. } => (LitKind::Integer, 0, 0), - LiteralKind::Float { .. } => (LitKind::Float, 0, 0), - LiteralKind::Char { terminated } => (LitKind::Char, 1, terminated as usize), - LiteralKind::Byte { terminated } => (LitKind::Byte, 2, terminated as usize), - LiteralKind::Str { terminated } => (LitKind::Str, 1, terminated as usize), - LiteralKind::ByteStr { terminated } => (LitKind::ByteStr, 2, terminated as usize), - LiteralKind::CStr { terminated } => (LitKind::CStr, 2, terminated as usize), - LiteralKind::RawStr { n_hashes } => ( - LitKind::StrRaw(n_hashes.unwrap_or_default()), - 2 + n_hashes.unwrap_or_default() as usize, - 1 + n_hashes.unwrap_or_default() as usize, - ), - LiteralKind::RawByteStr { n_hashes } => ( - LitKind::ByteStrRaw(n_hashes.unwrap_or_default()), - 3 + n_hashes.unwrap_or_default() as usize, - 1 + n_hashes.unwrap_or_default() as usize, - ), - LiteralKind::RawCStr { n_hashes } => ( - LitKind::CStrRaw(n_hashes.unwrap_or_default()), - 3 + n_hashes.unwrap_or_default() as usize, - 1 + n_hashes.unwrap_or_default() as usize, - ), - }; - - let (lit, suffix) = s.split_at(suffix_start as usize); - let lit = &lit[start_offset..lit.len() - end_offset]; - let suffix = match suffix { - "" | "_" => None, - suffix => Some(Symbol::intern(suffix)), - }; - - Ok(bridge::Literal { kind, symbol: Symbol::intern(lit), suffix, span: self.call_site }) + literal_from_str(s, self.call_site) } fn emit_diagnostic(&mut self, _: bridge::Diagnostic<Self::Span>) { @@ -149,70 +81,7 @@ impl server::TokenStream for RaSpanServer { &mut self, tree: bridge::TokenTree<Self::TokenStream, Self::Span, Self::Symbol>, ) -> Self::TokenStream { - match tree { - bridge::TokenTree::Group(group) => { - let group = TopSubtree::from_bridge(group); - TokenStream { token_trees: group.0 } - } - - bridge::TokenTree::Ident(ident) => { - let text = ident.sym; - let ident: tt::Ident = tt::Ident { - sym: text, - span: ident.span, - is_raw: if ident.is_raw { tt::IdentIsRaw::Yes } else { tt::IdentIsRaw::No }, - }; - let leaf = tt::Leaf::from(ident); - let tree = tt::TokenTree::from(leaf); - TokenStream { token_trees: vec![tree] } - } - - bridge::TokenTree::Literal(literal) => { - let token_trees = - if let Some((_minus, symbol)) = literal.symbol.as_str().split_once('-') { - let punct = tt::Punct { - spacing: tt::Spacing::Alone, - span: literal.span, - char: '-' as char, - }; - let leaf: tt::Leaf = tt::Leaf::from(punct); - let minus_tree = tt::TokenTree::from(leaf); - - let literal = tt::Literal { - symbol: Symbol::intern(symbol), - suffix: literal.suffix, - span: literal.span, - kind: literal_kind_to_internal(literal.kind), - }; - let leaf: tt::Leaf = tt::Leaf::from(literal); - let tree = tt::TokenTree::from(leaf); - vec![minus_tree, tree] - } else { - let literal = tt::Literal { - symbol: literal.symbol, - suffix: literal.suffix, - span: literal.span, - kind: literal_kind_to_internal(literal.kind), - }; - - let leaf: tt::Leaf = tt::Leaf::from(literal); - let tree = tt::TokenTree::from(leaf); - vec![tree] - }; - TokenStream { token_trees } - } - - bridge::TokenTree::Punct(p) => { - let punct = tt::Punct { - char: p.ch as char, - spacing: if p.joint { tt::Spacing::Joint } else { tt::Spacing::Alone }, - span: p.span, - }; - let leaf = tt::Leaf::from(punct); - let tree = tt::TokenTree::from(leaf); - TokenStream { token_trees: vec![tree] } - } - } + from_token_tree(tree) } fn expand_expr(&mut self, self_: &Self::TokenStream) -> Result<Self::TokenStream, ()> { diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs index d55b269f868..b493b325e83 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs @@ -5,23 +5,9 @@ use std::ops::{Bound, Range}; use intern::Symbol; use proc_macro::bridge::{self, server}; -use crate::server_impl::{TopSubtree, literal_kind_to_internal, token_stream::TokenStreamBuilder}; -mod tt { - pub use span::TokenId; +use crate::server_impl::{from_token_tree, literal_from_str, token_stream::TokenStreamBuilder}; - pub use tt::*; - - pub type TokenTree = ::tt::TokenTree<TokenId>; - pub type Leaf = ::tt::Leaf<TokenId>; - pub type Literal = ::tt::Literal<TokenId>; - pub type Punct = ::tt::Punct<TokenId>; - pub type Ident = ::tt::Ident<TokenId>; -} -type TokenTree = tt::TokenTree; -type Punct = tt::Punct; -type Spacing = tt::Spacing; -type Literal = tt::Literal; -type Span = tt::TokenId; +type Span = span::TokenId; type TokenStream = crate::server_impl::TokenStream<Span>; pub struct FreeFunctions; @@ -49,67 +35,7 @@ impl server::FreeFunctions for TokenIdServer { &mut self, s: &str, ) -> Result<bridge::Literal<Self::Span, Self::Symbol>, ()> { - use proc_macro::bridge::LitKind; - use rustc_lexer::{LiteralKind, Token, TokenKind}; - - let mut tokens = rustc_lexer::tokenize(s); - let minus_or_lit = tokens.next().unwrap_or(Token { kind: TokenKind::Eof, len: 0 }); - - let lit = if minus_or_lit.kind == TokenKind::Minus { - let lit = tokens.next().ok_or(())?; - if !matches!( - lit.kind, - TokenKind::Literal { - kind: LiteralKind::Int { .. } | LiteralKind::Float { .. }, - .. - } - ) { - return Err(()); - } - lit - } else { - minus_or_lit - }; - - if tokens.next().is_some() { - return Err(()); - } - - let TokenKind::Literal { kind, suffix_start } = lit.kind else { return Err(()) }; - - let (kind, start_offset, end_offset) = match kind { - LiteralKind::Int { .. } => (LitKind::Integer, 0, 0), - LiteralKind::Float { .. } => (LitKind::Float, 0, 0), - LiteralKind::Char { terminated } => (LitKind::Char, 1, terminated as usize), - LiteralKind::Byte { terminated } => (LitKind::Byte, 2, terminated as usize), - LiteralKind::Str { terminated } => (LitKind::Str, 1, terminated as usize), - LiteralKind::ByteStr { terminated } => (LitKind::ByteStr, 2, terminated as usize), - LiteralKind::CStr { terminated } => (LitKind::CStr, 2, terminated as usize), - LiteralKind::RawStr { n_hashes } => ( - LitKind::StrRaw(n_hashes.unwrap_or_default()), - 2 + n_hashes.unwrap_or_default() as usize, - 1 + n_hashes.unwrap_or_default() as usize, - ), - LiteralKind::RawByteStr { n_hashes } => ( - LitKind::ByteStrRaw(n_hashes.unwrap_or_default()), - 3 + n_hashes.unwrap_or_default() as usize, - 1 + n_hashes.unwrap_or_default() as usize, - ), - LiteralKind::RawCStr { n_hashes } => ( - LitKind::CStrRaw(n_hashes.unwrap_or_default()), - 3 + n_hashes.unwrap_or_default() as usize, - 1 + n_hashes.unwrap_or_default() as usize, - ), - }; - - let (lit, suffix) = s.split_at(suffix_start as usize); - let lit = &lit[start_offset..lit.len() - end_offset]; - let suffix = match suffix { - "" | "_" => None, - suffix => Some(Symbol::intern(suffix)), - }; - - Ok(bridge::Literal { kind, symbol: Symbol::intern(lit), suffix, span: self.call_site }) + literal_from_str(s, self.call_site) } fn emit_diagnostic(&mut self, _: bridge::Diagnostic<Self::Span>) {} @@ -135,69 +61,7 @@ impl server::TokenStream for TokenIdServer { &mut self, tree: bridge::TokenTree<Self::TokenStream, Self::Span, Self::Symbol>, ) -> Self::TokenStream { - match tree { - bridge::TokenTree::Group(group) => { - let group = TopSubtree::from_bridge(group); - TokenStream { token_trees: group.0 } - } - - bridge::TokenTree::Ident(ident) => { - let ident: tt::Ident = tt::Ident { - sym: ident.sym, - span: ident.span, - is_raw: if ident.is_raw { tt::IdentIsRaw::Yes } else { tt::IdentIsRaw::No }, - }; - let leaf = tt::Leaf::from(ident); - let tree = TokenTree::from(leaf); - TokenStream { token_trees: vec![tree] } - } - - bridge::TokenTree::Literal(literal) => { - let token_trees = - if let Some((_minus, symbol)) = literal.symbol.as_str().split_once('-') { - let punct = tt::Punct { - spacing: tt::Spacing::Alone, - span: literal.span, - char: '-' as char, - }; - let leaf: tt::Leaf = tt::Leaf::from(punct); - let minus_tree = tt::TokenTree::from(leaf); - - let literal = Literal { - symbol: Symbol::intern(symbol), - suffix: literal.suffix, - span: literal.span, - kind: literal_kind_to_internal(literal.kind), - }; - let leaf: tt::Leaf = tt::Leaf::from(literal); - let tree = tt::TokenTree::from(leaf); - vec![minus_tree, tree] - } else { - let literal = Literal { - symbol: literal.symbol, - suffix: literal.suffix, - span: literal.span, - kind: literal_kind_to_internal(literal.kind), - }; - - let leaf: tt::Leaf = tt::Leaf::from(literal); - let tree = tt::TokenTree::from(leaf); - vec![tree] - }; - TokenStream { token_trees } - } - - bridge::TokenTree::Punct(p) => { - let punct = Punct { - char: p.ch as char, - spacing: if p.joint { Spacing::Joint } else { Spacing::Alone }, - span: p.span, - }; - let leaf = tt::Leaf::from(punct); - let tree = TokenTree::from(leaf); - TokenStream { token_trees: vec![tree] } - } - } + from_token_tree(tree) } fn expand_expr(&mut self, self_: &Self::TokenStream) -> Result<Self::TokenStream, ()> { @@ -337,6 +201,8 @@ impl server::Server for TokenIdServer { #[cfg(test)] mod tests { + use span::TokenId; + use super::*; #[test] @@ -345,18 +211,18 @@ mod tests { token_trees: vec![ tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { sym: Symbol::intern("struct"), - span: tt::TokenId(0), + span: TokenId(0), is_raw: tt::IdentIsRaw::No, })), tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { sym: Symbol::intern("T"), - span: tt::TokenId(0), + span: TokenId(0), is_raw: tt::IdentIsRaw::No, })), tt::TokenTree::Subtree(tt::Subtree { delimiter: tt::Delimiter { - open: tt::TokenId(0), - close: tt::TokenId(0), + open: TokenId(0), + close: TokenId(0), kind: tt::DelimiterKind::Brace, }, len: 0, @@ -372,8 +238,8 @@ mod tests { let subtree_paren_a = vec![ tt::TokenTree::Subtree(tt::Subtree { delimiter: tt::Delimiter { - open: tt::TokenId(0), - close: tt::TokenId(0), + open: TokenId(0), + close: TokenId(0), kind: tt::DelimiterKind::Parenthesis, }, len: 1, @@ -381,24 +247,24 @@ mod tests { tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { is_raw: tt::IdentIsRaw::No, sym: Symbol::intern("a"), - span: tt::TokenId(0), + span: TokenId(0), })), ]; - let t1 = TokenStream::from_str("(a)", tt::TokenId(0)).unwrap(); + let t1 = TokenStream::from_str("(a)", TokenId(0)).unwrap(); assert_eq!(t1.token_trees.len(), 2); assert!(t1.token_trees[0..2] == subtree_paren_a); - let t2 = TokenStream::from_str("(a);", tt::TokenId(0)).unwrap(); + let t2 = TokenStream::from_str("(a);", TokenId(0)).unwrap(); assert_eq!(t2.token_trees.len(), 3); assert!(t2.token_trees[0..2] == subtree_paren_a); - let underscore = TokenStream::from_str("_", tt::TokenId(0)).unwrap(); + let underscore = TokenStream::from_str("_", TokenId(0)).unwrap(); assert!( underscore.token_trees[0] == tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { sym: Symbol::intern("_"), - span: tt::TokenId(0), + span: TokenId(0), is_raw: tt::IdentIsRaw::No, })) ); 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 6e730b1aea2..bb02284a513 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 @@ -431,12 +431,6 @@ impl CargoWorkspace { .ok_or(cargo_metadata::Error::NoJson)?; Ok((cargo_metadata::MetadataCommand::parse(stdout)?, None)) })() - .map(|(metadata, error)| { - ( - metadata, - error.map(|e| e.context(format!("Failed to run `{:?}`", meta.cargo_command()))), - ) - }) .with_context(|| format!("Failed to run `{:?}`", meta.cargo_command())) } 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 d1ca8c1a91a..5cbea9c2b3d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -1192,7 +1192,7 @@ impl ConfigChange { #[derive(Debug, Clone, Eq, PartialEq)] pub enum LinkedProject { ProjectManifest(ProjectManifest), - InlineJsonProject(ProjectJson), + InlineProjectJson(ProjectJson), } impl From<ProjectManifest> for LinkedProject { @@ -1203,7 +1203,7 @@ impl From<ProjectManifest> for LinkedProject { impl From<ProjectJson> for LinkedProject { fn from(v: ProjectJson) -> Self { - LinkedProject::InlineJsonProject(v) + LinkedProject::InlineProjectJson(v) } } @@ -1597,6 +1597,16 @@ impl Config { term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(), } } + + pub fn diagnostic_fixes(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig { + // We always want to show quickfixes for diagnostics, even when diagnostics/experimental diagnostics are disabled. + DiagnosticsConfig { + enabled: true, + disable_experimental: false, + ..self.diagnostics(source_root) + } + } + pub fn expand_proc_attr_macros(&self) -> bool { self.procMacro_enable().to_owned() && self.procMacro_attributes_enable().to_owned() } 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 69983a67626..6d46ce68ed4 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 @@ -1439,7 +1439,7 @@ pub(crate) fn handle_code_action( }; let assists = snap.analysis.assists_with_fixes( &assists_config, - &snap.config.diagnostics(Some(source_root)), + &snap.config.diagnostic_fixes(Some(source_root)), resolve, frange, )?; @@ -1530,7 +1530,7 @@ pub(crate) fn handle_code_action_resolve( let assists = snap.analysis.assists_with_fixes( &assists_config, - &snap.config.diagnostics(Some(source_root)), + &snap.config.diagnostic_fixes(Some(source_root)), AssistResolveStrategy::Single(assist_resolve), frange, )?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/utils.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/utils.rs index 673eaa5952f..5bea7084fdb 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/utils.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/utils.rs @@ -108,8 +108,7 @@ impl GlobalState { /// edge users from being upset! pub(crate) fn poke_rust_analyzer_developer(&mut self, message: String) { let from_source_build = option_env!("POKE_RA_DEVS").is_some(); - let profiling_enabled = std::env::var("RA_PROFILE").is_ok(); - if from_source_build || profiling_enabled { + if from_source_build { self.show_and_log_error(message, None); } } 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 ae9e3e99874..4677880daaf 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -292,7 +292,7 @@ impl GlobalState { if let (Some(_command), Some(path)) = (&discover_command, &path) { let build = linked_projects.iter().find_map(|project| match project { - LinkedProject::InlineJsonProject(it) => it.crate_by_buildfile(path), + LinkedProject::InlineProjectJson(it) => it.crate_by_buildfile(path), _ => None, }); @@ -318,7 +318,7 @@ impl GlobalState { &progress, ) } - LinkedProject::InlineJsonProject(it) => { + LinkedProject::InlineProjectJson(it) => { let workspace = project_model::ProjectWorkspace::load_inline( it.clone(), &cargo_config, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index f6bcb5642c3..59073af983b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -975,10 +975,6 @@ version = \"0.0.0\" } fn out_dirs_check_impl(root_contains_symlink: bool) { - if skip_slow_tests() { - return; - } - let mut server = Project::with_fixture( r###" //- /Cargo.toml @@ -1130,12 +1126,18 @@ fn main() { #[test] fn out_dirs_check() { + if skip_slow_tests() { + return; + } out_dirs_check_impl(false); } #[test] #[cfg(not(windows))] // windows requires elevated permissions to create symlinks fn root_contains_symlink_out_dirs_check() { + if skip_slow_tests() { + return; + } out_dirs_check_impl(true); } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/ratoml.rs index 485f32281dd..cac7efd84aa 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/ratoml.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/ratoml.rs @@ -439,6 +439,7 @@ assist.emitMustUse = true"#, } #[test] +#[ignore = "flaky test that tends to hang"] fn ratoml_inherit_config_from_ws_root() { if skip_slow_tests() { return; diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram index 10abca7d35d..c81da06682e 100644 --- a/src/tools/rust-analyzer/crates/syntax/rust.ungram +++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram @@ -39,7 +39,10 @@ PathSegment = | NameRef GenericArgList? | NameRef ParenthesizedArgList RetType? | NameRef ReturnTypeSyntax -| '<' Type ('as' PathType)? '>' +| TypeAnchor + +TypeAnchor = + '<' Type ('as' PathType)? '>' ReturnTypeSyntax = '(' '..' ')' @@ -98,7 +101,7 @@ WhereClause = 'where' predicates:(WherePred (',' WherePred)* ','?) WherePred = - ('for' GenericParamList)? (Lifetime | Type) ':' TypeBoundList? + ('for' GenericParamList)? (Lifetime | Type) ':' TypeBoundList? //*************************// diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs index cd9f4dba890..04c7e8a578c 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs @@ -1232,21 +1232,13 @@ impl PathSegment { support::child(&self.syntax) } #[inline] - pub fn path_type(&self) -> Option<PathType> { support::child(&self.syntax) } - #[inline] pub fn ret_type(&self) -> Option<RetType> { support::child(&self.syntax) } #[inline] pub fn return_type_syntax(&self) -> Option<ReturnTypeSyntax> { support::child(&self.syntax) } #[inline] - pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) } + pub fn type_anchor(&self) -> Option<TypeAnchor> { support::child(&self.syntax) } #[inline] pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) } - #[inline] - pub fn l_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<]) } - #[inline] - pub fn r_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![>]) } - #[inline] - pub fn as_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![as]) } } pub struct PathType { pub(crate) syntax: SyntaxNode, @@ -1739,6 +1731,21 @@ impl TypeAlias { #[inline] pub fn type_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![type]) } } +pub struct TypeAnchor { + pub(crate) syntax: SyntaxNode, +} +impl TypeAnchor { + #[inline] + pub fn path_type(&self) -> Option<PathType> { support::child(&self.syntax) } + #[inline] + pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) } + #[inline] + pub fn l_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<]) } + #[inline] + pub fn r_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![>]) } + #[inline] + pub fn as_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![as]) } +} pub struct TypeArg { pub(crate) syntax: SyntaxNode, } @@ -7108,6 +7115,42 @@ impl fmt::Debug for TypeAlias { f.debug_struct("TypeAlias").field("syntax", &self.syntax).finish() } } +impl AstNode for TypeAnchor { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + TYPE_ANCHOR + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == TYPE_ANCHOR } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for TypeAnchor { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for TypeAnchor {} +impl PartialEq for TypeAnchor { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for TypeAnchor { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for TypeAnchor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TypeAnchor").field("syntax", &self.syntax).finish() + } +} impl AstNode for TypeArg { #[inline] fn kind() -> SyntaxKind @@ -10624,6 +10667,11 @@ impl std::fmt::Display for TypeAlias { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for TypeAnchor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for TypeArg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index b9ccd34cff0..dcf853427e5 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -276,18 +276,15 @@ impl ast::PathSegment { _ => PathSegmentKind::Name(name_ref), } } else { - match self.syntax().first_child_or_token()?.kind() { - T![<] => { - // <T> or <T as Trait> - // T is any TypeRef, Trait has to be a PathType - let mut type_refs = - self.syntax().children().filter(|node| ast::Type::can_cast(node.kind())); - let type_ref = type_refs.next().and_then(ast::Type::cast); - let trait_ref = type_refs.next().and_then(ast::PathType::cast); - PathSegmentKind::Type { type_ref, trait_ref } - } - _ => return None, - } + let anchor = self.type_anchor()?; + // FIXME: Move this over to `ast::TypeAnchor` + // <T> or <T as Trait> + // T is any TypeRef, Trait has to be a PathType + let mut type_refs = + anchor.syntax().children().filter(|node| ast::Type::can_cast(node.kind())); + let type_ref = type_refs.next().and_then(ast::Type::cast); + let trait_ref = type_refs.next().and_then(ast::PathType::cast); + PathSegmentKind::Type { type_ref, trait_ref } }; Some(res) } @@ -473,7 +470,7 @@ impl ast::Impl { // [#15778](https://github.com/rust-lang/rust-analyzer/issues/15778) impl ast::PathSegment { pub fn qualifying_trait(&self) -> Option<ast::PathType> { - let mut path_types = support::children(self.syntax()); + let mut path_types = support::children(self.type_anchor()?.syntax()); let first = path_types.next()?; path_types.next().or(Some(first)) } diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index 96e1301f227..8eb48f8d93e 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -538,6 +538,21 @@ pub fn disallow_cfg(_attr: TokenStream, input: TokenStream) -> TokenStream { disabled: false, }, ), + ( + r#" +#[proc_macro_attribute] +pub fn generate_suffixed_type(_attr: TokenStream, input: TokenStream) -> TokenStream { + input +} +"# + .into(), + ProcMacro { + name: Symbol::intern("generate_suffixed_type"), + kind: ProcMacroKind::Attr, + expander: sync::Arc::new(GenerateSuffixedTypeProcMacroExpander), + disabled: false, + }, + ), ]) } @@ -919,3 +934,57 @@ impl ProcMacroExpander for DisallowCfgProcMacroExpander { Ok(subtree.clone()) } } + +// Generates a new type by adding a suffix to the original name +#[derive(Debug)] +struct GenerateSuffixedTypeProcMacroExpander; +impl ProcMacroExpander for GenerateSuffixedTypeProcMacroExpander { + fn expand( + &self, + subtree: &TopSubtree, + _attrs: Option<&TopSubtree>, + _env: &Env, + _def_site: Span, + call_site: Span, + _mixed_site: Span, + _current_dir: String, + ) -> Result<TopSubtree, ProcMacroExpansionError> { + let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[1] else { + return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); + }; + + let ident = match ident.sym.as_str() { + "struct" => { + let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[2] else { + return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); + }; + ident + } + + "enum" => { + let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[4] else { + return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); + }; + ident + } + + _ => { + return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); + } + }; + + let generated_ident = tt::Ident { + sym: Symbol::intern(&format!("{}Suffix", ident.sym)), + span: ident.span, + is_raw: tt::IdentIsRaw::No, + }; + + let ret = quote! { call_site => + #subtree + + struct #generated_ident; + }; + + Ok(ret) + } +} diff --git a/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md b/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md index 151f8758a17..bbdb48bbbc9 100644 --- a/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md +++ b/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md @@ -5,7 +5,7 @@ build system, you’ll have to describe the structure of your project for rust-analyzer in the `rust-project.json` format: ```typescript -interface JsonProject { +interface ProjectJson { /// Path to the sysroot directory. /// /// The sysroot is where rustc looks for the diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index 18fb097aad7..ab431140c10 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -5500,9 +5500,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", - "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "dev": true, "license": "MIT", "optional": true, diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 5b47d1bbaed..af0dd5c9acd 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -a8e4c68dcb4dc1e48a0db294c5323cab0227fcb9 +7c10378e1fee5ddc6573b916aeb884ab10e0de17 |
