diff options
| author | bors <bors@rust-lang.org> | 2023-04-10 12:48:02 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2023-04-10 12:48:02 +0000 |
| commit | 2c455530e1d78036e83adbc930a799caadc90022 (patch) | |
| tree | 7bb888ad6e275594af2ca66ad278c3597f8c736e /src | |
| parent | 949a38509e0f2444361e28e322fda2f74c297c80 (diff) | |
| parent | d50fee9fb2e66b4162fbf5496e5abb7d3f29129a (diff) | |
| download | rust-2c455530e1d78036e83adbc930a799caadc90022.tar.gz rust-2c455530e1d78036e83adbc930a799caadc90022.zip | |
Auto merge of #2835 - saethlin:rustup, r=oli-obk
rustup `@oli-obk` I think this is the resolution we agreed to on Zulip?
Diffstat (limited to 'src')
74 files changed, 1754 insertions, 1317 deletions
diff --git a/src/bootstrap/CHANGELOG.md b/src/bootstrap/CHANGELOG.md index 654e03d0c3c..74dd22df9e0 100644 --- a/src/bootstrap/CHANGELOG.md +++ b/src/bootstrap/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `remote-test-server`'s `remote` argument has been removed in favor of the `--bind` flag. Use `--bind 0.0.0.0:12345` to replicate the behavior of the `remote` argument. - `x.py fmt` now formats only files modified between the merge-base of HEAD and the last commit in the master branch of the rust-lang repository and the current working directory. To restore old behaviour, use `x.py fmt .`. The check mode is not affected by this change. [#105702](https://github.com/rust-lang/rust/pull/105702) - The `llvm.version-check` config option has been removed. Older versions were never supported. If you still need to support older versions (e.g. you are applying custom patches), patch `check_llvm_version` in bootstrap to change the minimum version. [#108619](https://github.com/rust-lang/rust/pull/108619) +- The `rust.ignore-git` option has been renamed to `rust.omit-git-hash`. [#110059](https://github.com/rust-lang/rust/pull/110059) ### Non-breaking changes diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index 965dfa5f398..a158d1f718e 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -45,6 +45,7 @@ dependencies = [ "hex", "ignore", "is-terminal", + "junction", "libc", "object", "once_cell", @@ -350,6 +351,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] +name = "junction" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca39ef0d69b18e6a2fd14c2f0a1d593200f4a4ed949b240b5917ab51fac754cb" +dependencies = [ + "scopeguard", + "winapi", +] + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index 2fbe7aa57aa..eeda6d7c121 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -61,6 +61,9 @@ sysinfo = { version = "0.26.0", optional = true } [target.'cfg(not(target_os = "solaris"))'.dependencies] fd-lock = "3.0.8" +[target.'cfg(windows)'.dependencies.junction] +version = "1.0.0" + [target.'cfg(windows)'.dependencies.windows] version = "0.46.0" features = [ diff --git a/src/bootstrap/bolt.rs b/src/bootstrap/bolt.rs index 973dc4f602b..10e6d2e7d6d 100644 --- a/src/bootstrap/bolt.rs +++ b/src/bootstrap/bolt.rs @@ -45,8 +45,6 @@ pub fn optimize_with_bolt(path: &Path, profile_path: &Path, output_path: &Path) .arg("-split-all-cold") // Move jump tables to a separate section .arg("-jump-tables=move") - // Use GNU_STACK program header for new segment (workaround for issues with strip/objcopy) - .arg("-use-gnu-stack") // Fold functions with identical code .arg("-icf=1") // Update DWARF debug info in the final binary diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index 4d528a767e4..ade8fa4c74d 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -4,8 +4,9 @@ use std::collections::BTreeSet; use std::env; use std::ffi::OsStr; use std::fmt::{Debug, Write}; -use std::fs::{self}; +use std::fs::{self, File}; use std::hash::Hash; +use std::io::{BufRead, BufReader}; use std::ops::Deref; use std::path::{Component, Path, PathBuf}; use std::process::Command; @@ -28,8 +29,11 @@ use crate::{clean, dist}; use crate::{Build, CLang, DocTests, GitRepo, Mode}; pub use crate::Compiler; -// FIXME: replace with std::lazy after it gets stabilized and reaches beta -use once_cell::sync::Lazy; +// FIXME: +// - use std::lazy for `Lazy` +// - use std::cell for `OnceCell` +// Once they get stabilized and reach beta. +use once_cell::sync::{Lazy, OnceCell}; pub struct Builder<'a> { pub build: &'a Build, @@ -484,17 +488,43 @@ impl<'a> ShouldRun<'a> { // multiple aliases for the same job pub fn paths(mut self, paths: &[&str]) -> Self { + static SUBMODULES_PATHS: OnceCell<Vec<String>> = OnceCell::new(); + + let init_submodules_paths = |src: &PathBuf| { + let file = File::open(src.join(".gitmodules")).unwrap(); + + let mut submodules_paths = vec![]; + for line in BufReader::new(file).lines() { + if let Ok(line) = line { + let line = line.trim(); + + if line.starts_with("path") { + let actual_path = + line.split(' ').last().expect("Couldn't get value of path"); + submodules_paths.push(actual_path.to_owned()); + } + } + } + + submodules_paths + }; + + let submodules_paths = + SUBMODULES_PATHS.get_or_init(|| init_submodules_paths(&self.builder.src)); + self.paths.insert(PathSet::Set( paths .iter() .map(|p| { - // FIXME(#96188): make sure this is actually a path. - // This currently breaks for paths within submodules. - //assert!( - // self.builder.src.join(p).exists(), - // "`should_run.paths` should correspond to real on-disk paths - use `alias` if there is no relevant path: {}", - // p - //); + // assert only if `p` isn't submodule + if !submodules_paths.iter().find(|sm_p| p.contains(*sm_p)).is_some() { + assert!( + self.builder.src.join(p).exists(), + "`should_run.paths` should correspond to real on-disk paths - use `alias` if there is no relevant path: {}", + p + ); + } + TaskPath { path: p.into(), kind: Some(self.kind) } }) .collect(), diff --git a/src/bootstrap/channel.rs b/src/bootstrap/channel.rs index eae81b9fc69..390047f6fdc 100644 --- a/src/bootstrap/channel.rs +++ b/src/bootstrap/channel.rs @@ -19,7 +19,7 @@ pub enum GitInfo { #[default] Absent, /// This is a git repository. - /// If the info should be used (`ignore_git` is false), this will be + /// If the info should be used (`omit_git_hash` is false), this will be /// `Some`, otherwise it will be `None`. Present(Option<Info>), /// This is not a git repostory, but the info can be fetched from the @@ -35,7 +35,7 @@ pub struct Info { } impl GitInfo { - pub fn new(ignore_git: bool, dir: &Path) -> GitInfo { + pub fn new(omit_git_hash: bool, dir: &Path) -> GitInfo { // See if this even begins to look like a git dir if !dir.join(".git").exists() { match read_commit_info_file(dir) { @@ -52,7 +52,7 @@ impl GitInfo { // If we're ignoring the git info, we don't actually need to collect it, just make sure this // was a git repo in the first place. - if ignore_git { + if omit_git_hash { return GitInfo::Present(None); } diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs index cd19667139a..f9387a0fc80 100644 --- a/src/bootstrap/check.rs +++ b/src/bootstrap/check.rs @@ -271,9 +271,17 @@ impl Step for Rustc { false, ); - let libdir = builder.sysroot_libdir(compiler, target); - let hostdir = builder.sysroot_libdir(compiler, compiler.host); - add_to_sysroot(&builder, &libdir, &hostdir, &librustc_stamp(builder, compiler, target)); + // HACK: This avoids putting the newly built artifacts in the sysroot if we're using + // `download-rustc`, to avoid "multiple candidates for `rmeta`" errors. Technically, that's + // not quite right: people can set `download-rustc = true` to download even if there are + // changes to the compiler, and in that case ideally we would put the *new* artifacts in the + // sysroot, in case there are API changes that should be used by tools. In practice, + // though, that should be very uncommon, and people can still disable download-rustc. + if !builder.download_rustc() { + let libdir = builder.sysroot_libdir(compiler, target); + let hostdir = builder.sysroot_libdir(compiler, compiler.host); + add_to_sysroot(&builder, &libdir, &hostdir, &librustc_stamp(builder, compiler, target)); + } } } diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index b8b6b7b2d4e..dd65dc91c0c 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -77,7 +77,7 @@ pub struct Config { pub tools: Option<HashSet<String>>, pub sanitizers: bool, pub profiler: bool, - pub ignore_git: bool, + pub omit_git_hash: bool, pub exclude: Vec<TaskPath>, pub include_default_paths: bool, pub rustc_error_format: Option<String>, @@ -227,27 +227,36 @@ pub struct Config { pub reuse: Option<PathBuf>, pub cargo_native_static: bool, pub configure_args: Vec<String>, + pub out: PathBuf, + pub rust_info: channel::GitInfo, // These are either the stage0 downloaded binaries or the locally installed ones. pub initial_cargo: PathBuf, pub initial_rustc: PathBuf, + #[cfg(not(test))] initial_rustfmt: RefCell<RustfmtState>, #[cfg(test)] pub initial_rustfmt: RefCell<RustfmtState>, - pub out: PathBuf, - pub rust_info: channel::GitInfo, } #[derive(Default, Deserialize)] #[cfg_attr(test, derive(Clone))] pub struct Stage0Metadata { + pub compiler: CompilerMetadata, pub config: Stage0Config, pub checksums_sha256: HashMap<String, String>, pub rustfmt: Option<RustfmtMetadata>, } #[derive(Default, Deserialize)] #[cfg_attr(test, derive(Clone))] +pub struct CompilerMetadata { + pub date: String, + pub version: String, +} + +#[derive(Default, Deserialize)] +#[cfg_attr(test, derive(Clone))] pub struct Stage0Config { pub dist_server: String, pub artifacts_server: String, @@ -755,7 +764,7 @@ define_config! { verbose_tests: Option<bool> = "verbose-tests", optimize_tests: Option<bool> = "optimize-tests", codegen_tests: Option<bool> = "codegen-tests", - ignore_git: Option<bool> = "ignore-git", + omit_git_hash: Option<bool> = "omit-git-hash", dist_src: Option<bool> = "dist-src", save_toolstates: Option<String> = "save-toolstates", codegen_backends: Option<Vec<String>> = "codegen-backends", @@ -1000,10 +1009,10 @@ impl Config { config.out = crate::util::absolute(&config.out); } - config.initial_rustc = build - .rustc - .map(PathBuf::from) - .unwrap_or_else(|| config.out.join(config.build.triple).join("stage0/bin/rustc")); + config.initial_rustc = build.rustc.map(PathBuf::from).unwrap_or_else(|| { + config.download_beta_toolchain(); + config.out.join(config.build.triple).join("stage0/bin/rustc") + }); config.initial_cargo = build .cargo .map(PathBuf::from) @@ -1088,7 +1097,7 @@ impl Config { let mut debuginfo_level_tools = None; let mut debuginfo_level_tests = None; let mut optimize = None; - let mut ignore_git = None; + let mut omit_git_hash = None; if let Some(rust) = toml.rust { debug = rust.debug; @@ -1109,7 +1118,7 @@ impl Config { .map(|v| v.expect("invalid value for rust.split_debuginfo")) .unwrap_or(SplitDebuginfo::default_for_platform(&config.build.triple)); optimize = rust.optimize; - ignore_git = rust.ignore_git; + omit_git_hash = rust.omit_git_hash; config.rust_new_symbol_mangling = rust.new_symbol_mangling; set(&mut config.rust_optimize_tests, rust.optimize_tests); set(&mut config.codegen_tests, rust.codegen_tests); @@ -1166,8 +1175,8 @@ impl Config { // rust_info must be set before is_ci_llvm_available() is called. let default = config.channel == "dev"; - config.ignore_git = ignore_git.unwrap_or(default); - config.rust_info = GitInfo::new(config.ignore_git, &config.src); + config.omit_git_hash = omit_git_hash.unwrap_or(default); + config.rust_info = GitInfo::new(config.omit_git_hash, &config.src); if let Some(llvm) = toml.llvm { match llvm.ccache { diff --git a/src/bootstrap/config/tests.rs b/src/bootstrap/config/tests.rs index 5cea143e0a7..50569eb4f37 100644 --- a/src/bootstrap/config/tests.rs +++ b/src/bootstrap/config/tests.rs @@ -33,35 +33,58 @@ fn download_ci_llvm() { )); } +// FIXME(ozkanonur): extend scope of the test +// refs: +// - https://github.com/rust-lang/rust/issues/109120 +// - https://github.com/rust-lang/rust/pull/109162#issuecomment-1496782487 #[test] fn detect_src_and_out() { - let cfg = parse(""); + fn test(cfg: Config, build_dir: Option<&str>) { + // This will bring absolute form of `src/bootstrap` path + let current_dir = std::env::current_dir().unwrap(); - // This will bring absolute form of `src/bootstrap` path - let current_dir = std::env::current_dir().unwrap(); + // get `src` by moving into project root path + let expected_src = current_dir.ancestors().nth(2).unwrap(); + assert_eq!(&cfg.src, expected_src); - // get `src` by moving into project root path - let expected_src = current_dir.ancestors().nth(2).unwrap(); + // Sanity check for `src` + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let expected_src = manifest_dir.ancestors().nth(2).unwrap(); + assert_eq!(&cfg.src, expected_src); - assert_eq!(&cfg.src, expected_src); + // test if build-dir was manually given in config.toml + if let Some(custom_build_dir) = build_dir { + assert_eq!(&cfg.out, Path::new(custom_build_dir)); + } + // test the native bootstrap way + else { + // This should bring output path of bootstrap in absolute form + let cargo_target_dir = env::var_os("CARGO_TARGET_DIR").expect( + "CARGO_TARGET_DIR must been provided for the test environment from bootstrap", + ); - // This should bring output path of bootstrap in absolute form - let cargo_target_dir = env::var_os("CARGO_TARGET_DIR") - .expect("CARGO_TARGET_DIR must been provided for the test environment from bootstrap"); + // Move to `build` from `build/bootstrap` + let expected_out = Path::new(&cargo_target_dir).parent().unwrap(); + assert_eq!(&cfg.out, expected_out); - // Move to `build` from `build/bootstrap` - let expected_out = Path::new(&cargo_target_dir).parent().unwrap(); - assert_eq!(&cfg.out, expected_out); + let args: Vec<String> = env::args().collect(); - let args: Vec<String> = env::args().collect(); + // Another test for `out` as a sanity check + // + // This will bring something similar to: + // `{build-dir}/bootstrap/debug/deps/bootstrap-c7ee91d5661e2804` + // `{build-dir}` can be anywhere, not just in the rust project directory. + let dep = Path::new(args.first().unwrap()); + let expected_out = dep.ancestors().nth(4).unwrap(); - // Another test for `out` as a sanity check - // - // This will bring something similar to: - // `{config_toml_place}/build/bootstrap/debug/deps/bootstrap-c7ee91d5661e2804` - // `{config_toml_place}` can be anywhere, not just in the rust project directory. - let dep = Path::new(args.first().unwrap()); - let expected_out = dep.ancestors().nth(4).unwrap(); + assert_eq!(&cfg.out, expected_out); + } + } + + test(parse(""), None); - assert_eq!(&cfg.out, expected_out); + { + let build_dir = if cfg!(windows) { Some("C:\\tmp") } else { Some("/tmp") }; + test(parse("build.build-dir = \"/tmp\""), build_dir); + } } diff --git a/src/bootstrap/defaults/config.codegen.toml b/src/bootstrap/defaults/config.codegen.toml index 20b2699c761..113df88d7c3 100644 --- a/src/bootstrap/defaults/config.codegen.toml +++ b/src/bootstrap/defaults/config.codegen.toml @@ -9,6 +9,8 @@ compiler-docs = true assertions = true # enable warnings during the llvm compilation enable-warnings = true +# build llvm from source +download-ci-llvm = false [rust] # This enables `RUSTC_LOG=debug`, avoiding confusing situations diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 2ce54d9a3b4..8ce220c8647 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -1488,7 +1488,7 @@ impl Step for Extended { let xform = |p: &Path| { let mut contents = t!(fs::read_to_string(p)); - for tool in &["rust-demangler", "miri"] { + for tool in &["rust-demangler", "miri", "rust-docs"] { if !built_tools.contains(tool) { contents = filter(&contents, tool); } @@ -1585,11 +1585,10 @@ impl Step for Extended { prepare("rustc"); prepare("cargo"); prepare("rust-analysis"); - prepare("rust-docs"); prepare("rust-std"); prepare("clippy"); prepare("rust-analyzer"); - for tool in &["rust-demangler", "miri"] { + for tool in &["rust-docs", "rust-demangler", "miri"] { if built_tools.contains(tool) { prepare(tool); } @@ -1624,23 +1623,25 @@ impl Step for Extended { .arg("-out") .arg(exe.join("RustcGroup.wxs")), ); - builder.run( - Command::new(&heat) - .current_dir(&exe) - .arg("dir") - .arg("rust-docs") - .args(&heat_flags) - .arg("-cg") - .arg("DocsGroup") - .arg("-dr") - .arg("Docs") - .arg("-var") - .arg("var.DocsDir") - .arg("-out") - .arg(exe.join("DocsGroup.wxs")) - .arg("-t") - .arg(etc.join("msi/squash-components.xsl")), - ); + if built_tools.contains("rust-docs") { + builder.run( + Command::new(&heat) + .current_dir(&exe) + .arg("dir") + .arg("rust-docs") + .args(&heat_flags) + .arg("-cg") + .arg("DocsGroup") + .arg("-dr") + .arg("Docs") + .arg("-var") + .arg("var.DocsDir") + .arg("-out") + .arg(exe.join("DocsGroup.wxs")) + .arg("-t") + .arg(etc.join("msi/squash-components.xsl")), + ); + } builder.run( Command::new(&heat) .current_dir(&exe) @@ -1787,7 +1788,6 @@ impl Step for Extended { cmd.current_dir(&exe) .arg("-nologo") .arg("-dRustcDir=rustc") - .arg("-dDocsDir=rust-docs") .arg("-dCargoDir=cargo") .arg("-dStdDir=rust-std") .arg("-dAnalysisDir=rust-analysis") @@ -1799,6 +1799,9 @@ impl Step for Extended { .arg(&input); add_env(builder, &mut cmd, target); + if built_tools.contains("rust-docs") { + cmd.arg("-dDocsDir=rust-docs"); + } if built_tools.contains("rust-demangler") { cmd.arg("-dRustDemanglerDir=rust-demangler"); } @@ -1817,7 +1820,9 @@ impl Step for Extended { candle(&etc.join("msi/ui.wxs")); candle(&etc.join("msi/rustwelcomedlg.wxs")); candle("RustcGroup.wxs".as_ref()); - candle("DocsGroup.wxs".as_ref()); + if built_tools.contains("rust-docs") { + candle("DocsGroup.wxs".as_ref()); + } candle("CargoGroup.wxs".as_ref()); candle("StdGroup.wxs".as_ref()); candle("ClippyGroup.wxs".as_ref()); @@ -1854,7 +1859,6 @@ impl Step for Extended { .arg("ui.wixobj") .arg("rustwelcomedlg.wixobj") .arg("RustcGroup.wixobj") - .arg("DocsGroup.wixobj") .arg("CargoGroup.wixobj") .arg("StdGroup.wixobj") .arg("AnalysisGroup.wixobj") @@ -1870,6 +1874,9 @@ impl Step for Extended { if built_tools.contains("rust-demangler") { cmd.arg("RustDemanglerGroup.wixobj"); } + if built_tools.contains("rust-docs") { + cmd.arg("DocsGroup.wixobj"); + } if target.ends_with("windows-gnu") { cmd.arg("GccGroup.wixobj"); diff --git a/src/bootstrap/download.rs b/src/bootstrap/download.rs index 8fbc034965a..24251556584 100644 --- a/src/bootstrap/download.rs +++ b/src/bootstrap/download.rs @@ -367,26 +367,70 @@ impl Config { pub(crate) fn download_ci_rustc(&self, commit: &str) { self.verbose(&format!("using downloaded stage2 artifacts from CI (commit {commit})")); + let version = self.artifact_version_part(commit); + // download-rustc doesn't need its own cargo, it can just use beta's. But it does need the + // `rustc_private` crates for tools. + let extra_components = ["rustc-dev"]; + + self.download_toolchain( + &version, + "ci-rustc", + commit, + &extra_components, + Self::download_ci_component, + ); + } + + pub(crate) fn download_beta_toolchain(&self) { + self.verbose(&format!("downloading stage0 beta artifacts")); + + let date = &self.stage0_metadata.compiler.date; + let version = &self.stage0_metadata.compiler.version; + let extra_components = ["cargo"]; + + let download_beta_component = |config: &Config, filename, prefix: &_, date: &_| { + config.download_component(DownloadSource::Dist, filename, prefix, date, "stage0") + }; + + self.download_toolchain( + version, + "stage0", + date, + &extra_components, + download_beta_component, + ); + } + + fn download_toolchain( + &self, + // FIXME(ozkanonur) use CompilerMetadata instead of `version: &str` + version: &str, + sysroot: &str, + stamp_key: &str, + extra_components: &[&str], + download_component: fn(&Config, String, &str, &str), + ) { let host = self.build.triple; - let bin_root = self.out.join(host).join("ci-rustc"); + let bin_root = self.out.join(host).join(sysroot); let rustc_stamp = bin_root.join(".rustc-stamp"); - if !bin_root.join("bin").join("rustc").exists() || program_out_of_date(&rustc_stamp, commit) + if !bin_root.join("bin").join(exe("rustc", self.build)).exists() + || program_out_of_date(&rustc_stamp, stamp_key) { if bin_root.exists() { t!(fs::remove_dir_all(&bin_root)); } let filename = format!("rust-std-{version}-{host}.tar.xz"); let pattern = format!("rust-std-{host}"); - self.download_ci_component(filename, &pattern, commit); + download_component(self, filename, &pattern, stamp_key); let filename = format!("rustc-{version}-{host}.tar.xz"); - self.download_ci_component(filename, "rustc", commit); - // download-rustc doesn't need its own cargo, it can just use beta's. - let filename = format!("rustc-dev-{version}-{host}.tar.xz"); - self.download_ci_component(filename, "rustc-dev", commit); - let filename = format!("rust-src-{version}.tar.xz"); - self.download_ci_component(filename, "rust-src", commit); + download_component(self, filename, "rustc", stamp_key); + + for component in extra_components { + let filename = format!("{component}-{version}-{host}.tar.xz"); + download_component(self, filename, component, stamp_key); + } if self.should_fix_bins_and_dylibs() { self.fix_bin_or_dylib(&bin_root.join("bin").join("rustc")); @@ -403,7 +447,7 @@ impl Config { } } - t!(fs::write(rustc_stamp, commit)); + t!(fs::write(rustc_stamp, stamp_key)); } } diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index e3f3ab5243e..5ee18cf6411 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -358,14 +358,14 @@ impl Build { #[cfg(not(unix))] let is_sudo = false; - let ignore_git = config.ignore_git; - let rust_info = channel::GitInfo::new(ignore_git, &src); - let cargo_info = channel::GitInfo::new(ignore_git, &src.join("src/tools/cargo")); + let omit_git_hash = config.omit_git_hash; + let rust_info = channel::GitInfo::new(omit_git_hash, &src); + let cargo_info = channel::GitInfo::new(omit_git_hash, &src.join("src/tools/cargo")); let rust_analyzer_info = - channel::GitInfo::new(ignore_git, &src.join("src/tools/rust-analyzer")); - let clippy_info = channel::GitInfo::new(ignore_git, &src.join("src/tools/clippy")); - let miri_info = channel::GitInfo::new(ignore_git, &src.join("src/tools/miri")); - let rustfmt_info = channel::GitInfo::new(ignore_git, &src.join("src/tools/rustfmt")); + channel::GitInfo::new(omit_git_hash, &src.join("src/tools/rust-analyzer")); + let clippy_info = channel::GitInfo::new(omit_git_hash, &src.join("src/tools/clippy")); + let miri_info = channel::GitInfo::new(omit_git_hash, &src.join("src/tools/miri")); + let rustfmt_info = channel::GitInfo::new(omit_git_hash, &src.join("src/tools/rustfmt")); // we always try to use git for LLVM builds let in_tree_llvm_info = channel::GitInfo::new(false, &src.join("src/llvm-project")); @@ -1233,7 +1233,7 @@ impl Build { match &self.config.channel[..] { "stable" => num.to_string(), "beta" => { - if self.rust_info().is_managed_git_subrepository() && !self.config.ignore_git { + if self.rust_info().is_managed_git_subrepository() && !self.config.omit_git_hash { format!("{}-beta.{}", num, self.beta_prerelease_version()) } else { format!("{}-beta", num) diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 92a7603a9df..058ff429e80 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -694,7 +694,7 @@ impl Step for CompiletestTest { /// Runs `cargo test` for compiletest. fn run(self, builder: &Builder<'_>) { let host = self.host; - let compiler = builder.compiler(0, host); + let compiler = builder.compiler(1, host); // We need `ToolStd` for the locally-built sysroot because // compiletest uses unstable features of the `test` crate. @@ -1133,7 +1133,7 @@ impl Step for Tidy { if builder.config.channel == "dev" || builder.config.channel == "nightly" { builder.info("fmt check"); if builder.initial_rustfmt().is_none() { - let inferred_rustfmt_dir = builder.config.initial_rustc.parent().unwrap(); + let inferred_rustfmt_dir = builder.initial_rustc.parent().unwrap(); eprintln!( "\ error: no `rustfmt` binary found in {PATH} diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 3c9a154da9a..6a687a7903e 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -320,7 +320,7 @@ pub fn prepare_tool_cargo( cargo.env("CFG_RELEASE_NUM", &builder.version); cargo.env("DOC_RUST_LANG_ORG_CHANNEL", builder.doc_rust_lang_org_channel()); - let info = GitInfo::new(builder.config.ignore_git, &dir); + let info = GitInfo::new(builder.config.omit_git_hash, &dir); if let Some(sha) = info.sha() { cargo.env("CFG_COMMIT_HASH", sha); } diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs index 9a6aaffe22b..2e1adbf63bb 100644 --- a/src/bootstrap/util.rs +++ b/src/bootstrap/util.rs @@ -146,106 +146,9 @@ pub fn symlink_dir(config: &Config, src: &Path, dest: &Path) -> io::Result<()> { fs::symlink(src, dest) } - // Creating a directory junction on windows involves dealing with reparse - // points and the DeviceIoControl function, and this code is a skeleton of - // what can be found here: - // - // http://www.flexhex.com/docs/articles/hard-links.phtml #[cfg(windows)] fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> { - use std::ffi::OsStr; - use std::os::windows::ffi::OsStrExt; - - use windows::{ - core::PCWSTR, - Win32::Foundation::{CloseHandle, HANDLE}, - Win32::Storage::FileSystem::{ - CreateFileW, FILE_ACCESS_FLAGS, FILE_FLAG_BACKUP_SEMANTICS, - FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, - MAXIMUM_REPARSE_DATA_BUFFER_SIZE, OPEN_EXISTING, - }, - Win32::System::Ioctl::FSCTL_SET_REPARSE_POINT, - Win32::System::SystemServices::{GENERIC_WRITE, IO_REPARSE_TAG_MOUNT_POINT}, - Win32::System::IO::DeviceIoControl, - }; - - #[allow(non_snake_case)] - #[repr(C)] - struct REPARSE_MOUNTPOINT_DATA_BUFFER { - ReparseTag: u32, - ReparseDataLength: u32, - Reserved: u16, - ReparseTargetLength: u16, - ReparseTargetMaximumLength: u16, - Reserved1: u16, - ReparseTarget: u16, - } - - fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> { - Ok(s.as_ref().encode_wide().chain(Some(0)).collect()) - } - - // We're using low-level APIs to create the junction, and these are more - // picky about paths. For example, forward slashes cannot be used as a - // path separator, so we should try to canonicalize the path first. - let target = fs::canonicalize(target)?; - - fs::create_dir(junction)?; - - let path = to_u16s(junction)?; - - let h = unsafe { - CreateFileW( - PCWSTR(path.as_ptr()), - FILE_ACCESS_FLAGS(GENERIC_WRITE), - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - None, - OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, - HANDLE::default(), - ) - } - .map_err(|_| io::Error::last_os_error())?; - - unsafe { - #[repr(C, align(8))] - struct Align8<T>(T); - let mut data = Align8([0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]); - let db = data.0.as_mut_ptr() as *mut REPARSE_MOUNTPOINT_DATA_BUFFER; - let buf = core::ptr::addr_of_mut!((*db).ReparseTarget) as *mut u16; - let mut i = 0; - // FIXME: this conversion is very hacky - let v = br"\??\"; - let v = v.iter().map(|x| *x as u16); - for c in v.chain(target.as_os_str().encode_wide().skip(4)) { - *buf.offset(i) = c; - i += 1; - } - *buf.offset(i) = 0; - i += 1; - - (*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; - (*db).ReparseTargetMaximumLength = (i * 2) as u16; - (*db).ReparseTargetLength = ((i - 1) * 2) as u16; - (*db).ReparseDataLength = ((*db).ReparseTargetLength + 12) as u32; - - let mut ret = 0u32; - DeviceIoControl( - h, - FSCTL_SET_REPARSE_POINT, - Some(db.cast()), - (*db).ReparseDataLength + 8, - None, - 0, - Some(&mut ret), - None, - ) - .ok() - .map_err(|_| io::Error::last_os_error())?; - } - - unsafe { CloseHandle(h) }; - Ok(()) + junction::create(&target, &junction) } } diff --git a/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile b/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile index b5715024a84..34b93be412e 100644 --- a/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile +++ b/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile @@ -1,8 +1,6 @@ FROM ubuntu:22.04 ARG DEBIAN_FRONTEND=noninteractive -# NOTE: intentionally uses python2 for x.py so we can test it still works. -# validate-toolstate only runs in our CI, so it's ok for it to only support python3. RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ make \ @@ -33,4 +31,6 @@ RUN pip3 install --no-deps --no-cache-dir --require-hashes -r /tmp/reuse-require COPY host-x86_64/mingw-check/validate-toolstate.sh /scripts/ COPY host-x86_64/mingw-check/validate-error-codes.sh /scripts/ +# NOTE: intentionally uses python2 for x.py so we can test it still works. +# validate-toolstate only runs in our CI, so it's ok for it to only support python3. ENV SCRIPT python2.7 ../x.py test --stage 0 src/tools/tidy tidyselftest diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile index 7e640c49f01..2217e6ee704 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile @@ -24,6 +24,6 @@ RUN sh /scripts/sccache.sh # We are disabling CI LLVM since distcheck is an offline build. ENV NO_DOWNLOAD_CI_LLVM 1 -ENV RUST_CONFIGURE_ARGS --build=x86_64-unknown-linux-gnu --set rust.ignore-git=false +ENV RUST_CONFIGURE_ARGS --build=x86_64-unknown-linux-gnu --set rust.omit-git-hash=false ENV SCRIPT python3 ../x.py --stage 2 test distcheck ENV DIST_SRC 1 diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md index c7f120dafea..62347f169a5 100644 --- a/src/doc/rustc/src/codegen-options/index.md +++ b/src/doc/rustc/src/codegen-options/index.md @@ -71,9 +71,11 @@ If not specified, debug assertions are automatically enabled only if the This flag controls the generation of debug information. It takes one of the following values: -* `0`: no debug info at all (the default). -* `1`: line tables only. -* `2`: full debug info. +* `0` or `none`: no debug info at all (the default). +* `line-directives-only`: line info directives only. For the nvptx* targets this enables [profiling](https://reviews.llvm.org/D46061). For other use cases, `line-tables-only` is the better, more compatible choice. +* `line-tables-only`: line tables only. Generates the minimal amount of debug info for backtraces with filename/line number info, but not anything else, i.e. no variable or function parameter info. +* `1` or `limited`: debug info without type or variable-level information. +* `2` or `full`: full debug info. Note: The [`-g` flag][option-g-debug] is an alias for `-C debuginfo=2`. diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 5c18a38ddab..67fe2a610c2 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -257,6 +257,7 @@ target | std | host | notes `bpfel-unknown-none` | * | | BPF (little endian) `hexagon-unknown-linux-musl` | ? | | `i386-apple-ios` | ✓ | | 32-bit x86 iOS +[`i586-pc-nto-qnx700`](platform-support/nto-qnx.md) | * | | 32-bit x86 QNX Neutrino 7.0 RTOS | `i686-apple-darwin` | ✓ | ✓ | 32-bit macOS (10.7+, Lion+) `i686-pc-windows-msvc` | * | | 32-bit Windows XP support `i686-unknown-haiku` | ✓ | ✓ | 32-bit Haiku diff --git a/src/doc/rustc/src/platform-support/nto-qnx.md b/src/doc/rustc/src/platform-support/nto-qnx.md index 38198fe6c3a..0d815c9b598 100644 --- a/src/doc/rustc/src/platform-support/nto-qnx.md +++ b/src/doc/rustc/src/platform-support/nto-qnx.md @@ -16,10 +16,18 @@ and [Blackberry QNX][BlackBerry]. ## Requirements -Currently, only cross-compilation for QNX Neutrino on AArch64 and x86_64 are supported (little endian). +Currently, the following QNX Neutrino versions and compilation targets are supported: + +| QNX Neutrino Version | Target Architecture | Full support | `no_std` support | +|----------------------|---------------------|:------------:|:----------------:| +| 7.1 | AArch64 | ✓ | ✓ | +| 7.1 | x86_64 | ✓ | ✓ | +| 7.0 | x86 | | ✓ | + Adding other architectures that are supported by QNX Neutrino is possible. -The standard library, including `core` and `alloc` (with default allocator) are supported. +In the table above, 'full support' indicates support for building Rust applications with the full standard library. +'`no_std` support' indicates that only `core` and `alloc` are available. For building or using the Rust toolchain for QNX Neutrino, the [QNX Software Development Platform (SDP)](https://blackberry.qnx.com/en/products/foundation-software/qnx-software-development-platform) @@ -70,7 +78,7 @@ fn panic(_panic: &PanicInfo<'_>) -> ! { pub extern "C" fn rust_eh_personality() {} ``` -The QNX Neutrino support of Rust has been tested with QNX Neutrino 7.1. +The QNX Neutrino support of Rust has been tested with QNX Neutrino 7.0 and 7.1. There are no further known requirements. @@ -80,6 +88,7 @@ For conditional compilation, following QNX Neutrino specific attributes are defi - `target_os` = `"nto"` - `target_env` = `"nto71"` (for QNX Neutrino 7.1) +- `target_env` = `"nto70"` (for QNX Neutrino 7.0) ## Building the target diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 960c1de1782..ae180439d23 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -38,6 +38,15 @@ future. Attempting to use these error numbers on stable will result in the code sample being interpreted as plain text. +### `missing_doc_code_examples` lint + +This lint will emit a warning if an item doesn't have a code example in its documentation. +It can be enabled using: + +```rust,ignore (nightly) +#![deny(rustdoc::missing_doc_code_examples)] +``` + ## Extensions to the `#[doc]` attribute These features operate by extending the `#[doc]` attribute, and thus can be caught by the compiler diff --git a/src/doc/rustdoc/src/write-documentation/what-to-include.md b/src/doc/rustdoc/src/write-documentation/what-to-include.md index cf1e6a8d3ca..16457ed0ff8 100644 --- a/src/doc/rustdoc/src/write-documentation/what-to-include.md +++ b/src/doc/rustdoc/src/write-documentation/what-to-include.md @@ -39,9 +39,7 @@ warning: 1 warning emitted As a library author, adding the lint `#![deny(missing_docs)]` is a great way to ensure the project does not drift away from being documented well, and `#![warn(missing_docs)]` is a good way to move towards comprehensive -documentation. In addition to docs, `#![deny(rustdoc::missing_doc_code_examples)]` -ensures each function contains a usage example. In our example above, the -warning is resolved by adding crate level documentation. +documentation. There are more lints in the upcoming chapter [Lints][rustdoc-lints]. diff --git a/src/doc/unstable-book/src/language-features/lang-items.md b/src/doc/unstable-book/src/language-features/lang-items.md index 39238dffa10..6adb3506e64 100644 --- a/src/doc/unstable-book/src/language-features/lang-items.md +++ b/src/doc/unstable-book/src/language-features/lang-items.md @@ -16,18 +16,26 @@ and one for deallocation. A freestanding program that uses the `Box` sugar for dynamic allocations via `malloc` and `free`: ```rust,ignore (libc-is-finicky) -#![feature(lang_items, box_syntax, start, libc, core_intrinsics, rustc_private)] +#![feature(lang_items, start, libc, core_intrinsics, rustc_private, rustc_attrs)] #![no_std] use core::intrinsics; use core::panic::PanicInfo; +use core::ptr::NonNull; extern crate libc; -struct Unique<T>(*mut T); +struct Unique<T>(NonNull<T>); #[lang = "owned_box"] pub struct Box<T>(Unique<T>); +impl<T> Box<T> { + pub fn new(x: T) -> Self { + #[rustc_box] + Box::new(x) + } +} + #[lang = "exchange_malloc"] unsafe fn allocate(size: usize, _align: usize) -> *mut u8 { let p = libc::malloc(size as libc::size_t) as *mut u8; @@ -47,13 +55,13 @@ unsafe fn box_free<T: ?Sized>(ptr: *mut T) { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - let _x = box 1; + let _x = Box::new(1); 0 } #[lang = "eh_personality"] extern fn rust_eh_personality() {} -#[lang = "panic_impl"] extern fn rust_begin_panic(info: &PanicInfo) -> ! { unsafe { intrinsics::abort() } } +#[lang = "panic_impl"] extern fn rust_begin_panic(_info: &PanicInfo) -> ! { intrinsics::abort() } #[no_mangle] pub extern fn rust_eh_register_frames () {} #[no_mangle] pub extern fn rust_eh_unregister_frames () {} ``` diff --git a/src/doc/unstable-book/src/language-features/plugin.md b/src/doc/unstable-book/src/language-features/plugin.md index dfbb468d4df..1fade6ce95b 100644 --- a/src/doc/unstable-book/src/language-features/plugin.md +++ b/src/doc/unstable-book/src/language-features/plugin.md @@ -37,7 +37,7 @@ additional checks for code style, safety, etc. Now let's write a plugin that warns about any item named `lintme`. ```rust,ignore (requires-stage-2) -#![feature(box_syntax, rustc_private)] +#![feature(rustc_private)] extern crate rustc_ast; @@ -68,7 +68,7 @@ impl EarlyLintPass for Pass { #[no_mangle] fn __rustc_plugin_registrar(reg: &mut Registry) { reg.lint_store.register_lints(&[&TEST_LINT]); - reg.lint_store.register_early_pass(|| box Pass); + reg.lint_store.register_early_pass(|| Box::new(Pass)); } ``` diff --git a/src/etc/installer/msi/rust.wxs b/src/etc/installer/msi/rust.wxs index 0aa0784e544..9f4e4fd0611 100644 --- a/src/etc/installer/msi/rust.wxs +++ b/src/etc/installer/msi/rust.wxs @@ -167,7 +167,9 @@ <?if $(env.CFG_MINGW)="1" ?> <Directory Id="Gcc" Name="." /> <?endif?> + <!-- tool-rust-docs-start --> <Directory Id="Docs" Name="." /> + <!-- tool-rust-docs-end --> <Directory Id="Cargo" Name="." /> <Directory Id="Std" Name="." /> </Directory> @@ -209,6 +211,7 @@ <RegistryValue Root="HKMU" Key="$(var.BaseRegKey)" Name="RustShell" Type="integer" Value="1" KeyPath="yes" /> <RemoveFolder Id="ApplicationProgramsFolder1" On="uninstall" /> </Component> + <!-- tool-rust-docs-start --> <Component Id="DocIndexShortcut" Guid="*"> <Shortcut Id="RustDocs" Name="$(var.ProductName) Documentation" @@ -217,6 +220,7 @@ <RegistryValue Root="HKMU" Key="$(var.BaseRegKey)" Name="RustDocs" Type="integer" Value="1" KeyPath="yes" /> <RemoveFolder Id="ApplicationProgramsFolder2" On="uninstall" /> </Component> + <!-- tool-rust-docs-end --> </Directory> </Directory> @@ -256,6 +260,7 @@ <ComponentGroupRef Id="GccGroup" /> </Feature> <?endif?> + <!-- tool-rust-docs-start --> <Feature Id="Docs" Title="HTML documentation" Display="5" @@ -264,6 +269,7 @@ <ComponentGroupRef Id="DocsGroup" /> <ComponentRef Id="DocIndexShortcut" /> </Feature> + <!-- tool-rust-docs-end --> <Feature Id="Path" Title="Add to PATH" Description="Add Rust to PATH environment variable" diff --git a/src/etc/installer/pkg/Distribution.xml b/src/etc/installer/pkg/Distribution.xml index 64f6bab9bb5..1643fc8364b 100644 --- a/src/etc/installer/pkg/Distribution.xml +++ b/src/etc/installer/pkg/Distribution.xml @@ -15,7 +15,9 @@ <line choice="rustc"/> <line choice="rust-std"/> <line choice="cargo"/> + <!-- tool-rust-docs-start --> <line choice="rust-docs"/> + <!-- tool-rust-docs-end --> </line> <line choice="uninstall" /> </choices-outline> @@ -55,15 +57,19 @@ > <pkg-ref id="org.rust-lang.rust-std"/> </choice> + <!-- tool-rust-docs-start --> <choice id="rust-docs" visible="true" title="Documentation" description="HTML documentation." selected="(!choices.uninstall.selected && choices['rust-docs'].selected) || (choices.uninstall.selected && choices.install.selected)" > <pkg-ref id="org.rust-lang.rust-docs"/> </choice> + <!-- tool-rust-docs-end --> <pkg-ref id="org.rust-lang.rustc" version="0" onConclusion="none">rustc.pkg</pkg-ref> <pkg-ref id="org.rust-lang.cargo" version="0" onConclusion="none">cargo.pkg</pkg-ref> + <!-- tool-rust-docs-start --> <pkg-ref id="org.rust-lang.rust-docs" version="0" onConclusion="none">rust-docs.pkg</pkg-ref> + <!-- tool-rust-docs-end --> <pkg-ref id="org.rust-lang.rust-std" version="0" onConclusion="none">rust-std.pkg</pkg-ref> <pkg-ref id="org.rust-lang.uninstall" version="0" onConclusion="none">uninstall.pkg</pkg-ref> <background file="rust-logo.png" mime-type="image/png" diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 768f8bb7bc8..9270d1c02e2 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -153,7 +153,6 @@ pub(crate) fn try_inline_glob( let reexports = cx .tcx .module_reexports(current_mod) - .unwrap_or_default() .iter() .filter_map(|child| child.res.opt_def_id()) .collect(); @@ -558,7 +557,7 @@ fn build_module_items( // If we're re-exporting a re-export it may actually re-export something in // two namespaces, so the target may be listed twice. Make sure we only // visit each node at most once. - for &item in cx.tcx.module_children(did).iter() { + for item in cx.tcx.module_children(did).iter() { if item.vis.is_public() { let res = item.res.expect_non_local(); if let Some(def_id) = res.opt_def_id() diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 1c57cd7a946..7f150f38025 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -21,6 +21,7 @@ use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId, LOCAL_CRATE}; use rustc_hir::PredicateOrigin; use rustc_hir_analysis::hir_ty_to_ty; use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData}; +use rustc_middle::metadata::Reexport; use rustc_middle::middle::resolve_bound_vars as rbv; use rustc_middle::ty::fold::TypeFolder; use rustc_middle::ty::InternalSubsts; @@ -2056,141 +2057,44 @@ fn clean_bare_fn_ty<'tcx>( BareFunctionDecl { unsafety: bare_fn.unsafety, abi: bare_fn.abi, decl, generic_params } } -/// Get DefId of of an item's user-visible parent. -/// -/// "User-visible" should account for re-exporting and inlining, which is why this function isn't -/// just `tcx.parent(def_id)`. If the provided `path` has more than one path element, the `DefId` -/// of the second-to-last will be given. -/// -/// ```text -/// use crate::foo::Bar; -/// ^^^ DefId of this item will be returned -/// ``` -/// -/// If the provided path has only one item, `tcx.parent(def_id)` will be returned instead. -fn get_path_parent_def_id( - tcx: TyCtxt<'_>, - def_id: DefId, - path: &hir::UsePath<'_>, -) -> Option<DefId> { - if let [.., parent_segment, _] = &path.segments { - match parent_segment.res { - hir::def::Res::Def(_, parent_def_id) => Some(parent_def_id), - _ if parent_segment.ident.name == kw::Crate => { - // In case the "parent" is the crate, it'll give `Res::Err` so we need to - // circumvent it this way. - Some(tcx.parent(def_id)) - } - _ => None, - } - } else { - // If the path doesn't have a parent, then the parent is the current module. - Some(tcx.parent(def_id)) - } -} - -/// This visitor is used to find an HIR Item based on its `use` path. This doesn't use the ordinary -/// name resolver because it does not walk all the way through a chain of re-exports. -pub(crate) struct OneLevelVisitor<'hir> { - map: rustc_middle::hir::map::Map<'hir>, - pub(crate) item: Option<&'hir hir::Item<'hir>>, - looking_for: Ident, +pub(crate) fn reexport_chain<'tcx>( + tcx: TyCtxt<'tcx>, + import_def_id: LocalDefId, target_def_id: LocalDefId, -} - -impl<'hir> OneLevelVisitor<'hir> { - pub(crate) fn new(map: rustc_middle::hir::map::Map<'hir>, target_def_id: LocalDefId) -> Self { - Self { map, item: None, looking_for: Ident::empty(), target_def_id } - } - - pub(crate) fn find_target( - &mut self, - tcx: TyCtxt<'_>, - def_id: DefId, - path: &hir::UsePath<'_>, - ) -> Option<&'hir hir::Item<'hir>> { - let parent_def_id = get_path_parent_def_id(tcx, def_id, path)?; - let parent = self.map.get_if_local(parent_def_id)?; - - // We get the `Ident` we will be looking for into `item`. - self.looking_for = path.segments[path.segments.len() - 1].ident; - // We reset the `item`. - self.item = None; - - match parent { - hir::Node::Item(parent_item) => { - hir::intravisit::walk_item(self, parent_item); - } - hir::Node::Crate(m) => { - hir::intravisit::walk_mod( - self, - m, - tcx.local_def_id_to_hir_id(parent_def_id.as_local().unwrap()), - ); - } - _ => return None, - } - self.item - } -} - -impl<'hir> hir::intravisit::Visitor<'hir> for OneLevelVisitor<'hir> { - type NestedFilter = rustc_middle::hir::nested_filter::All; - - fn nested_visit_map(&mut self) -> Self::Map { - self.map - } - - fn visit_item(&mut self, item: &'hir hir::Item<'hir>) { - if self.item.is_none() - && item.ident == self.looking_for - && (matches!(item.kind, hir::ItemKind::Use(_, _)) - || item.owner_id.def_id == self.target_def_id) +) -> &'tcx [Reexport] { + for child in tcx.module_reexports(tcx.local_parent(import_def_id)) { + if child.res.opt_def_id() == Some(target_def_id.to_def_id()) + && child.reexport_chain[0].id() == Some(import_def_id.to_def_id()) { - self.item = Some(item); + return &child.reexport_chain; } } + &[] } -/// Because a `Use` item directly links to the imported item, we need to manually go through each -/// import one by one. To do so, we go to the parent item and look for the `Ident` into it. Then, -/// if we found the "end item" (the imported one), we stop there because we don't need its -/// documentation. Otherwise, we repeat the same operation until we find the "end item". +/// Collect attributes from the whole import chain. fn get_all_import_attributes<'hir>( - mut item: &hir::Item<'hir>, cx: &mut DocContext<'hir>, + import_def_id: LocalDefId, target_def_id: LocalDefId, is_inline: bool, - mut prev_import: LocalDefId, ) -> Vec<(Cow<'hir, ast::Attribute>, Option<DefId>)> { - let mut attributes: Vec<(Cow<'hir, ast::Attribute>, Option<DefId>)> = Vec::new(); + let mut attrs = Vec::new(); let mut first = true; - let hir_map = cx.tcx.hir(); - let mut visitor = OneLevelVisitor::new(hir_map, target_def_id); - let mut visited = FxHashSet::default(); - - // If the item is an import and has at least a path with two parts, we go into it. - while let hir::ItemKind::Use(path, _) = item.kind && visited.insert(item.hir_id()) { - let import_parent = cx.tcx.opt_local_parent(prev_import).map(|def_id| def_id.to_def_id()); + for def_id in reexport_chain(cx.tcx, import_def_id, target_def_id) + .iter() + .flat_map(|reexport| reexport.id()) + { + let import_attrs = inline::load_attrs(cx, def_id); if first { // This is the "original" reexport so we get all its attributes without filtering them. - attributes = hir_map.attrs(item.hir_id()) - .iter() - .map(|attr| (Cow::Borrowed(attr), import_parent)) - .collect::<Vec<_>>(); + attrs = import_attrs.iter().map(|attr| (Cow::Borrowed(attr), Some(def_id))).collect(); first = false; } else { - add_without_unwanted_attributes(&mut attributes, hir_map.attrs(item.hir_id()), is_inline, import_parent); + add_without_unwanted_attributes(&mut attrs, import_attrs, is_inline, Some(def_id)); } - - if let Some(i) = visitor.find_target(cx.tcx, item.owner_id.def_id.to_def_id(), path) { - item = i; - } else { - break; - } - prev_import = item.owner_id.def_id; } - attributes + attrs } fn filter_tokens_from_list( @@ -2375,39 +2279,24 @@ fn clean_maybe_renamed_item<'tcx>( _ => unreachable!("not yet converted"), }; - let attrs = if let Some(import_id) = import_id && - let Some(hir::Node::Item(use_node)) = cx.tcx.hir().find_by_def_id(import_id) - { + let target_attrs = inline::load_attrs(cx, def_id); + let attrs = if let Some(import_id) = import_id { let is_inline = inline::load_attrs(cx, import_id.to_def_id()) .lists(sym::doc) .get_word_attr(sym::inline) .is_some(); - // Then we get all the various imports' attributes. - let mut attrs = get_all_import_attributes( - use_node, - cx, - item.owner_id.def_id, - is_inline, - import_id, - ); - - add_without_unwanted_attributes( - &mut attrs, - inline::load_attrs(cx, def_id), - is_inline, - None - ); + let mut attrs = + get_all_import_attributes(cx, import_id, item.owner_id.def_id, is_inline); + add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None); attrs } else { // We only keep the item's attributes. - inline::load_attrs(cx, def_id).iter().map(|attr| (Cow::Borrowed(attr), None)).collect::<Vec<_>>() + target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect() }; let cfg = attrs.cfg(cx.tcx, &cx.cache.hidden_cfg); - let attrs = Attributes::from_ast_iter(attrs.iter().map(|(attr, did)| match attr { - Cow::Borrowed(attr) => (*attr, *did), - Cow::Owned(attr) => (attr, *did) - }), false); + let attrs = + Attributes::from_ast_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false); let mut item = Item::from_def_id_and_attrs_and_parts(def_id, Some(name), kind, Box::new(attrs), cfg); diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 0895bb510d4..7a2449cbe9a 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -136,10 +136,6 @@ impl Buffer { self.into_inner() } - pub(crate) fn is_for_html(&self) -> bool { - self.for_html - } - pub(crate) fn reserve(&mut self, additional: usize) { self.buffer.reserve(additional) } @@ -1142,22 +1138,21 @@ fn fmt_type<'cx>( // the ugliness comes from inlining across crates where // everything comes in as a fully resolved QPath (hard to // look at). - match href(trait_.def_id(), cx) { - Ok((ref url, _, ref path)) if !f.alternate() => { - write!( - f, - "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \ - title=\"type {path}::{name}\">{name}</a>{args}", - url = url, - shortty = ItemType::AssocType, - name = assoc.name, - path = join_with_double_colon(path), - args = assoc.args.print(cx), - )?; - } - _ => write!(f, "{}{:#}", assoc.name, assoc.args.print(cx))?, - } - Ok(()) + if !f.alternate() && let Ok((url, _, path)) = href(trait_.def_id(), cx) { + write!( + f, + "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \ + title=\"type {path}::{name}\">{name}</a>", + shortty = ItemType::AssocType, + name = assoc.name, + path = join_with_double_colon(&path), + ) + } else { + write!(f, "{}", assoc.name) + }?; + + // Carry `f.alternate()` into this display w/o branching manually. + fmt::Display::fmt(&assoc.args.print(cx), f) } } } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index d75d03071f8..1e3cd266850 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -50,6 +50,7 @@ use std::string::ToString; use askama::Template; use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; +use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::Mutability; @@ -69,7 +70,7 @@ use crate::formats::item_type::ItemType; use crate::formats::{AssocItemRender, Impl, RenderMode}; use crate::html::escape::Escape; use crate::html::format::{ - href, join_with_double_colon, print_abi_with_space, print_constness_with_space, + display_fn, href, join_with_double_colon, print_abi_with_space, print_constness_with_space, print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space, Buffer, Ending, HrefError, PrintWithSpace, }; @@ -408,128 +409,134 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String { ) } -fn document( - w: &mut Buffer, - cx: &mut Context<'_>, - item: &clean::Item, - parent: Option<&clean::Item>, +fn document<'a, 'cx: 'a>( + cx: &'a mut Context<'cx>, + item: &'a clean::Item, + parent: Option<&'a clean::Item>, heading_offset: HeadingOffset, -) { +) -> impl fmt::Display + 'a + Captures<'cx> { if let Some(ref name) = item.name { info!("Documenting {}", name); } - document_item_info(cx, item, parent).render_into(w).unwrap(); - if parent.is_none() { - document_full_collapsible(w, item, cx, heading_offset); - } else { - document_full(w, item, cx, heading_offset); - } + + display_fn(move |f| { + document_item_info(cx, item, parent).render_into(f).unwrap(); + if parent.is_none() { + write!(f, "{}", document_full_collapsible(item, cx, heading_offset))?; + } else { + write!(f, "{}", document_full(item, cx, heading_offset))?; + } + Ok(()) + }) } /// Render md_text as markdown. -fn render_markdown( - w: &mut Buffer, - cx: &mut Context<'_>, - md_text: &str, +fn render_markdown<'a, 'cx: 'a>( + cx: &'a mut Context<'cx>, + md_text: &'a str, links: Vec<RenderedLink>, heading_offset: HeadingOffset, -) { - write!( - w, - "<div class=\"docblock\">{}</div>", - Markdown { - content: md_text, - links: &links, - ids: &mut cx.id_map, - error_codes: cx.shared.codes, - edition: cx.shared.edition(), - playground: &cx.shared.playground, - heading_offset, - } - .into_string() - ) +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |f| { + write!( + f, + "<div class=\"docblock\">{}</div>", + Markdown { + content: md_text, + links: &links, + ids: &mut cx.id_map, + error_codes: cx.shared.codes, + edition: cx.shared.edition(), + playground: &cx.shared.playground, + heading_offset, + } + .into_string() + ) + }) } /// Writes a documentation block containing only the first paragraph of the documentation. If the /// docs are longer, a "Read more" link is appended to the end. -fn document_short( - w: &mut Buffer, - item: &clean::Item, - cx: &mut Context<'_>, - link: AssocItemLink<'_>, - parent: &clean::Item, +fn document_short<'a, 'cx: 'a>( + item: &'a clean::Item, + cx: &'a mut Context<'cx>, + link: AssocItemLink<'a>, + parent: &'a clean::Item, show_def_docs: bool, -) { - document_item_info(cx, item, Some(parent)).render_into(w).unwrap(); - if !show_def_docs { - return; - } - if let Some(s) = item.doc_value() { - let (mut summary_html, has_more_content) = - MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |f| { + document_item_info(cx, item, Some(parent)).render_into(f).unwrap(); + if !show_def_docs { + return Ok(()); + } + if let Some(s) = item.doc_value() { + let (mut summary_html, has_more_content) = + MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); - if has_more_content { - let link = format!(r#" <a{}>Read more</a>"#, assoc_href_attr(item, link, cx)); + if has_more_content { + let link = format!(r#" <a{}>Read more</a>"#, assoc_href_attr(item, link, cx)); - if let Some(idx) = summary_html.rfind("</p>") { - summary_html.insert_str(idx, &link); - } else { - summary_html.push_str(&link); + if let Some(idx) = summary_html.rfind("</p>") { + summary_html.insert_str(idx, &link); + } else { + summary_html.push_str(&link); + } } - } - write!(w, "<div class='docblock'>{}</div>", summary_html,); - } + write!(f, "<div class='docblock'>{}</div>", summary_html)?; + } + Ok(()) + }) } -fn document_full_collapsible( - w: &mut Buffer, - item: &clean::Item, - cx: &mut Context<'_>, +fn document_full_collapsible<'a, 'cx: 'a>( + item: &'a clean::Item, + cx: &'a mut Context<'cx>, heading_offset: HeadingOffset, -) { - document_full_inner(w, item, cx, true, heading_offset); +) -> impl fmt::Display + 'a + Captures<'cx> { + document_full_inner(item, cx, true, heading_offset) } -fn document_full( - w: &mut Buffer, - item: &clean::Item, - cx: &mut Context<'_>, +fn document_full<'a, 'cx: 'a>( + item: &'a clean::Item, + cx: &'a mut Context<'cx>, heading_offset: HeadingOffset, -) { - document_full_inner(w, item, cx, false, heading_offset); +) -> impl fmt::Display + 'a + Captures<'cx> { + document_full_inner(item, cx, false, heading_offset) } -fn document_full_inner( - w: &mut Buffer, - item: &clean::Item, - cx: &mut Context<'_>, +fn document_full_inner<'a, 'cx: 'a>( + item: &'a clean::Item, + cx: &'a mut Context<'cx>, is_collapsible: bool, heading_offset: HeadingOffset, -) { - if let Some(s) = item.collapsed_doc_value() { - debug!("Doc block: =====\n{}\n=====", s); - if is_collapsible { - w.write_str( - "<details class=\"toggle top-doc\" open>\ - <summary class=\"hideme\">\ - <span>Expand description</span>\ - </summary>", - ); - render_markdown(w, cx, &s, item.links(cx), heading_offset); - w.write_str("</details>"); - } else { - render_markdown(w, cx, &s, item.links(cx), heading_offset); +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |f| { + if let Some(s) = item.collapsed_doc_value() { + debug!("Doc block: =====\n{}\n=====", s); + if is_collapsible { + write!( + f, + "<details class=\"toggle top-doc\" open>\ + <summary class=\"hideme\">\ + <span>Expand description</span>\ + </summary>{}</details>", + render_markdown(cx, &s, item.links(cx), heading_offset) + )?; + } else { + write!(f, "{}", render_markdown(cx, &s, item.links(cx), heading_offset))?; + } } - } - let kind = match &*item.kind { - clean::ItemKind::StrippedItem(box kind) | kind => kind, - }; + let kind = match &*item.kind { + clean::ItemKind::StrippedItem(box kind) | kind => kind, + }; - if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind { - render_call_locations(w, cx, item); - } + if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind { + render_call_locations(f, cx, item); + } + Ok(()) + }) } #[derive(Template)] @@ -653,7 +660,7 @@ fn short_item_info( // "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages). pub(crate) fn render_impls( cx: &mut Context<'_>, - w: &mut Buffer, + mut w: impl Write, impls: &[&Impl], containing_item: &clean::Item, toggle_open_by_default: bool, @@ -665,7 +672,7 @@ pub(crate) fn render_impls( let did = i.trait_did().unwrap(); let provided_trait_methods = i.inner_impl().provided_trait_methods(tcx); let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods); - let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() }; + let mut buffer = Buffer::new(); render_impl( &mut buffer, cx, @@ -686,7 +693,7 @@ pub(crate) fn render_impls( }) .collect::<Vec<_>>(); rendered_impls.sort(); - w.write_str(&rendered_impls.join("")); + w.write_str(&rendered_impls.join("")).unwrap(); } /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item. @@ -842,7 +849,7 @@ fn assoc_method( let (indent, indent_str, end_newline) = if parent == ItemType::Trait { header_len += 4; let indent_str = " "; - render_attributes_in_pre(w, meth, indent_str); + write!(w, "{}", render_attributes_in_pre(meth, indent_str)); (4, indent_str, Ending::NoNewline) } else { render_attributes_in_code(w, meth); @@ -1038,10 +1045,16 @@ fn attributes(it: &clean::Item) -> Vec<String> { // When an attribute is rendered inside a `<pre>` tag, it is formatted using // a whitespace prefix and newline. -fn render_attributes_in_pre(w: &mut Buffer, it: &clean::Item, prefix: &str) { - for a in attributes(it) { - writeln!(w, "{}{}", prefix, a); - } +fn render_attributes_in_pre<'a>( + it: &'a clean::Item, + prefix: &'a str, +) -> impl fmt::Display + Captures<'a> { + crate::html::format::display_fn(move |f| { + for a in attributes(it) { + writeln!(f, "{}{}", prefix, a)?; + } + Ok(()) + }) } // When an attribute is rendered inside a <code> tag, it is formatted using @@ -1067,61 +1080,68 @@ impl<'a> AssocItemLink<'a> { } } -fn write_impl_section_heading(w: &mut Buffer, title: &str, id: &str) { +fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) { write!( w, "<h2 id=\"{id}\" class=\"small-section-header\">\ {title}\ <a href=\"#{id}\" class=\"anchor\">§</a>\ </h2>" - ); + ) + .unwrap(); } pub(crate) fn render_all_impls( - w: &mut Buffer, + mut w: impl Write, cx: &mut Context<'_>, containing_item: &clean::Item, concrete: &[&Impl], synthetic: &[&Impl], blanket_impl: &[&Impl], ) { - let mut impls = Buffer::empty_from(w); + let mut impls = Buffer::html(); render_impls(cx, &mut impls, concrete, containing_item, true); let impls = impls.into_inner(); if !impls.is_empty() { - write_impl_section_heading(w, "Trait Implementations", "trait-implementations"); - write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls); + write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations"); + write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls).unwrap(); } if !synthetic.is_empty() { - write_impl_section_heading(w, "Auto Trait Implementations", "synthetic-implementations"); - w.write_str("<div id=\"synthetic-implementations-list\">"); - render_impls(cx, w, synthetic, containing_item, false); - w.write_str("</div>"); + write_impl_section_heading( + &mut w, + "Auto Trait Implementations", + "synthetic-implementations", + ); + w.write_str("<div id=\"synthetic-implementations-list\">").unwrap(); + render_impls(cx, &mut w, synthetic, containing_item, false); + w.write_str("</div>").unwrap(); } if !blanket_impl.is_empty() { - write_impl_section_heading(w, "Blanket Implementations", "blanket-implementations"); - w.write_str("<div id=\"blanket-implementations-list\">"); - render_impls(cx, w, blanket_impl, containing_item, false); - w.write_str("</div>"); + write_impl_section_heading(&mut w, "Blanket Implementations", "blanket-implementations"); + w.write_str("<div id=\"blanket-implementations-list\">").unwrap(); + render_impls(cx, &mut w, blanket_impl, containing_item, false); + w.write_str("</div>").unwrap(); } } -fn render_assoc_items( - w: &mut Buffer, - cx: &mut Context<'_>, - containing_item: &clean::Item, +fn render_assoc_items<'a, 'cx: 'a>( + cx: &'a mut Context<'cx>, + containing_item: &'a clean::Item, it: DefId, - what: AssocItemRender<'_>, -) { + what: AssocItemRender<'a>, +) -> impl fmt::Display + 'a + Captures<'cx> { let mut derefs = DefIdSet::default(); derefs.insert(it); - render_assoc_items_inner(w, cx, containing_item, it, what, &mut derefs) + display_fn(move |f| { + render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs); + Ok(()) + }) } fn render_assoc_items_inner( - w: &mut Buffer, + mut w: &mut dyn fmt::Write, cx: &mut Context<'_>, containing_item: &clean::Item, it: DefId, @@ -1134,7 +1154,7 @@ fn render_assoc_items_inner( let Some(v) = cache.impls.get(&it) else { return }; let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none()); if !non_trait.is_empty() { - let mut tmp_buf = Buffer::empty_from(w); + let mut tmp_buf = Buffer::html(); let (render_mode, id) = match what { AssocItemRender::All => { write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations"); @@ -1158,7 +1178,7 @@ fn render_assoc_items_inner( (RenderMode::ForDeref { mut_: deref_mut_ }, cx.derive_id(id)) } }; - let mut impls_buf = Buffer::empty_from(w); + let mut impls_buf = Buffer::html(); for i in &non_trait { render_impl( &mut impls_buf, @@ -1178,10 +1198,10 @@ fn render_assoc_items_inner( ); } if !impls_buf.is_empty() { - w.push_buffer(tmp_buf); - write!(w, "<div id=\"{}\">", id); - w.push_buffer(impls_buf); - w.write_str("</div>"); + write!(w, "{}", tmp_buf.into_inner()).unwrap(); + write!(w, "<div id=\"{}\">", id).unwrap(); + write!(w, "{}", impls_buf.into_inner()).unwrap(); + w.write_str("</div>").unwrap(); } } @@ -1191,7 +1211,7 @@ fn render_assoc_items_inner( if let Some(impl_) = deref_impl { let has_deref_mut = traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait()); - render_deref_methods(w, cx, impl_, containing_item, has_deref_mut, derefs); + render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs); } // If we were already one level into rendering deref methods, we don't want to render @@ -1210,7 +1230,7 @@ fn render_assoc_items_inner( } fn render_deref_methods( - w: &mut Buffer, + mut w: impl Write, cx: &mut Context<'_>, impl_: &Impl, container_item: &clean::Item, @@ -1242,10 +1262,10 @@ fn render_deref_methods( return; } } - render_assoc_items_inner(w, cx, container_item, did, what, derefs); + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs); } else if let Some(prim) = target.primitive_type() { if let Some(&did) = cache.primitive_locations.get(&prim) { - render_assoc_items_inner(w, cx, container_item, did, what, derefs); + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs); } } } @@ -1478,18 +1498,25 @@ fn render_impl( document_item_info(cx, it, Some(parent)) .render_into(&mut info_buffer) .unwrap(); - document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); + write!( + &mut doc_buffer, + "{}", + document_full(item, cx, HeadingOffset::H5) + ); short_documented = false; } else { // In case the item isn't documented, // provide short documentation from the trait. - document_short( + write!( &mut doc_buffer, - it, - cx, - link, - parent, - rendering_params.show_def_docs, + "{}", + document_short( + it, + cx, + link, + parent, + rendering_params.show_def_docs, + ) ); } } @@ -1498,18 +1525,15 @@ fn render_impl( .render_into(&mut info_buffer) .unwrap(); if rendering_params.show_def_docs { - document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); + write!(&mut doc_buffer, "{}", document_full(item, cx, HeadingOffset::H5)); short_documented = false; } } } else { - document_short( + write!( &mut doc_buffer, - item, - cx, - link, - parent, - rendering_params.show_def_docs, + "{}", + document_short(item, cx, link, parent, rendering_params.show_def_docs,) ); } } @@ -2206,7 +2230,7 @@ const MAX_FULL_EXAMPLES: usize = 5; const NUM_VISIBLE_LINES: usize = 10; /// Generates the HTML for example call locations generated via the --scrape-examples flag. -fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item) { +fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &clean::Item) { let tcx = cx.tcx(); let def_id = item.item_id.expect_def_id(); let key = tcx.def_path_hash(def_id); @@ -2215,7 +2239,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // Generate a unique ID so users can link to this section for a given method let id = cx.id_map.derive("scraped-examples"); write!( - w, + &mut w, "<div class=\"docblock scraped-example-list\">\ <span></span>\ <h5 id=\"{id}\">\ @@ -2224,7 +2248,8 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite </h5>", root_path = cx.root_path(), id = id - ); + ) + .unwrap(); // Create a URL to a particular location in a reverse-dependency's source file let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) { @@ -2242,7 +2267,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite }; // Generate the HTML for a single example, being the title and code block - let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| -> bool { + let write_example = |mut w: &mut W, (path, call_data): (&PathBuf, &CallData)| -> bool { let contents = match fs::read_to_string(&path) { Ok(contents) => contents, Err(err) => { @@ -2290,7 +2315,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite let locations_encoded = serde_json::to_string(&line_ranges).unwrap(); write!( - w, + &mut w, "<div class=\"scraped-example {expanded_cls}\" data-locs=\"{locations}\">\ <div class=\"scraped-example-title\">\ {name} (<a href=\"{url}\">{title}</a>)\ @@ -2303,10 +2328,12 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // The locations are encoded as a data attribute, so they can be read // later by the JS for interactions. locations = Escape(&locations_encoded) - ); + ) + .unwrap(); if line_ranges.len() > 1 { - write!(w, r#"<button class="prev">≺</button> <button class="next">≻</button>"#); + write!(w, r#"<button class="prev">≺</button> <button class="next">≻</button>"#) + .unwrap(); } // Look for the example file in the source map if it exists, otherwise return a dummy span @@ -2333,7 +2360,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite decoration_info.insert("highlight", byte_ranges); sources::print_src( - w, + &mut w, contents_subset, file_span, cx, @@ -2341,7 +2368,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite highlight::DecorationInfo(decoration_info), sources::SourceContext::Embedded { offset: line_min, needs_expansion }, ); - write!(w, "</div></div>"); + write!(w, "</div></div>").unwrap(); true }; @@ -2375,7 +2402,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // An example may fail to write if its source can't be read for some reason, so this method // continues iterating until a write succeeds - let write_and_skip_failure = |w: &mut Buffer, it: &mut Peekable<_>| { + let write_and_skip_failure = |w: &mut W, it: &mut Peekable<_>| { while let Some(example) = it.next() { if write_example(&mut *w, example) { break; @@ -2384,7 +2411,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite }; // Write just one example that's visible by default in the method's description. - write_and_skip_failure(w, &mut it); + write_and_skip_failure(&mut w, &mut it); // Then add the remaining examples in a hidden section. if it.peek().is_some() { @@ -2397,17 +2424,19 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite <div class=\"hide-more\">Hide additional examples</div>\ <div class=\"more-scraped-examples\">\ <div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>" - ); + ) + .unwrap(); // Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could // make the page arbitrarily huge! for _ in 0..MAX_FULL_EXAMPLES { - write_and_skip_failure(w, &mut it); + write_and_skip_failure(&mut w, &mut it); } // For the remaining examples, generate a <ul> containing links to the source files. if it.peek().is_some() { - write!(w, r#"<div class="example-links">Additional examples can be found in:<br><ul>"#); + write!(w, r#"<div class="example-links">Additional examples can be found in:<br><ul>"#) + .unwrap(); it.for_each(|(_, call_data)| { let (url, _) = link_to_loc(call_data, &call_data.locations[0]); write!( @@ -2415,13 +2444,14 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite r#"<li><a href="{url}">{name}</a></li>"#, url = url, name = call_data.display_name - ); + ) + .unwrap(); }); - write!(w, "</ul></div>"); + write!(w, "</ul></div>").unwrap(); } - write!(w, "</div></details>"); + write!(w, "</div></details>").unwrap(); } - write!(w, "</div>"); + write!(w, "</div>").unwrap(); } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 674cd0d62d4..6bce5734004 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -202,7 +202,7 @@ fn should_hide_fields(n_fields: usize) -> bool { n_fields > 12 } -fn toggle_open(w: &mut Buffer, text: impl fmt::Display) { +fn toggle_open(mut w: impl fmt::Write, text: impl fmt::Display) { write!( w, "<details class=\"toggle type-contents-toggle\">\ @@ -210,15 +210,16 @@ fn toggle_open(w: &mut Buffer, text: impl fmt::Display) { <span>Show {}</span>\ </summary>", text - ); + ) + .unwrap(); } -fn toggle_close(w: &mut Buffer) { - w.write_str("</details>"); +fn toggle_close(mut w: impl fmt::Write) { + w.write_str("</details>").unwrap(); } fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) { - document(w, cx, item, None, HeadingOffset::H2); + write!(w, "{}", document(cx, item, None, HeadingOffset::H2)); let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::<Vec<usize>>(); @@ -544,12 +545,12 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx)); wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); w.reserve(header_len); write!( w, - "{vis}{constness}{asyncness}{unsafety}{abi}fn \ + "{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}", + attrs = render_attributes_in_pre(it, ""), vis = visibility, constness = constness, asyncness = asyncness, @@ -562,7 +563,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle notable_traits = notable_traits.unwrap_or_default(), ); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); } fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Trait) { @@ -580,17 +581,17 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: let must_implement_one_of_functions = tcx.trait_def(t.def_id).must_implement_one_of.clone(); // Output the trait definition - wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); + wrap_item(w, |mut w| { write!( w, - "{}{}{}trait {}{}{}", + "{attrs}{}{}{}trait {}{}{}", visibility_print_with_space(it.visibility(tcx), it.item_id, cx), t.unsafety(tcx).print_with_space(), if t.is_auto(tcx) { "auto " } else { "" }, it.name.unwrap(), t.generics.print(cx), - bounds + bounds, + attrs = render_attributes_in_pre(it, ""), ); if !t.generics.where_predicates.is_empty() { @@ -610,7 +611,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: if should_hide_fields(count_types) { toggle = true; toggle_open( - w, + &mut w, format_args!("{} associated items", count_types + count_consts + count_methods), ); } @@ -634,7 +635,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: if !toggle && should_hide_fields(count_types + count_consts) { toggle = true; toggle_open( - w, + &mut w, format_args!( "{} associated constant{} and {} method{}", count_consts, @@ -662,7 +663,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } if !toggle && should_hide_fields(count_methods) { toggle = true; - toggle_open(w, format_args!("{} methods", count_methods)); + toggle_open(&mut w, format_args!("{} methods", count_methods)); } if count_consts != 0 && count_methods != 0 { w.write_str("\n"); @@ -710,14 +711,14 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } } if toggle { - toggle_close(w); + toggle_close(&mut w); } w.write_str("}"); } }); // Trait documentation - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) { write!( @@ -735,7 +736,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: let item_type = m.type_(); let id = cx.derive_id(format!("{}.{}", item_type, name)); let mut content = Buffer::empty_from(w); - document(&mut content, cx, m, Some(t), HeadingOffset::H5); + write!(&mut content, "{}", document(cx, m, Some(t), HeadingOffset::H5)); let toggled = !content.is_empty(); if toggled { let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; @@ -847,7 +848,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } // If there are methods directly on this trait object, render them here. - render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All); + write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)); let cloned_shared = Rc::clone(&cx.shared); let cache = &cloned_shared.cache; @@ -1057,147 +1058,201 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::TraitAlias) { wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); write!( w, - "trait {}{}{} = {};", + "{attrs}trait {}{}{} = {};", it.name.unwrap(), t.generics.print(cx), print_where_clause(&t.generics, cx, 0, Ending::Newline), - bounds(&t.bounds, true, cx) + bounds(&t.bounds, true, cx), + attrs = render_attributes_in_pre(it, ""), ); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); // Render any items associated directly to this alias, as otherwise they // won't be visible anywhere in the docs. It would be nice to also show // associated items from the aliased type (see discussion in #32077), but // we need #14072 to make sense of the generics. - render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All) + write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)) } fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) { wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); write!( w, - "type {}{}{where_clause} = impl {bounds};", + "{attrs}type {}{}{where_clause} = impl {bounds};", it.name.unwrap(), t.generics.print(cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), bounds = bounds(&t.bounds, false, cx), + attrs = render_attributes_in_pre(it, ""), ); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); // Render any items associated directly to this alias, as otherwise they // won't be visible anywhere in the docs. It would be nice to also show // associated items from the aliased type (see discussion in #32077), but // we need #14072 to make sense of the generics. - render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All) + write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)) } fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Typedef) { fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) { wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); write!( w, - "{}type {}{}{where_clause} = {type_};", + "{attrs}{}type {}{}{where_clause} = {type_};", visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), it.name.unwrap(), t.generics.print(cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), type_ = t.type_.print(cx), + attrs = render_attributes_in_pre(it, ""), ); }); } write_content(w, cx, it, t); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); let def_id = it.item_id.expect_def_id(); // Render any items associated directly to this alias, as otherwise they // won't be visible anywhere in the docs. It would be nice to also show // associated items from the aliased type (see discussion in #32077), but // we need #14072 to make sense of the generics. - render_assoc_items(w, cx, it, def_id, AssocItemRender::All); - document_type_layout(w, cx, def_id); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", document_type_layout(cx, def_id)); } fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) { - wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); - render_union(w, it, Some(&s.generics), &s.fields, cx); - }); + #[derive(Template)] + #[template(path = "item_union.html")] + struct ItemUnion<'a, 'cx> { + cx: std::cell::RefCell<&'a mut Context<'cx>>, + it: &'a clean::Item, + s: &'a clean::Union, + } - document(w, cx, it, None, HeadingOffset::H2); + impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { + fn render_assoc_items<'b>( + &'b self, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let def_id = self.it.item_id.expect_def_id(); + let mut cx = self.cx.borrow_mut(); + let v = render_assoc_items(*cx, self.it, def_id, AssocItemRender::All); + write!(f, "{v}") + }) + } + fn document_type_layout<'b>( + &'b self, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let def_id = self.it.item_id.expect_def_id(); + let cx = self.cx.borrow_mut(); + let v = document_type_layout(*cx, def_id); + write!(f, "{v}") + }) + } + fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let cx = self.cx.borrow_mut(); + let v = render_union(self.it, Some(&self.s.generics), &self.s.fields, *cx); + write!(f, "{v}") + }) + } + fn render_attributes_in_pre<'b>( + &'b self, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let v = render_attributes_in_pre(self.it, ""); + write!(f, "{v}") + }) + } + fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let mut cx = self.cx.borrow_mut(); + let v = document(*cx, self.it, None, HeadingOffset::H2); + write!(f, "{v}") + }) + } + fn document_field<'b>( + &'b self, + field: &'a clean::Item, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let mut cx = self.cx.borrow_mut(); + let v = document(*cx, field, Some(self.it), HeadingOffset::H3); + write!(f, "{v}") + }) + } + fn stability_field(&self, field: &clean::Item) -> Option<String> { + let cx = self.cx.borrow(); + field.stability_class(cx.tcx()) + } + fn print_ty<'b>( + &'b self, + ty: &'a clean::Type, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let cx = self.cx.borrow(); + let v = ty.print(*cx); + write!(f, "{v}") + }) + } - let mut fields = s - .fields - .iter() - .filter_map(|f| match *f.kind { - clean::StructFieldItem(ref ty) => Some((f, ty)), - _ => None, - }) - .peekable(); - if fields.peek().is_some() { - write!( - w, - "<h2 id=\"fields\" class=\"fields small-section-header\">\ - Fields<a href=\"#fields\" class=\"anchor\">§</a>\ - </h2>" - ); - for (field, ty) in fields { - let name = field.name.expect("union field name"); - let id = format!("{}.{}", ItemType::StructField, name); - write!( - w, - "<span id=\"{id}\" class=\"{shortty} small-section-header\">\ - <a href=\"#{id}\" class=\"anchor field\">§</a>\ - <code>{name}: {ty}</code>\ - </span>", - shortty = ItemType::StructField, - ty = ty.print(cx), - ); - if let Some(stability_class) = field.stability_class(cx.tcx()) { - write!(w, "<span class=\"stab {stability_class}\"></span>"); - } - document(w, cx, field, Some(it), HeadingOffset::H3); + fn fields_iter( + &self, + ) -> std::iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>> { + self.s + .fields + .iter() + .filter_map(|f| match *f.kind { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, + }) + .peekable() } } - let def_id = it.item_id.expect_def_id(); - render_assoc_items(w, cx, it, def_id, AssocItemRender::All); - document_type_layout(w, cx, def_id); + + ItemUnion { cx: std::cell::RefCell::new(cx), it, s }.render_into(w).unwrap(); } -fn print_tuple_struct_fields(w: &mut Buffer, cx: &Context<'_>, s: &[clean::Item]) { - for (i, ty) in s.iter().enumerate() { - if i > 0 { - w.write_str(", "); - } - match *ty.kind { - clean::StrippedItem(box clean::StructFieldItem(_)) => w.write_str("_"), - clean::StructFieldItem(ref ty) => write!(w, "{}", ty.print(cx)), - _ => unreachable!(), +fn print_tuple_struct_fields<'a, 'cx: 'a>( + cx: &'a Context<'cx>, + s: &'a [clean::Item], +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(|f| { + for (i, ty) in s.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + match *ty.kind { + clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_")?, + clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx))?, + _ => unreachable!(), + } } - } + Ok(()) + }) } fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) { let tcx = cx.tcx(); let count_variants = e.variants().count(); - wrap_item(w, |w| { - render_attributes_in_pre(w, it, ""); + wrap_item(w, |mut w| { write!( w, - "{}enum {}{}", + "{attrs}{}enum {}{}", visibility_print_with_space(it.visibility(tcx), it.item_id, cx), it.name.unwrap(), e.generics.print(cx), + attrs = render_attributes_in_pre(it, ""), ); if !print_where_clause_and_check(w, &e.generics, cx) { // If there wasn't a `where` clause, we add a whitespace. @@ -1211,7 +1266,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: w.write_str("{\n"); let toggle = should_hide_fields(count_variants); if toggle { - toggle_open(w, format_args!("{} variants", count_variants)); + toggle_open(&mut w, format_args!("{} variants", count_variants)); } for v in e.variants() { w.write_str(" "); @@ -1221,9 +1276,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: clean::VariantItem(ref var) => match var.kind { clean::VariantKind::CLike => write!(w, "{}", name), clean::VariantKind::Tuple(ref s) => { - write!(w, "{}(", name); - print_tuple_struct_fields(w, cx, s); - w.write_str(")"); + write!(w, "{name}({})", print_tuple_struct_fields(cx, s),); } clean::VariantKind::Struct(ref s) => { render_struct(w, v, None, None, &s.fields, " ", false, cx); @@ -1238,24 +1291,25 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: w.write_str(" // some variants omitted\n"); } if toggle { - toggle_close(w); + toggle_close(&mut w); } w.write_str("}"); } }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); if count_variants != 0 { write!( w, "<h2 id=\"variants\" class=\"variants small-section-header\">\ Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\ - </h2>", - document_non_exhaustive_header(it) + </h2>\ + {}\ + <div class=\"variants\">", + document_non_exhaustive_header(it), + document_non_exhaustive(it) ); - document_non_exhaustive(w, it); - write!(w, "<div class=\"variants\">"); for variant in e.variants() { let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); write!( @@ -1276,9 +1330,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() }; if let clean::VariantKind::Tuple(ref s) = variant_data.kind { - w.write_str("("); - print_tuple_struct_fields(w, cx, s); - w.write_str(")"); + write!(w, "({})", print_tuple_struct_fields(cx, s),); } w.write_str("</h3></section>"); @@ -1302,9 +1354,10 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: write!( w, "<div class=\"sub-variant\" id=\"{variant_id}\">\ - <h4>{heading}</h4>", + <h4>{heading}</h4>\ + {}", + document_non_exhaustive(variant) ); - document_non_exhaustive(w, variant); for field in fields { match *field.kind { clean::StrippedItem(box clean::StructFieldItem(_)) => {} @@ -1322,10 +1375,13 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: <code>{f}: {t}</code>\ </span>", f = field.name.unwrap(), - t = ty.print(cx) + t = ty.print(cx), + ); + write!( + w, + "{}</div>", + document(cx, field, Some(variant), HeadingOffset::H5) ); - document(w, cx, field, Some(variant), HeadingOffset::H5); - write!(w, "</div>"); } _ => unreachable!(), } @@ -1333,18 +1389,18 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: w.write_str("</div>"); } - document(w, cx, variant, Some(it), HeadingOffset::H4); + write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4)); } write!(w, "</div>"); } let def_id = it.item_id.expect_def_id(); - render_assoc_items(w, cx, it, def_id, AssocItemRender::All); - document_type_layout(w, cx, def_id); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", document_type_layout(cx, def_id)); } fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Macro) { highlight::render_item_decl_with_highlighting(&t.source, w); - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &clean::ProcMacro) { @@ -1370,14 +1426,14 @@ fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &c } } }); - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { let def_id = it.item_id.expect_def_id(); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - render_assoc_items(w, cx, it, def_id, AssocItemRender::All); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); } else { // We handle the "reference" primitive type on its own because we only want to list // implementations on generic types. @@ -1433,7 +1489,7 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle } }); - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) { @@ -1442,7 +1498,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); let mut fields = s .fields @@ -1458,11 +1514,12 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean w, "<h2 id=\"fields\" class=\"fields small-section-header\">\ {}{}<a href=\"#fields\" class=\"anchor\">§</a>\ - </h2>", + </h2>\ + {}", if s.ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, - document_non_exhaustive_header(it) + document_non_exhaustive_header(it), + document_non_exhaustive(it) ); - document_non_exhaustive(w, it); for (index, (field, ty)) in fields.enumerate() { let field_name = field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string()); @@ -1476,13 +1533,13 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean item_type = ItemType::StructField, ty = ty.print(cx) ); - document(w, cx, field, Some(it), HeadingOffset::H3); + write!(w, "{}", document(cx, field, Some(it), HeadingOffset::H3)); } } } let def_id = it.item_id.expect_def_id(); - render_assoc_items(w, cx, it, def_id, AssocItemRender::All); - document_type_layout(w, cx, def_id); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", document_type_layout(cx, def_id)); } fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { @@ -1497,7 +1554,7 @@ fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean typ = s.type_.print(cx) ); }); - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { @@ -1512,13 +1569,13 @@ fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { ); }); - document(w, cx, it, None, HeadingOffset::H2); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); - render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All) + write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)) } fn item_keyword(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { - document(w, cx, it, None, HeadingOffset::H2) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } /// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). @@ -1655,64 +1712,69 @@ fn render_implementor( ); } -fn render_union( - w: &mut Buffer, - it: &clean::Item, - g: Option<&clean::Generics>, - fields: &[clean::Item], - cx: &Context<'_>, -) { - let tcx = cx.tcx(); - write!( - w, - "{}union {}", - visibility_print_with_space(it.visibility(tcx), it.item_id, cx), - it.name.unwrap(), - ); - - let where_displayed = g - .map(|g| { - write!(w, "{}", g.print(cx)); - print_where_clause_and_check(w, g, cx) - }) - .unwrap_or(false); +fn render_union<'a, 'cx: 'a>( + it: &'a clean::Item, + g: Option<&'a clean::Generics>, + fields: &'a [clean::Item], + cx: &'a Context<'cx>, +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |mut f| { + let tcx = cx.tcx(); + write!( + f, + "{}union {}", + visibility_print_with_space(it.visibility(tcx), it.item_id, cx), + it.name.unwrap(), + )?; + + let where_displayed = g + .map(|g| { + let mut buf = Buffer::html(); + write!(buf, "{}", g.print(cx)); + let where_displayed = print_where_clause_and_check(&mut buf, g, cx); + write!(f, "{buf}", buf = buf.into_inner()).unwrap(); + where_displayed + }) + .unwrap_or(false); - // If there wasn't a `where` clause, we add a whitespace. - if !where_displayed { - w.write_str(" "); - } + // If there wasn't a `where` clause, we add a whitespace. + if !where_displayed { + f.write_str(" ")?; + } - write!(w, "{{\n"); - let count_fields = - fields.iter().filter(|f| matches!(*f.kind, clean::StructFieldItem(..))).count(); - let toggle = should_hide_fields(count_fields); - if toggle { - toggle_open(w, format_args!("{} fields", count_fields)); - } + write!(f, "{{\n")?; + let count_fields = + fields.iter().filter(|field| matches!(*field.kind, clean::StructFieldItem(..))).count(); + let toggle = should_hide_fields(count_fields); + if toggle { + toggle_open(&mut f, format_args!("{} fields", count_fields)); + } - for field in fields { - if let clean::StructFieldItem(ref ty) = *field.kind { - write!( - w, - " {}{}: {},\n", - visibility_print_with_space(field.visibility(tcx), field.item_id, cx), - field.name.unwrap(), - ty.print(cx) - ); + for field in fields { + if let clean::StructFieldItem(ref ty) = *field.kind { + write!( + f, + " {}{}: {},\n", + visibility_print_with_space(field.visibility(tcx), field.item_id, cx), + field.name.unwrap(), + ty.print(cx) + )?; + } } - } - if it.has_stripped_entries().unwrap() { - write!(w, " /* private fields */\n"); - } - if toggle { - toggle_close(w); - } - w.write_str("}"); + if it.has_stripped_entries().unwrap() { + write!(f, " /* private fields */\n")?; + } + if toggle { + toggle_close(&mut f); + } + f.write_str("}").unwrap(); + Ok(()) + }) } fn render_struct( - w: &mut Buffer, + mut w: &mut Buffer, it: &clean::Item, g: Option<&clean::Generics>, ty: Option<CtorKind>, @@ -1747,7 +1809,7 @@ fn render_struct( let has_visible_fields = count_fields > 0; let toggle = should_hide_fields(count_fields); if toggle { - toggle_open(w, format_args!("{} fields", count_fields)); + toggle_open(&mut w, format_args!("{} fields", count_fields)); } for field in fields { if let clean::StructFieldItem(ref ty) = *field.kind { @@ -1771,7 +1833,7 @@ fn render_struct( write!(w, " /* private fields */ "); } if toggle { - toggle_close(w); + toggle_close(&mut w); } w.write_str("}"); } @@ -1817,161 +1879,169 @@ fn document_non_exhaustive_header(item: &clean::Item) -> &str { if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" } } -fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) { - if item.is_non_exhaustive() { - write!( - w, - "<details class=\"toggle non-exhaustive\">\ - <summary class=\"hideme\"><span>{}</span></summary>\ - <div class=\"docblock\">", - { - if item.is_struct() { - "This struct is marked as non-exhaustive" - } else if item.is_enum() { - "This enum is marked as non-exhaustive" - } else if item.is_variant() { - "This variant is marked as non-exhaustive" - } else { - "This type is marked as non-exhaustive" +fn document_non_exhaustive<'a>(item: &'a clean::Item) -> impl fmt::Display + 'a { + display_fn(|f| { + if item.is_non_exhaustive() { + write!( + f, + "<details class=\"toggle non-exhaustive\">\ + <summary class=\"hideme\"><span>{}</span></summary>\ + <div class=\"docblock\">", + { + if item.is_struct() { + "This struct is marked as non-exhaustive" + } else if item.is_enum() { + "This enum is marked as non-exhaustive" + } else if item.is_variant() { + "This variant is marked as non-exhaustive" + } else { + "This type is marked as non-exhaustive" + } } + )?; + + if item.is_struct() { + f.write_str( + "Non-exhaustive structs could have additional fields added in future. \ + Therefore, non-exhaustive structs cannot be constructed in external crates \ + using the traditional <code>Struct { .. }</code> syntax; cannot be \ + matched against without a wildcard <code>..</code>; and \ + struct update syntax will not work.", + )?; + } else if item.is_enum() { + f.write_str( + "Non-exhaustive enums could have additional variants added in future. \ + Therefore, when matching against variants of non-exhaustive enums, an \ + extra wildcard arm must be added to account for any future variants.", + )?; + } else if item.is_variant() { + f.write_str( + "Non-exhaustive enum variants could have additional fields added in future. \ + Therefore, non-exhaustive enum variants cannot be constructed in external \ + crates and cannot be matched against.", + )?; + } else { + f.write_str( + "This type will require a wildcard arm in any match statements or constructors.", + )?; } - ); - if item.is_struct() { - w.write_str( - "Non-exhaustive structs could have additional fields added in future. \ - Therefore, non-exhaustive structs cannot be constructed in external crates \ - using the traditional <code>Struct { .. }</code> syntax; cannot be \ - matched against without a wildcard <code>..</code>; and \ - struct update syntax will not work.", - ); - } else if item.is_enum() { - w.write_str( - "Non-exhaustive enums could have additional variants added in future. \ - Therefore, when matching against variants of non-exhaustive enums, an \ - extra wildcard arm must be added to account for any future variants.", - ); - } else if item.is_variant() { - w.write_str( - "Non-exhaustive enum variants could have additional fields added in future. \ - Therefore, non-exhaustive enum variants cannot be constructed in external \ - crates and cannot be matched against.", - ); - } else { - w.write_str( - "This type will require a wildcard arm in any match statements or constructors.", - ); + f.write_str("</div></details>")?; } - - w.write_str("</div></details>"); - } + Ok(()) + }) } -fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) { - fn write_size_of_layout(w: &mut Buffer, layout: &LayoutS, tag_size: u64) { +fn document_type_layout<'a, 'cx: 'a>( + cx: &'a Context<'cx>, + ty_def_id: DefId, +) -> impl fmt::Display + 'a + Captures<'cx> { + fn write_size_of_layout(mut w: impl fmt::Write, layout: &LayoutS, tag_size: u64) { if layout.abi.is_unsized() { - write!(w, "(unsized)"); + write!(w, "(unsized)").unwrap(); } else { let size = layout.size.bytes() - tag_size; - write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" },); + write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" }).unwrap(); if layout.abi.is_uninhabited() { write!( w, " (<a href=\"https://doc.rust-lang.org/stable/reference/glossary.html#uninhabited\">uninhabited</a>)" - ); + ).unwrap(); } } } - if !cx.shared.show_type_layout { - return; - } - - writeln!( - w, - "<h2 id=\"layout\" class=\"small-section-header\"> \ - Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>" - ); - writeln!(w, "<div class=\"docblock\">"); - - let tcx = cx.tcx(); - let param_env = tcx.param_env(ty_def_id); - let ty = tcx.type_of(ty_def_id).subst_identity(); - match tcx.layout_of(param_env.and(ty)) { - Ok(ty_layout) => { - writeln!( - w, - "<div class=\"warning\"><p><strong>Note:</strong> Most layout information is \ - <strong>completely unstable</strong> and may even differ between compilations. \ - The only exception is types with certain <code>repr(...)</code> attributes. \ - Please see the Rust Reference’s \ - <a href=\"https://doc.rust-lang.org/reference/type-layout.html\">“Type Layout”</a> \ - chapter for details on type layout guarantees.</p></div>" - ); - w.write_str("<p><strong>Size:</strong> "); - write_size_of_layout(w, &ty_layout.layout.0, 0); - writeln!(w, "</p>"); - if let Variants::Multiple { variants, tag, tag_encoding, .. } = - &ty_layout.layout.variants() - { - if !variants.is_empty() { - w.write_str( - "<p><strong>Size for each variant:</strong></p>\ - <ul>", - ); - - let Adt(adt, _) = ty_layout.ty.kind() else { - span_bug!(tcx.def_span(ty_def_id), "not an adt") - }; + display_fn(move |mut f| { + if !cx.shared.show_type_layout { + return Ok(()); + } - let tag_size = if let TagEncoding::Niche { .. } = tag_encoding { - 0 - } else if let Primitive::Int(i, _) = tag.primitive() { - i.size().bytes() - } else { - span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int") - }; + writeln!( + f, + "<h2 id=\"layout\" class=\"small-section-header\"> \ + Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>" + )?; + writeln!(f, "<div class=\"docblock\">")?; - for (index, layout) in variants.iter_enumerated() { - let name = adt.variant(index).name; - write!(w, "<li><code>{name}</code>: "); - write_size_of_layout(w, layout, tag_size); - writeln!(w, "</li>"); + let tcx = cx.tcx(); + let param_env = tcx.param_env(ty_def_id); + let ty = tcx.type_of(ty_def_id).subst_identity(); + match tcx.layout_of(param_env.and(ty)) { + Ok(ty_layout) => { + writeln!( + f, + "<div class=\"warning\"><p><strong>Note:</strong> Most layout information is \ + <strong>completely unstable</strong> and may even differ between compilations. \ + The only exception is types with certain <code>repr(...)</code> attributes. \ + Please see the Rust Reference’s \ + <a href=\"https://doc.rust-lang.org/reference/type-layout.html\">“Type Layout”</a> \ + chapter for details on type layout guarantees.</p></div>" + )?; + f.write_str("<p><strong>Size:</strong> ")?; + write_size_of_layout(&mut f, &ty_layout.layout.0, 0); + writeln!(f, "</p>")?; + if let Variants::Multiple { variants, tag, tag_encoding, .. } = + &ty_layout.layout.variants() + { + if !variants.is_empty() { + f.write_str( + "<p><strong>Size for each variant:</strong></p>\ + <ul>", + )?; + + let Adt(adt, _) = ty_layout.ty.kind() else { + span_bug!(tcx.def_span(ty_def_id), "not an adt") + }; + + let tag_size = if let TagEncoding::Niche { .. } = tag_encoding { + 0 + } else if let Primitive::Int(i, _) = tag.primitive() { + i.size().bytes() + } else { + span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int") + }; + + for (index, layout) in variants.iter_enumerated() { + let name = adt.variant(index).name; + write!(&mut f, "<li><code>{name}</code>: ")?; + write_size_of_layout(&mut f, layout, tag_size); + writeln!(&mut f, "</li>")?; + } + f.write_str("</ul>")?; } - w.write_str("</ul>"); } } + // This kind of layout error can occur with valid code, e.g. if you try to + // get the layout of a generic type such as `Vec<T>`. + Err(LayoutError::Unknown(_)) => { + writeln!( + f, + "<p><strong>Note:</strong> Unable to compute type layout, \ + possibly due to this type having generic parameters. \ + Layout can only be computed for concrete, fully-instantiated types.</p>" + )?; + } + // This kind of error probably can't happen with valid code, but we don't + // want to panic and prevent the docs from building, so we just let the + // user know that we couldn't compute the layout. + Err(LayoutError::SizeOverflow(_)) => { + writeln!( + f, + "<p><strong>Note:</strong> Encountered an error during type layout; \ + the type was too big.</p>" + )?; + } + Err(LayoutError::NormalizationFailure(_, _)) => { + writeln!( + f, + "<p><strong>Note:</strong> Encountered an error during type layout; \ + the type failed to be normalized.</p>" + )?; + } } - // This kind of layout error can occur with valid code, e.g. if you try to - // get the layout of a generic type such as `Vec<T>`. - Err(LayoutError::Unknown(_)) => { - writeln!( - w, - "<p><strong>Note:</strong> Unable to compute type layout, \ - possibly due to this type having generic parameters. \ - Layout can only be computed for concrete, fully-instantiated types.</p>" - ); - } - // This kind of error probably can't happen with valid code, but we don't - // want to panic and prevent the docs from building, so we just let the - // user know that we couldn't compute the layout. - Err(LayoutError::SizeOverflow(_)) => { - writeln!( - w, - "<p><strong>Note:</strong> Encountered an error during type layout; \ - the type was too big.</p>" - ); - } - Err(LayoutError::NormalizationFailure(_, _)) => { - writeln!( - w, - "<p><strong>Note:</strong> Encountered an error during type layout; \ - the type failed to be normalized.</p>" - ) - } - } - writeln!(w, "</div>"); + writeln!(f, "</div>") + }) } fn pluralize(count: usize) -> &'static str { diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index 1d298f52f75..c8397967c87 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -2,7 +2,6 @@ use crate::clean; use crate::docfs::PathError; use crate::error::Error; use crate::html::format; -use crate::html::format::Buffer; use crate::html::highlight; use crate::html::layout; use crate::html::render::Context; @@ -17,6 +16,7 @@ use rustc_span::source_map::FileName; use std::cell::RefCell; use std::ffi::OsStr; +use std::fmt; use std::fs; use std::ops::RangeInclusive; use std::path::{Component, Path, PathBuf}; @@ -294,7 +294,7 @@ pub(crate) enum SourceContext { /// Wrapper struct to render the source code of a file. This will do things like /// adding line numbers to the left-hand side. pub(crate) fn print_src( - buf: &mut Buffer, + mut writer: impl fmt::Write, s: &str, file_span: rustc_span::Span, context: &Context<'_>, @@ -329,5 +329,5 @@ pub(crate) fn print_src( ); Ok(()) }); - Source { embedded, needs_expansion, lines, code_html: code }.render_into(buf).unwrap(); + Source { embedded, needs_expansion, lines, code_html: code }.render_into(&mut writer).unwrap(); } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 933a44c5aa7..9df19352567 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -6,6 +6,10 @@ 3. Copy the filenames with updated suffixes from the directory. */ +:root { + --nav-sub-mobile-padding: 8px; +} + /* See FiraSans-LICENSE.txt for the Fira Sans license. */ @font-face { font-family: 'Fira Sans'; @@ -348,7 +352,7 @@ pre.item-decl { .source .content pre { padding: 20px; } -.rustdoc.source .example-wrap > pre.src-line-numbers { +.rustdoc.source .example-wrap pre.src-line-numbers { padding: 20px 0 20px 4px; } @@ -392,6 +396,7 @@ img { overflow-x: hidden; /* The sidebar is by default hidden */ overflow-y: hidden; + z-index: 1; } .sidebar, .mobile-topbar, .sidebar-menu-toggle, @@ -532,14 +537,17 @@ ul.block, .block li { margin-bottom: 0px; } -.rustdoc .example-wrap > pre { +.rustdoc .example-wrap pre { margin: 0; flex-grow: 1; +} + +.rustdoc:not(.source) .example-wrap pre { overflow: auto hidden; } -.rustdoc .example-wrap > pre.example-line-numbers, -.rustdoc .example-wrap > pre.src-line-numbers { +.rustdoc .example-wrap pre.example-line-numbers, +.rustdoc .example-wrap pre.src-line-numbers { flex-grow: 0; min-width: fit-content; /* prevent collapsing into nothing in truncated scraped examples */ overflow: initial; @@ -550,7 +558,7 @@ ul.block, .block li { color: var(--src-line-numbers-span-color); } -.rustdoc .example-wrap > pre.src-line-numbers { +.rustdoc .example-wrap pre.src-line-numbers { padding: 14px 0; } .src-line-numbers a, .src-line-numbers span { @@ -698,7 +706,7 @@ h2.small-section-header > .anchor { } .main-heading a:hover, -.example-wrap > .rust a:hover, +.example-wrap .rust a:hover, .all-items a:hover, .docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover, .docblock-short a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover, @@ -1722,7 +1730,7 @@ in main.js .source nav.sub { margin: 0; - padding: 8px; + padding: var(--nav-sub-mobile-padding); } } @@ -1779,6 +1787,7 @@ in main.js .sub-logo-container > img { height: 35px; width: 35px; + margin-bottom: var(--nav-sub-mobile-padding); } } diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 45c0360a49e..56ee4c1510e 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -332,13 +332,7 @@ function preLoadCss(cssUrl) { }; function getPageId() { - if (window.location.hash) { - const tmp = window.location.hash.replace(/^#/, ""); - if (tmp.length > 0) { - return tmp; - } - } - return null; + return window.location.hash.replace(/^#/, ""); } const toggleAllDocsId = "toggle-all-docs"; @@ -707,7 +701,7 @@ function preLoadCss(cssUrl) { }); const pageId = getPageId(); - if (pageId !== null) { + if (pageId !== "") { expandSection(pageId); } }()); diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 1cd552e7f25..ebbe6c1ca9a 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -86,12 +86,8 @@ if (settingId === "theme") { const useSystem = getSettingValue("use-system-theme"); if (useSystem === "true" || settingValue === null) { - if (useSystem !== "false") { - settingValue = "system preference"; - } else { - // This is the default theme. - settingValue = "light"; - } + // "light" is the default theme + settingValue = useSystem === "false" ? "light" : "system preference"; } } if (settingValue !== null && settingValue !== "null") { diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 8d82b5b78ed..93979a94418 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -53,10 +53,9 @@ function removeClass(elem, className) { * @param {boolean} [reversed] - Whether to iterate in reverse */ function onEach(arr, func, reversed) { - if (arr && arr.length > 0 && func) { + if (arr && arr.length > 0) { if (reversed) { - const length = arr.length; - for (let i = length - 1; i >= 0; --i) { + for (let i = arr.length - 1; i >= 0; --i) { if (func(arr[i])) { return true; } @@ -150,26 +149,19 @@ const updateTheme = (function() { * … dictates that it should be. */ function updateTheme() { - const use = (theme, saveTheme) => { - switchTheme(theme, saveTheme); - }; - // maybe the user has disabled the setting in the meantime! if (getSettingValue("use-system-theme") !== "false") { const lightTheme = getSettingValue("preferred-light-theme") || "light"; const darkTheme = getSettingValue("preferred-dark-theme") || "dark"; + updateLocalStorage("use-system-theme", "true"); - if (mql.matches) { - use(darkTheme, true); - } else { - // prefers a light theme, or has no preference - use(lightTheme, true); - } + // use light theme if user prefers it, or has no preference + switchTheme(mql.matches ? darkTheme : lightTheme, true); // note: we save the theme so that it doesn't suddenly change when // the user disables "use-system-theme" and reloads the page or // navigates to another page } else { - use(getSettingValue("theme"), false); + switchTheme(getSettingValue("theme"), false); } } diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html new file mode 100644 index 00000000000..a01457971c1 --- /dev/null +++ b/src/librustdoc/html/templates/item_union.html @@ -0,0 +1,23 @@ +<pre class="rust item-decl"><code> + {{ self.render_attributes_in_pre() | safe }} + {{ self.render_union() | safe }} +</code></pre> +{{ self.document() | safe }} +{% if self.fields_iter().peek().is_some() %} + <h2 id="fields" class="fields small-section-header"> + Fields<a href="#fields" class="anchor">§</a> + </h2> + {% for (field, ty) in self.fields_iter() %} + {% let name = field.name.expect("union field name") %} + <span id="structfield.{{ name }}" class="{{ ItemType::StructField }} small-section-header"> + <a href="#structfield.{{ name }}" class="anchor field">§</a> + <code>{{ name }}: {{ self.print_ty(ty) | safe }}</code> + </span> + {% if let Some(stability_class) = self.stability_field(field) %} + <span class="stab {{ stability_class }}"></span> + {% endif %} + {{ self.document_field(field) | safe }} + {% endfor %} +{% endif %} +{{ self.render_assoc_items() | safe }} +{{ self.document_type_layout() | safe }} diff --git a/src/librustdoc/html/templates/source.html b/src/librustdoc/html/templates/source.html index a224ff12f44..42d01277db2 100644 --- a/src/librustdoc/html/templates/source.html +++ b/src/librustdoc/html/templates/source.html @@ -1,5 +1,7 @@ <div class="example-wrap"> {# #} - <pre class="src-line-numbers"> + {# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr + Do not show "1 2 3 4 5 ..." in web search results. #} + <div data-nosnippet><pre class="src-line-numbers"> {% for line in lines.clone() %} {% if embedded %} <span>{{line|safe}}</span> @@ -7,7 +9,7 @@ <a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a> {%~ endif %} {% endfor %} - </pre> {# #} + </pre></div> {# #} <pre class="rust"> {# #} <code> {% if needs_expansion %} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 4c4dbc9864f..79f53ee57cc 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -203,7 +203,7 @@ fn init_logging() { .with_verbose_exit(true) .with_verbose_entry(true) .with_indent_amount(2); - #[cfg(parallel_compiler)] + #[cfg(all(parallel_compiler, debug_assertions))] let layer = layer.with_thread_ids(true).with_thread_names(true); use tracing_subscriber::layer::SubscriberExt; diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 060062db002..393d51fe090 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -13,9 +13,9 @@ use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; use rustc_span::symbol::{kw, sym, Symbol}; use rustc_span::Span; -use std::mem; +use std::{iter, mem}; -use crate::clean::{cfg::Cfg, AttributesExt, NestedAttributesExt, OneLevelVisitor}; +use crate::clean::{cfg::Cfg, reexport_chain, AttributesExt, NestedAttributesExt}; use crate::core; /// This module is used to store stuff from Rust's AST in a more convenient @@ -133,7 +133,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // is declared but also a reexport of itself producing two exports of the same // macro in the same module. let mut inserted = FxHashSet::default(); - for export in self.cx.tcx.module_reexports(CRATE_DEF_ID).unwrap_or(&[]) { + for export in self.cx.tcx.module_reexports(CRATE_DEF_ID) { if let Res::Def(DefKind::Macro(_), def_id) = export.res && let Some(local_def_id) = def_id.as_local() && self.cx.tcx.has_attr(def_id, sym::macro_export) && @@ -220,7 +220,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { renamed: Option<Symbol>, glob: bool, please_inline: bool, - path: &hir::UsePath<'_>, ) -> bool { debug!("maybe_inline_local res: {:?}", res); @@ -266,9 +265,9 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { } if !please_inline && - let mut visitor = OneLevelVisitor::new(self.cx.tcx.hir(), res_did) && - let Some(item) = visitor.find_target(self.cx.tcx, def_id.to_def_id(), path) && - let item_def_id = item.owner_id.def_id && + let Some(item_def_id) = reexport_chain(self.cx.tcx, def_id, res_did).iter() + .flat_map(|reexport| reexport.id()).map(|id| id.expect_local()) + .chain(iter::once(res_did)).nth(1) && item_def_id != def_id && self .cx @@ -383,7 +382,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { ident, is_glob, please_inline, - path, ) { continue; } @@ -421,12 +419,20 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { | hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::TyAlias(..) - | hir::ItemKind::OpaqueTy(..) + | hir::ItemKind::OpaqueTy(hir::OpaqueTy { + origin: hir::OpaqueTyOrigin::TyAlias, .. + }) | hir::ItemKind::Static(..) | hir::ItemKind::Trait(..) | hir::ItemKind::TraitAlias(..) => { self.add_to_current_mod(item, renamed, import_id); } + hir::ItemKind::OpaqueTy(hir::OpaqueTy { + origin: hir::OpaqueTyOrigin::AsyncFn(_) | hir::OpaqueTyOrigin::FnReturn(_), + .. + }) => { + // return-position impl traits are never nameable, and should never be documented. + } hir::ItemKind::Const(..) => { // Underscore constants do not correspond to a nameable item and // so are never useful in documentation. diff --git a/src/llvm-project b/src/llvm-project -Subproject 2b9c52f66815bb8d6ea74a4b26df3410602be9b +Subproject 585a6eb3ebf7c40fd7c1b23e3ece557b3cc2aa3 diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs index 327e090d38b..0bb1775aae9 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -122,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { let sized_trait = need!(cx.tcx.lang_items().sized_trait()); - let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter()) + let preds = traits::elaborate(cx.tcx, cx.param_env.caller_bounds().iter()) .filter(|p| !p.is_global()) .filter_map(|pred| { // Note that we do not want to deal with qualified predicates here. diff --git a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs index 44bf824aa0e..11b908e7e53 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet; -use rustc_ast::ast::{Item, ItemKind, Ty, TyKind}; +use rustc_ast::ast::{Item, ItemKind, Ty, TyKind, StaticItem, ConstItem}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -100,13 +100,13 @@ impl EarlyLintPass for RedundantStaticLifetimes { } if !item.span.from_expansion() { - if let ItemKind::Const(_, ref var_type, _) = item.kind { + if let ItemKind::Const(box ConstItem { ty: ref var_type, .. }) = item.kind { Self::visit_type(var_type, cx, "constants have by default a `'static` lifetime"); // Don't check associated consts because `'static` cannot be elided on those (issue // #2438) } - if let ItemKind::Static(ref var_type, _, _) = item.kind { + if let ItemKind::Static(box StaticItem { ty: ref var_type,.. }) = item.kind { Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime"); } } diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs index d2dedc20439..c5b58b0c060 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs @@ -286,8 +286,8 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { match (l, r) { (ExternCrate(l), ExternCrate(r)) => l == r, (Use(l), Use(r)) => eq_use_tree(l, r), - (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), - (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Static(box ast::StaticItem { ty: lt, mutability: lm, expr: le}), Static(box ast::StaticItem { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re} )) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { defaultness: ld, @@ -451,7 +451,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { use AssocItemKind::*; match (l, r) { - (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re})) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { defaultness: ld, diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 619aa9f4bf6..9051cf51658 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -2104,7 +2104,7 @@ pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool { .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None }); traits::impossible_predicates( cx.tcx, - traits::elaborate_predicates(cx.tcx, predicates) + traits::elaborate(cx.tcx, predicates) .collect::<Vec<_>>(), ) } diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index d66640ba0b7..354b6d71aa4 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -301,13 +301,13 @@ fn check_terminator<'tcx>( | TerminatorKind::Goto { .. } | TerminatorKind::Return | TerminatorKind::Resume + | TerminatorKind::Terminate | TerminatorKind::Unreachable => Ok(()), TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body), TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body), - TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())), TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => { Err((span, "const fn generators are unstable".into())) }, @@ -318,7 +318,7 @@ fn check_terminator<'tcx>( from_hir_call: _, destination: _, target: _, - cleanup: _, + unwind: _, fn_span: _, } => { let fn_ty = func.ty(body, tcx); @@ -361,7 +361,7 @@ fn check_terminator<'tcx>( expected: _, msg: _, target: _, - cleanup: _, + unwind: _, } => check_operand(tcx, cond, span, body), TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())), diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 28c045f8382..98b27a5c6b6 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -8,107 +8,84 @@ use std::process::Command; use std::str::FromStr; use crate::util::{add_dylib_path, PathBufExt}; -use lazycell::LazyCell; +use lazycell::AtomicLazyCell; +use serde::de::{Deserialize, Deserializer, Error as _}; +use std::collections::{HashMap, HashSet}; use test::{ColorConfig, OutputFormat}; -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum Mode { - RunPassValgrind, - Pretty, - DebugInfo, - Codegen, - Rustdoc, - RustdocJson, - CodegenUnits, - Incremental, - RunMake, - Ui, - JsDocTest, - MirOpt, - Assembly, -} +macro_rules! string_enum { + ($(#[$meta:meta])* $vis:vis enum $name:ident { $($variant:ident => $repr:expr,)* }) => { + $(#[$meta])* + $vis enum $name { + $($variant,)* + } -impl Mode { - pub fn disambiguator(self) -> &'static str { - // Pretty-printing tests could run concurrently, and if they do, - // they need to keep their output segregated. - match self { - Pretty => ".pretty", - _ => "", + impl $name { + $vis const VARIANTS: &'static [Self] = &[$(Self::$variant,)*]; + $vis const STR_VARIANTS: &'static [&'static str] = &[$(Self::$variant.to_str(),)*]; + + $vis const fn to_str(&self) -> &'static str { + match self { + $(Self::$variant => $repr,)* + } + } } - } -} -impl FromStr for Mode { - type Err = (); - fn from_str(s: &str) -> Result<Mode, ()> { - match s { - "run-pass-valgrind" => Ok(RunPassValgrind), - "pretty" => Ok(Pretty), - "debuginfo" => Ok(DebugInfo), - "codegen" => Ok(Codegen), - "rustdoc" => Ok(Rustdoc), - "rustdoc-json" => Ok(RustdocJson), - "codegen-units" => Ok(CodegenUnits), - "incremental" => Ok(Incremental), - "run-make" => Ok(RunMake), - "ui" => Ok(Ui), - "js-doc-test" => Ok(JsDocTest), - "mir-opt" => Ok(MirOpt), - "assembly" => Ok(Assembly), - _ => Err(()), + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.to_str(), f) + } } - } -} -impl fmt::Display for Mode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match *self { - RunPassValgrind => "run-pass-valgrind", - Pretty => "pretty", - DebugInfo => "debuginfo", - Codegen => "codegen", - Rustdoc => "rustdoc", - RustdocJson => "rustdoc-json", - CodegenUnits => "codegen-units", - Incremental => "incremental", - RunMake => "run-make", - Ui => "ui", - JsDocTest => "js-doc-test", - MirOpt => "mir-opt", - Assembly => "assembly", - }; - fmt::Display::fmt(s, f) + impl FromStr for $name { + type Err = (); + + fn from_str(s: &str) -> Result<Self, ()> { + match s { + $($repr => Ok(Self::$variant),)* + _ => Err(()), + } + } + } } } -#[derive(Clone, Copy, PartialEq, Debug, Hash)] -pub enum PassMode { - Check, - Build, - Run, +string_enum! { + #[derive(Clone, Copy, PartialEq, Debug)] + pub enum Mode { + RunPassValgrind => "run-pass-valgrind", + Pretty => "pretty", + DebugInfo => "debuginfo", + Codegen => "codegen", + Rustdoc => "rustdoc", + RustdocJson => "rustdoc-json", + CodegenUnits => "codegen-units", + Incremental => "incremental", + RunMake => "run-make", + Ui => "ui", + JsDocTest => "js-doc-test", + MirOpt => "mir-opt", + Assembly => "assembly", + } } -impl FromStr for PassMode { - type Err = (); - fn from_str(s: &str) -> Result<Self, ()> { - match s { - "check" => Ok(PassMode::Check), - "build" => Ok(PassMode::Build), - "run" => Ok(PassMode::Run), - _ => Err(()), +impl Mode { + pub fn disambiguator(self) -> &'static str { + // Pretty-printing tests could run concurrently, and if they do, + // they need to keep their output segregated. + match self { + Pretty => ".pretty", + _ => "", } } } -impl fmt::Display for PassMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match *self { - PassMode::Check => "check", - PassMode::Build => "build", - PassMode::Run => "run", - }; - fmt::Display::fmt(s, f) +string_enum! { + #[derive(Clone, Copy, PartialEq, Debug, Hash)] + pub enum PassMode { + Check => "check", + Build => "build", + Run => "run", } } @@ -119,63 +96,30 @@ pub enum FailMode { Run, } -#[derive(Clone, Debug, PartialEq)] -pub enum CompareMode { - Polonius, - Chalk, - NextSolver, - SplitDwarf, - SplitDwarfSingle, -} - -impl CompareMode { - pub(crate) fn to_str(&self) -> &'static str { - match *self { - CompareMode::Polonius => "polonius", - CompareMode::Chalk => "chalk", - CompareMode::NextSolver => "next-solver", - CompareMode::SplitDwarf => "split-dwarf", - CompareMode::SplitDwarfSingle => "split-dwarf-single", - } +string_enum! { + #[derive(Clone, Debug, PartialEq)] + pub enum CompareMode { + Polonius => "polonius", + Chalk => "chalk", + NextSolver => "next-solver", + SplitDwarf => "split-dwarf", + SplitDwarfSingle => "split-dwarf-single", } - - pub fn parse(s: String) -> CompareMode { - match s.as_str() { - "polonius" => CompareMode::Polonius, - "chalk" => CompareMode::Chalk, - "next-solver" => CompareMode::NextSolver, - "split-dwarf" => CompareMode::SplitDwarf, - "split-dwarf-single" => CompareMode::SplitDwarfSingle, - x => panic!("unknown --compare-mode option: {}", x), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Debugger { - Cdb, - Gdb, - Lldb, } -impl Debugger { - fn to_str(&self) -> &'static str { - match self { - Debugger::Cdb => "cdb", - Debugger::Gdb => "gdb", - Debugger::Lldb => "lldb", - } +string_enum! { + #[derive(Clone, Copy, Debug, PartialEq)] + pub enum Debugger { + Cdb => "cdb", + Gdb => "gdb", + Lldb => "lldb", } } -impl fmt::Display for Debugger { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.to_str(), f) - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Default, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] pub enum PanicStrategy { + #[default] Unwind, Abort, } @@ -383,7 +327,7 @@ pub struct Config { /// Only rerun the tests that result has been modified accoring to Git status pub only_modified: bool, - pub target_cfg: LazyCell<TargetCfg>, + pub target_cfgs: AtomicLazyCell<TargetCfgs>, pub nocapture: bool, } @@ -396,8 +340,18 @@ impl Config { }) } - fn target_cfg(&self) -> &TargetCfg { - self.target_cfg.borrow_with(|| TargetCfg::new(self)) + pub fn target_cfgs(&self) -> &TargetCfgs { + match self.target_cfgs.borrow() { + Some(cfgs) => cfgs, + None => { + let _ = self.target_cfgs.fill(TargetCfgs::new(self)); + self.target_cfgs.borrow().unwrap() + } + } + } + + pub fn target_cfg(&self) -> &TargetCfg { + &self.target_cfgs().current } pub fn matches_arch(&self, arch: &str) -> bool { @@ -449,94 +403,154 @@ impl Config { } } -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] +pub struct TargetCfgs { + pub current: TargetCfg, + pub all_targets: HashSet<String>, + pub all_archs: HashSet<String>, + pub all_oses: HashSet<String>, + pub all_oses_and_envs: HashSet<String>, + pub all_envs: HashSet<String>, + pub all_abis: HashSet<String>, + pub all_families: HashSet<String>, + pub all_pointer_widths: HashSet<String>, +} + +impl TargetCfgs { + fn new(config: &Config) -> TargetCfgs { + let targets: HashMap<String, TargetCfg> = if config.stage_id.starts_with("stage0-") { + // #[cfg(bootstrap)] + // Needed only for one cycle, remove during the bootstrap bump. + Self::collect_all_slow(config) + } else { + serde_json::from_str(&rustc_output( + config, + &["--print=all-target-specs-json", "-Zunstable-options"], + )) + .unwrap() + }; + + let mut current = None; + let mut all_targets = HashSet::new(); + let mut all_archs = HashSet::new(); + let mut all_oses = HashSet::new(); + let mut all_oses_and_envs = HashSet::new(); + let mut all_envs = HashSet::new(); + let mut all_abis = HashSet::new(); + let mut all_families = HashSet::new(); + let mut all_pointer_widths = HashSet::new(); + + for (target, cfg) in targets.into_iter() { + all_archs.insert(cfg.arch.clone()); + all_oses.insert(cfg.os.clone()); + all_oses_and_envs.insert(cfg.os_and_env()); + all_envs.insert(cfg.env.clone()); + all_abis.insert(cfg.abi.clone()); + for family in &cfg.families { + all_families.insert(family.clone()); + } + all_pointer_widths.insert(format!("{}bit", cfg.pointer_width)); + + if target == config.target { + current = Some(cfg); + } + all_targets.insert(target.into()); + } + + Self { + current: current.expect("current target not found"), + all_targets, + all_archs, + all_oses, + all_oses_and_envs, + all_envs, + all_abis, + all_families, + all_pointer_widths, + } + } + + // #[cfg(bootstrap)] + // Needed only for one cycle, remove during the bootstrap bump. + fn collect_all_slow(config: &Config) -> HashMap<String, TargetCfg> { + let mut result = HashMap::new(); + for target in rustc_output(config, &["--print=target-list"]).trim().lines() { + let json = rustc_output( + config, + &["--print=target-spec-json", "-Zunstable-options", "--target", target], + ); + match serde_json::from_str(&json) { + Ok(res) => { + result.insert(target.into(), res); + } + Err(err) => panic!("failed to parse target spec for {target}: {err}"), + } + } + result + } +} + +#[derive(Clone, Debug, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] pub struct TargetCfg { - arch: String, - os: String, - env: String, - abi: String, - families: Vec<String>, - pointer_width: u32, + pub(crate) arch: String, + #[serde(default = "default_os")] + pub(crate) os: String, + #[serde(default)] + pub(crate) env: String, + #[serde(default)] + pub(crate) abi: String, + #[serde(rename = "target-family", default)] + pub(crate) families: Vec<String>, + #[serde(rename = "target-pointer-width", deserialize_with = "serde_parse_u32")] + pub(crate) pointer_width: u32, + #[serde(rename = "target-endian", default)] endian: Endian, + #[serde(rename = "panic-strategy", default)] panic: PanicStrategy, } -#[derive(Eq, PartialEq, Clone, Debug)] +impl TargetCfg { + pub(crate) fn os_and_env(&self) -> String { + format!("{}-{}", self.os, self.env) + } +} + +fn default_os() -> String { + "none".into() +} + +#[derive(Eq, PartialEq, Clone, Debug, Default, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] pub enum Endian { + #[default] Little, Big, } -impl TargetCfg { - fn new(config: &Config) -> TargetCfg { - let mut command = Command::new(&config.rustc_path); - add_dylib_path(&mut command, iter::once(&config.compile_lib_path)); - let output = match command - .arg("--print=cfg") - .arg("--target") - .arg(&config.target) - .args(&config.target_rustcflags) - .output() - { - Ok(output) => output, - Err(e) => panic!("error: failed to get cfg info from {:?}: {e}", config.rustc_path), - }; - if !output.status.success() { - panic!( - "error: failed to get cfg info from {:?}\n--- stdout\n{}\n--- stderr\n{}", - config.rustc_path, - String::from_utf8(output.stdout).unwrap(), - String::from_utf8(output.stderr).unwrap(), - ); - } - let print_cfg = String::from_utf8(output.stdout).unwrap(); - let mut arch = None; - let mut os = None; - let mut env = None; - let mut abi = None; - let mut families = Vec::new(); - let mut pointer_width = None; - let mut endian = None; - let mut panic = None; - for line in print_cfg.lines() { - if let Some((name, value)) = line.split_once('=') { - let value = value.trim_matches('"'); - match name { - "target_arch" => arch = Some(value), - "target_os" => os = Some(value), - "target_env" => env = Some(value), - "target_abi" => abi = Some(value), - "target_family" => families.push(value.to_string()), - "target_pointer_width" => pointer_width = Some(value.parse().unwrap()), - "target_endian" => { - endian = Some(match value { - "little" => Endian::Little, - "big" => Endian::Big, - s => panic!("unexpected {s}"), - }) - } - "panic" => { - panic = match value { - "abort" => Some(PanicStrategy::Abort), - "unwind" => Some(PanicStrategy::Unwind), - s => panic!("unexpected {s}"), - } - } - _ => {} - } - } - } - TargetCfg { - arch: arch.unwrap().to_string(), - os: os.unwrap().to_string(), - env: env.unwrap().to_string(), - abi: abi.unwrap().to_string(), - families, - pointer_width: pointer_width.unwrap(), - endian: endian.unwrap(), - panic: panic.unwrap(), - } +fn rustc_output(config: &Config, args: &[&str]) -> String { + let mut command = Command::new(&config.rustc_path); + add_dylib_path(&mut command, iter::once(&config.compile_lib_path)); + command.args(&config.target_rustcflags).args(args); + command.env("RUSTC_BOOTSTRAP", "1"); + + let output = match command.output() { + Ok(output) => output, + Err(e) => panic!("error: failed to run {command:?}: {e}"), + }; + if !output.status.success() { + panic!( + "error: failed to run {command:?}\n--- stdout\n{}\n--- stderr\n{}", + String::from_utf8(output.stdout).unwrap(), + String::from_utf8(output.stderr).unwrap(), + ); } + String::from_utf8(output.stdout).unwrap() +} + +fn serde_parse_u32<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u32, D::Error> { + let string = String::deserialize(deserializer)?; + string.parse().map_err(D::Error::custom) } #[derive(Debug, Clone)] diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 5bc9d9afcb9..a7efe16150e 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -6,24 +6,19 @@ use std::io::BufReader; use std::path::{Path, PathBuf}; use std::process::Command; +use build_helper::ci::CiEnv; use tracing::*; -use crate::common::{CompareMode, Config, Debugger, FailMode, Mode, PassMode}; +use crate::common::{Config, Debugger, FailMode, Mode, PassMode}; +use crate::header::cfg::parse_cfg_name_directive; +use crate::header::cfg::MatchOutcome; use crate::util; use crate::{extract_cdb_version, extract_gdb_version}; +mod cfg; #[cfg(test)] mod tests; -/// The result of parse_cfg_name_directive. -#[derive(Clone, Copy, PartialEq, Debug)] -enum ParsedNameDirective { - /// No match. - NoMatch, - /// Match. - Match, -} - /// Properties which must be known very early, before actually running /// the test. #[derive(Default)] @@ -150,6 +145,8 @@ pub struct TestProps { pub normalize_stdout: Vec<(String, String)>, pub normalize_stderr: Vec<(String, String)>, pub failure_status: i32, + // For UI tests, allows compiler to exit with arbitrary failure status + pub dont_check_failure_status: bool, // Whether or not `rustfix` should apply the `CodeSuggestion`s of this test and compile the // resulting Rust code. pub run_rustfix: bool, @@ -192,6 +189,7 @@ mod directives { pub const CHECK_TEST_LINE_NUMBERS_MATCH: &'static str = "check-test-line-numbers-match"; pub const IGNORE_PASS: &'static str = "ignore-pass"; pub const FAILURE_STATUS: &'static str = "failure-status"; + pub const DONT_CHECK_FAILURE_STATUS: &'static str = "dont-check-failure-status"; pub const RUN_RUSTFIX: &'static str = "run-rustfix"; pub const RUSTFIX_ONLY_MACHINE_APPLICABLE: &'static str = "rustfix-only-machine-applicable"; pub const ASSEMBLY_OUTPUT: &'static str = "assembly-output"; @@ -239,6 +237,7 @@ impl TestProps { normalize_stdout: vec![], normalize_stderr: vec![], failure_status: -1, + dont_check_failure_status: false, run_rustfix: false, rustfix_only_machine_applicable: false, assembly_output: None, @@ -278,8 +277,12 @@ impl TestProps { /// `//[foo]`), then the property is ignored unless `cfg` is /// `Some("foo")`. fn load_from(&mut self, testfile: &Path, cfg: Option<&str>, config: &Config) { - // Mode-dependent defaults. - self.remap_src_base = config.mode == Mode::Ui && !config.suite.contains("rustdoc"); + // In CI, we've sometimes encountered non-determinism related to truncating very long paths. + // Set a consistent (short) prefix to avoid issues, but only in CI to avoid regressing the + // contributor experience. + if CiEnv::is_ci() { + self.remap_src_base = config.mode == Mode::Ui && !config.suite.contains("rustdoc"); + } let mut has_edition = false; if !testfile.is_dir() { @@ -401,6 +404,12 @@ impl TestProps { self.failure_status = code; } + config.set_name_directive( + ln, + DONT_CHECK_FAILURE_STATUS, + &mut self.dont_check_failure_status, + ); + config.set_name_directive(ln, RUN_RUSTFIX, &mut self.run_rustfix); config.set_name_directive( ln, @@ -647,7 +656,7 @@ impl Config { } fn parse_custom_normalization(&self, mut line: &str, prefix: &str) -> Option<(String, String)> { - if self.parse_cfg_name_directive(line, prefix) == ParsedNameDirective::Match { + if parse_cfg_name_directive(self, line, prefix).outcome == MatchOutcome::Match { let from = parse_normalization_string(&mut line)?; let to = parse_normalization_string(&mut line)?; Some((from, to)) @@ -664,68 +673,6 @@ impl Config { self.parse_name_directive(line, "needs-profiler-support") } - /// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86` - /// or `normalize-stderr-32bit`. - fn parse_cfg_name_directive(&self, line: &str, prefix: &str) -> ParsedNameDirective { - if !line.as_bytes().starts_with(prefix.as_bytes()) { - return ParsedNameDirective::NoMatch; - } - if line.as_bytes().get(prefix.len()) != Some(&b'-') { - return ParsedNameDirective::NoMatch; - } - - let name = line[prefix.len() + 1..].split(&[':', ' '][..]).next().unwrap(); - - let matches_pointer_width = || { - name.strip_suffix("bit") - .and_then(|width| width.parse::<u32>().ok()) - .map(|width| self.get_pointer_width() == width) - .unwrap_or(false) - }; - - // If something is ignored for emscripten, it likely also needs to be - // ignored for wasm32-unknown-unknown. - // `wasm32-bare` is an alias to refer to just wasm32-unknown-unknown - // (in contrast to `wasm32` which also matches non-bare targets like - // asmjs-unknown-emscripten). - let matches_wasm32_alias = || { - self.target == "wasm32-unknown-unknown" && matches!(name, "emscripten" | "wasm32-bare") - }; - - let is_match = name == "test" || - self.target == name || // triple - self.matches_os(name) || - self.matches_env(name) || - self.matches_abi(name) || - self.matches_family(name) || - self.target.ends_with(name) || // target and env - self.matches_arch(name) || - matches_wasm32_alias() || - matches_pointer_width() || - name == self.stage_id.split('-').next().unwrap() || // stage - name == self.channel || // channel - (self.target != self.host && name == "cross-compile") || - (name == "endian-big" && self.is_big_endian()) || - (self.remote_test_client.is_some() && name == "remote") || - match self.compare_mode { - Some(CompareMode::Polonius) => name == "compare-mode-polonius", - Some(CompareMode::Chalk) => name == "compare-mode-chalk", - Some(CompareMode::NextSolver) => name == "compare-mode-next-solver", - Some(CompareMode::SplitDwarf) => name == "compare-mode-split-dwarf", - Some(CompareMode::SplitDwarfSingle) => name == "compare-mode-split-dwarf-single", - None => false, - } || - (cfg!(debug_assertions) && name == "debug") || - match self.debugger { - Some(Debugger::Cdb) => name == "cdb", - Some(Debugger::Gdb) => name == "gdb", - Some(Debugger::Lldb) => name == "lldb", - None => false, - }; - - if is_match { ParsedNameDirective::Match } else { ParsedNameDirective::NoMatch } - } - fn has_cfg_prefix(&self, line: &str, prefix: &str) -> bool { // returns whether this line contains this prefix or not. For prefix // "ignore", returns true if line says "ignore-x86_64", "ignore-arch", @@ -992,21 +939,44 @@ pub fn make_test_description<R: Read>( } }; } - ignore = match config.parse_cfg_name_directive(ln, "ignore") { - ParsedNameDirective::Match => { - ignore_message = Some("cfg -> ignore => Match"); - true - } - ParsedNameDirective::NoMatch => ignore, - }; + + { + let parsed = parse_cfg_name_directive(config, ln, "ignore"); + ignore = match parsed.outcome { + MatchOutcome::Match => { + let reason = parsed.pretty_reason.unwrap(); + // The ignore reason must be a &'static str, so we have to leak memory to + // create it. This is fine, as the header is parsed only at the start of + // compiletest so it won't grow indefinitely. + ignore_message = Some(Box::leak(Box::<str>::from(match parsed.comment { + Some(comment) => format!("ignored {reason} ({comment})"), + None => format!("ignored {reason}"), + })) as &str); + true + } + MatchOutcome::NoMatch => ignore, + MatchOutcome::External => ignore, + MatchOutcome::Invalid => panic!("invalid line in {}: {ln}", path.display()), + }; + } if config.has_cfg_prefix(ln, "only") { - ignore = match config.parse_cfg_name_directive(ln, "only") { - ParsedNameDirective::Match => ignore, - ParsedNameDirective::NoMatch => { - ignore_message = Some("cfg -> only => NoMatch"); + let parsed = parse_cfg_name_directive(config, ln, "only"); + ignore = match parsed.outcome { + MatchOutcome::Match => ignore, + MatchOutcome::NoMatch => { + let reason = parsed.pretty_reason.unwrap(); + // The ignore reason must be a &'static str, so we have to leak memory to + // create it. This is fine, as the header is parsed only at the start of + // compiletest so it won't grow indefinitely. + ignore_message = Some(Box::leak(Box::<str>::from(match parsed.comment { + Some(comment) => format!("only executed {reason} ({comment})"), + None => format!("only executed {reason}"), + })) as &str); true } + MatchOutcome::External => ignore, + MatchOutcome::Invalid => panic!("invalid line in {}: {ln}", path.display()), }; } diff --git a/src/tools/compiletest/src/header/cfg.rs b/src/tools/compiletest/src/header/cfg.rs new file mode 100644 index 00000000000..3b9333dfe7a --- /dev/null +++ b/src/tools/compiletest/src/header/cfg.rs @@ -0,0 +1,320 @@ +use crate::common::{CompareMode, Config, Debugger}; +use std::collections::HashSet; + +const EXTRA_ARCHS: &[&str] = &["spirv"]; + +/// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86` +/// or `normalize-stderr-32bit`. +pub(super) fn parse_cfg_name_directive<'a>( + config: &Config, + line: &'a str, + prefix: &str, +) -> ParsedNameDirective<'a> { + if !line.as_bytes().starts_with(prefix.as_bytes()) { + return ParsedNameDirective::invalid(); + } + if line.as_bytes().get(prefix.len()) != Some(&b'-') { + return ParsedNameDirective::invalid(); + } + let line = &line[prefix.len() + 1..]; + + let (name, comment) = + line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None)); + + // Some of the matchers might be "" depending on what the target information is. To avoid + // problems we outright reject empty directives. + if name == "" { + return ParsedNameDirective::invalid(); + } + + let mut outcome = MatchOutcome::Invalid; + let mut message = None; + + macro_rules! condition { + ( + name: $name:expr, + $(allowed_names: $allowed_names:expr,)? + $(condition: $condition:expr,)? + message: $($message:tt)* + ) => {{ + // This is not inlined to avoid problems with macro repetitions. + let format_message = || format!($($message)*); + + if outcome != MatchOutcome::Invalid { + // Ignore all other matches if we already found one + } else if $name.custom_matches(name) { + message = Some(format_message()); + if true $(&& $condition)? { + outcome = MatchOutcome::Match; + } else { + outcome = MatchOutcome::NoMatch; + } + } + $(else if $allowed_names.custom_contains(name) { + message = Some(format_message()); + outcome = MatchOutcome::NoMatch; + })? + }}; + } + + let target_cfgs = config.target_cfgs(); + let target_cfg = config.target_cfg(); + + condition! { + name: "test", + message: "always" + } + condition! { + name: &config.target, + allowed_names: &target_cfgs.all_targets, + message: "when the target is {name}" + } + condition! { + name: &[ + Some(&*target_cfg.os), + // If something is ignored for emscripten, it likely also needs to be + // ignored for wasm32-unknown-unknown. + (config.target == "wasm32-unknown-unknown").then_some("emscripten"), + ], + allowed_names: &target_cfgs.all_oses, + message: "when the operative system is {name}" + } + condition! { + name: &target_cfg.env, + allowed_names: &target_cfgs.all_envs, + message: "when the target environment is {name}" + } + condition! { + name: &target_cfg.os_and_env(), + allowed_names: &target_cfgs.all_oses_and_envs, + message: "when the operative system and target environment are {name}" + } + condition! { + name: &target_cfg.abi, + allowed_names: &target_cfgs.all_abis, + message: "when the ABI is {name}" + } + condition! { + name: &target_cfg.arch, + allowed_names: ContainsEither { a: &target_cfgs.all_archs, b: &EXTRA_ARCHS }, + message: "when the architecture is {name}" + } + condition! { + name: format!("{}bit", target_cfg.pointer_width), + allowed_names: &target_cfgs.all_pointer_widths, + message: "when the pointer width is {name}" + } + condition! { + name: &*target_cfg.families, + allowed_names: &target_cfgs.all_families, + message: "when the target family is {name}" + } + + // `wasm32-bare` is an alias to refer to just wasm32-unknown-unknown + // (in contrast to `wasm32` which also matches non-bare targets like + // asmjs-unknown-emscripten). + condition! { + name: "wasm32-bare", + condition: config.target == "wasm32-unknown-unknown", + message: "when the target is WASM" + } + + condition! { + name: "asmjs", + condition: config.target.starts_with("asmjs"), + message: "when the architecture is asm.js", + } + condition! { + name: "thumb", + condition: config.target.starts_with("thumb"), + message: "when the architecture is part of the Thumb family" + } + + condition! { + name: &config.channel, + allowed_names: &["stable", "beta", "nightly"], + message: "when the release channel is {name}", + } + condition! { + name: "cross-compile", + condition: config.target != config.host, + message: "when cross-compiling" + } + condition! { + name: "endian-big", + condition: config.is_big_endian(), + message: "on big-endian targets", + } + condition! { + name: config.stage_id.split('-').next().unwrap(), + allowed_names: &["stage0", "stage1", "stage2"], + message: "when the bootstrapping stage is {name}", + } + condition! { + name: "remote", + condition: config.remote_test_client.is_some(), + message: "when running tests remotely", + } + condition! { + name: "debug", + condition: cfg!(debug_assertions), + message: "when building with debug assertions", + } + condition! { + name: config.debugger.as_ref().map(|d| d.to_str()), + allowed_names: &Debugger::STR_VARIANTS, + message: "when the debugger is {name}", + } + condition! { + name: config.compare_mode + .as_ref() + .map(|d| format!("compare-mode-{}", d.to_str())), + allowed_names: ContainsPrefixed { + prefix: "compare-mode-", + inner: CompareMode::STR_VARIANTS, + }, + message: "when comparing with {name}", + } + + if prefix == "ignore" && outcome == MatchOutcome::Invalid { + // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest. + if name.starts_with("tidy-") { + outcome = MatchOutcome::External; + } + + // Don't error out for ignore-pass, as that is handled elsewhere. + if name == "pass" { + outcome = MatchOutcome::External; + } + + // Don't error out for ignore-llvm-version, that has a custom syntax and is handled + // elsewhere. + if name == "llvm-version" { + outcome = MatchOutcome::External; + } + + // Don't error out for ignore-llvm-version, that has a custom syntax and is handled + // elsewhere. + if name == "gdb-version" { + outcome = MatchOutcome::External; + } + } + + ParsedNameDirective { + name: Some(name), + comment: comment.map(|c| c.trim().trim_start_matches('-').trim()), + outcome, + pretty_reason: message, + } +} + +/// The result of parse_cfg_name_directive. +#[derive(Clone, PartialEq, Debug)] +pub(super) struct ParsedNameDirective<'a> { + pub(super) name: Option<&'a str>, + pub(super) pretty_reason: Option<String>, + pub(super) comment: Option<&'a str>, + pub(super) outcome: MatchOutcome, +} + +impl ParsedNameDirective<'_> { + fn invalid() -> Self { + Self { name: None, pretty_reason: None, comment: None, outcome: MatchOutcome::NoMatch } + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub(super) enum MatchOutcome { + /// No match. + NoMatch, + /// Match. + Match, + /// The directive was invalid. + Invalid, + /// The directive is handled by other parts of our tooling. + External, +} + +trait CustomContains { + fn custom_contains(&self, item: &str) -> bool; +} + +impl CustomContains for HashSet<String> { + fn custom_contains(&self, item: &str) -> bool { + self.contains(item) + } +} + +impl CustomContains for &[&str] { + fn custom_contains(&self, item: &str) -> bool { + self.contains(&item) + } +} + +impl<const N: usize> CustomContains for [&str; N] { + fn custom_contains(&self, item: &str) -> bool { + self.contains(&item) + } +} + +struct ContainsPrefixed<T: CustomContains> { + prefix: &'static str, + inner: T, +} + +impl<T: CustomContains> CustomContains for ContainsPrefixed<T> { + fn custom_contains(&self, item: &str) -> bool { + match item.strip_prefix(self.prefix) { + Some(stripped) => self.inner.custom_contains(stripped), + None => false, + } + } +} + +struct ContainsEither<'a, A: CustomContains, B: CustomContains> { + a: &'a A, + b: &'a B, +} + +impl<A: CustomContains, B: CustomContains> CustomContains for ContainsEither<'_, A, B> { + fn custom_contains(&self, item: &str) -> bool { + self.a.custom_contains(item) || self.b.custom_contains(item) + } +} + +trait CustomMatches { + fn custom_matches(&self, name: &str) -> bool; +} + +impl CustomMatches for &str { + fn custom_matches(&self, name: &str) -> bool { + name == *self + } +} + +impl CustomMatches for String { + fn custom_matches(&self, name: &str) -> bool { + name == self + } +} + +impl<T: CustomMatches> CustomMatches for &[T] { + fn custom_matches(&self, name: &str) -> bool { + self.iter().any(|m| m.custom_matches(name)) + } +} + +impl<const N: usize, T: CustomMatches> CustomMatches for [T; N] { + fn custom_matches(&self, name: &str) -> bool { + self.iter().any(|m| m.custom_matches(name)) + } +} + +impl<T: CustomMatches> CustomMatches for Option<T> { + fn custom_matches(&self, name: &str) -> bool { + match self { + Some(inner) => inner.custom_matches(name), + None => false, + } + } +} diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index e42b8c52408..acd588d7fee 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -47,7 +47,7 @@ fn config() -> Config { "--src-base=", "--build-base=", "--sysroot-base=", - "--stage-id=stage2", + "--stage-id=stage2-x86_64-unknown-linux-gnu", "--cc=c", "--cxx=c++", "--cflags=", @@ -174,7 +174,7 @@ fn ignore_target() { assert!(check_ignore(&config, "// ignore-gnu")); assert!(check_ignore(&config, "// ignore-64bit")); - assert!(!check_ignore(&config, "// ignore-i686")); + assert!(!check_ignore(&config, "// ignore-x86")); assert!(!check_ignore(&config, "// ignore-windows")); assert!(!check_ignore(&config, "// ignore-msvc")); assert!(!check_ignore(&config, "// ignore-32bit")); @@ -200,7 +200,7 @@ fn only_target() { #[test] fn stage() { let mut config = config(); - config.stage_id = "stage1".to_owned(); + config.stage_id = "stage1-x86_64-unknown-linux-gnu".to_owned(); assert!(check_ignore(&config, "// ignore-stage1")); assert!(!check_ignore(&config, "// ignore-stage2")); diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 7048b0e08bb..cfb1ee34f67 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -6,12 +6,12 @@ extern crate test; use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS}; -use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths}; +use crate::common::{Config, Debugger, Mode, PassMode, TestPaths}; use crate::util::logv; use build_helper::git::{get_git_modified_files, get_git_untracked_files}; use core::panic; use getopts::Options; -use lazycell::LazyCell; +use lazycell::AtomicLazyCell; use std::collections::BTreeSet; use std::ffi::OsString; use std::fs; @@ -25,6 +25,7 @@ use tracing::*; use walkdir::WalkDir; use self::header::{make_test_description, EarlyProps}; +use std::sync::Arc; #[cfg(test)] mod tests; @@ -42,7 +43,7 @@ pub mod util; fn main() { tracing_subscriber::fmt::init(); - let config = parse_config(env::args().collect()); + let config = Arc::new(parse_config(env::args().collect())); if config.valgrind_path.is_none() && config.force_valgrind { panic!("Can't find Valgrind to run Valgrind tests"); @@ -293,7 +294,9 @@ pub fn parse_config(args: Vec<String>) -> Config { only_modified: matches.opt_present("only-modified"), color, remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from), - compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse), + compare_mode: matches + .opt_str("compare-mode") + .map(|s| s.parse().expect("invalid --compare-mode provided")), rustfix_coverage: matches.opt_present("rustfix-coverage"), has_tidy, channel: matches.opt_str("channel").unwrap(), @@ -311,7 +314,7 @@ pub fn parse_config(args: Vec<String>) -> Config { force_rerun: matches.opt_present("force-rerun"), - target_cfg: LazyCell::new(), + target_cfgs: AtomicLazyCell::new(), nocapture: matches.opt_present("nocapture"), } @@ -367,7 +370,7 @@ pub fn opt_str2(maybestr: Option<String>) -> String { } } -pub fn run_tests(config: Config) { +pub fn run_tests(config: Arc<Config>) { // If we want to collect rustfix coverage information, // we first make sure that the coverage file does not exist. // It will be created later on. @@ -409,7 +412,7 @@ pub fn run_tests(config: Config) { }; let mut tests = Vec::new(); - for c in &configs { + for c in configs { let mut found_paths = BTreeSet::new(); make_tests(c, &mut tests, &mut found_paths); check_overlapping_tests(&found_paths); @@ -431,7 +434,11 @@ pub fn run_tests(config: Config) { println!( "Some tests failed in compiletest suite={}{} mode={} host={} target={}", config.suite, - config.compare_mode.map(|c| format!(" compare_mode={:?}", c)).unwrap_or_default(), + config + .compare_mode + .as_ref() + .map(|c| format!(" compare_mode={:?}", c)) + .unwrap_or_default(), config.mode, config.host, config.target @@ -451,13 +458,13 @@ pub fn run_tests(config: Config) { } } -fn configure_cdb(config: &Config) -> Option<Config> { +fn configure_cdb(config: &Config) -> Option<Arc<Config>> { config.cdb.as_ref()?; - Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() }) + Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() })) } -fn configure_gdb(config: &Config) -> Option<Config> { +fn configure_gdb(config: &Config) -> Option<Arc<Config>> { config.gdb_version?; if config.matches_env("msvc") { @@ -488,10 +495,10 @@ fn configure_gdb(config: &Config) -> Option<Config> { env::set_var("RUST_TEST_THREADS", "1"); } - Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() }) + Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() })) } -fn configure_lldb(config: &Config) -> Option<Config> { +fn configure_lldb(config: &Config) -> Option<Arc<Config>> { config.lldb_python_dir.as_ref()?; if let Some(350) = config.lldb_version { @@ -504,7 +511,7 @@ fn configure_lldb(config: &Config) -> Option<Config> { return None; } - Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() }) + Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() })) } pub fn test_opts(config: &Config) -> test::TestOpts { @@ -539,17 +546,17 @@ pub fn test_opts(config: &Config) -> test::TestOpts { } pub fn make_tests( - config: &Config, + config: Arc<Config>, tests: &mut Vec<test::TestDescAndFn>, found_paths: &mut BTreeSet<PathBuf>, ) { debug!("making tests from {:?}", config.src_base.display()); - let inputs = common_inputs_stamp(config); - let modified_tests = modified_tests(config, &config.src_base).unwrap_or_else(|err| { + let inputs = common_inputs_stamp(&config); + let modified_tests = modified_tests(&config, &config.src_base).unwrap_or_else(|err| { panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err) }); collect_tests_from_dir( - config, + config.clone(), &config.src_base, &PathBuf::new(), &inputs, @@ -620,7 +627,7 @@ fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> { } fn collect_tests_from_dir( - config: &Config, + config: Arc<Config>, dir: &Path, relative_dir_path: &Path, inputs: &Stamp, @@ -648,7 +655,7 @@ fn collect_tests_from_dir( // sequential loop because otherwise, if we do it in the // tests themselves, they race for the privilege of // creating the directories and sometimes fail randomly. - let build_dir = output_relative_path(config, relative_dir_path); + let build_dir = output_relative_path(&config, relative_dir_path); fs::create_dir_all(&build_dir).unwrap(); // Add each `.rs` file as a test, and recurse further on any @@ -664,13 +671,13 @@ fn collect_tests_from_dir( let paths = TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() }; - tests.extend(make_test(config, &paths, inputs)) + tests.extend(make_test(config.clone(), &paths, inputs)) } else if file_path.is_dir() { let relative_file_path = relative_dir_path.join(file.file_name()); if &file_name != "auxiliary" { debug!("found directory: {:?}", file_path.display()); collect_tests_from_dir( - config, + config.clone(), &file_path, &relative_file_path, inputs, @@ -699,14 +706,18 @@ pub fn is_test(file_name: &OsString) -> bool { !invalid_prefixes.iter().any(|p| file_name.starts_with(p)) } -fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> { +fn make_test( + config: Arc<Config>, + testpaths: &TestPaths, + inputs: &Stamp, +) -> Vec<test::TestDescAndFn> { let test_path = if config.mode == Mode::RunMake { // Parse directives in the Makefile testpaths.file.join("Makefile") } else { PathBuf::from(&testpaths.file) }; - let early_props = EarlyProps::from_file(config, &test_path); + let early_props = EarlyProps::from_file(&config, &test_path); // Incremental tests are special, they inherently cannot be run in parallel. // `runtest::run` will be responsible for iterating over revisions. @@ -721,19 +732,22 @@ fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test let src_file = std::fs::File::open(&test_path).expect("open test file to parse ignores"); let cfg = revision.map(|v| &**v); - let test_name = crate::make_test_name(config, testpaths, revision); - let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg); + let test_name = crate::make_test_name(&config, testpaths, revision); + let mut desc = make_test_description(&config, test_name, &test_path, src_file, cfg); // Ignore tests that already run and are up to date with respect to inputs. if !config.force_rerun { desc.ignore |= is_up_to_date( - config, + &config, testpaths, &early_props, revision.map(|s| s.as_str()), inputs, ); } - test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) } + test::TestDescAndFn { + desc, + testfn: make_test_closure(config.clone(), testpaths, revision), + } }) .collect() } @@ -867,7 +881,7 @@ fn make_test_name( } fn make_test_closure( - config: &Config, + config: Arc<Config>, testpaths: &TestPaths, revision: Option<&String>, ) -> test::TestFn { diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index a4003072310..e55c82c4b63 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -30,6 +30,7 @@ use std::iter; use std::path::{Path, PathBuf}; use std::process::{Child, Command, ExitStatus, Output, Stdio}; use std::str; +use std::sync::Arc; use glob::glob; use once_cell::sync::Lazy; @@ -96,7 +97,7 @@ pub fn get_lib_name(lib: &str, dylib: bool) -> String { } } -pub fn run(config: Config, testpaths: &TestPaths, revision: Option<&str>) { +pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) { match &*config.target { "arm-linux-androideabi" | "armv7-linux-androideabi" @@ -309,7 +310,9 @@ impl<'test> TestCx<'test> { ); } - self.check_correct_failure_status(proc_res); + if !self.props.dont_check_failure_status { + self.check_correct_failure_status(proc_res); + } } } @@ -2132,7 +2135,7 @@ impl<'test> TestCx<'test> { if let Some(ref p) = self.config.nodejs { args.push(p.clone()); } else { - self.fatal("no NodeJS binary found (--nodejs)"); + self.fatal("emscripten target requested and no NodeJS binary found (--nodejs)"); } // If this is otherwise wasm, then run tests under nodejs with our // shim @@ -2140,7 +2143,7 @@ impl<'test> TestCx<'test> { if let Some(ref p) = self.config.nodejs { args.push(p.clone()); } else { - self.fatal("no NodeJS binary found (--nodejs)"); + self.fatal("wasm32 target requested and no NodeJS binary found (--nodejs)"); } let src = self @@ -2996,6 +2999,7 @@ impl<'test> TestCx<'test> { || host.contains("freebsd") || host.contains("netbsd") || host.contains("openbsd") + || host.contains("aix") { "gmake" } else { diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 1bf1217c83b..f1ed3be2edd 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -bd991d9953625e9d51fc4fcb5e19aa9c3ea598a8 +d4be8efc6296bace5b1e165f1b34d3c6da76aa8e diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 21a413002d0..8f6ae729491 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -951,7 +951,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if this.machine.panic_on_unsupported { // message is slightly different here to make automated analysis easier let error_msg = format!("unsupported Miri functionality: {}", error_msg.as_ref()); - this.start_panic(error_msg.as_ref(), StackPopUnwind::Skip)?; + this.start_panic(error_msg.as_ref(), mir::UnwindAction::Continue)?; Ok(()) } else { throw_unsup_format!("{}", error_msg.as_ref()); diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index f95fe585a8f..5c8aba6d441 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -5,7 +5,6 @@ #![feature(io_error_more)] #![feature(variant_count)] #![feature(yeet_expr)] -#![feature(is_some_and)] #![feature(nonzero_ops)] #![feature(local_key_cell_methods)] #![feature(is_terminal)] @@ -125,10 +124,13 @@ pub use crate::tag_gc::{EvalContextExt as _, VisitTags}; /// Insert rustc arguments at the beginning of the argument list that Miri wants to be /// set per default, for maximal validation power. +/// Also disable the MIR pass that inserts an alignment check on every pointer dereference. Miri +/// does that too, and with a better error message. pub const MIRI_DEFAULT_ARGS: &[&str] = &[ "--cfg=miri", "-Zalways-encode-mir", "-Zextra-const-ub-checks", "-Zmir-emit-retag", "-Zmir-opt-level=0", + "-Zmir-enable-passes=-CheckAlignment", ]; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index cc1964de332..477d8d33ebb 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -834,7 +834,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { args: &[OpTy<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option<mir::BasicBlock>, - unwind: StackPopUnwind, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> { ecx.find_mir_or_eval_fn(instance, abi, args, dest, ret, unwind) } @@ -847,7 +847,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { args: &[OpTy<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option<mir::BasicBlock>, - _unwind: StackPopUnwind, + _unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { ecx.call_dlsym(fn_val, abi, args, dest, ret) } @@ -859,7 +859,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { args: &[OpTy<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option<mir::BasicBlock>, - unwind: StackPopUnwind, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { ecx.call_intrinsic(instance, args, dest, ret, unwind) } @@ -868,7 +868,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { fn assert_panic( ecx: &mut MiriInterpCx<'mir, 'tcx>, msg: &mir::AssertMessage<'tcx>, - unwind: Option<mir::BasicBlock>, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { ecx.assert_panic(msg, unwind) } diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 73439133af2..fcee381ff71 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -258,7 +258,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { args: &[OpTy<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option<mir::BasicBlock>, - unwind: StackPopUnwind, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> { let this = self.eval_context_mut(); let link_name = this.item_link_name(def_id); diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs index 9ecbb18ef5a..ca2c1652dc1 100644 --- a/src/tools/miri/src/shims/intrinsics/mod.rs +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -26,7 +26,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { args: &[OpTy<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option<mir::BasicBlock>, - _unwind: StackPopUnwind, + _unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index dbc48876a4b..918efda3777 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -34,7 +34,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { args: &[OpTy<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option<mir::BasicBlock>, - unwind: StackPopUnwind, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> { let this = self.eval_context_mut(); trace!("eval_fn_call: {:#?}, {:?}", instance, dest); @@ -70,7 +70,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { align_op: &OpTy<'tcx, Provenance>, dest: &PlaceTy<'tcx, Provenance>, ret: Option<mir::BasicBlock>, - unwind: StackPopUnwind, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx, bool> { let this = self.eval_context_mut(); let ret = ret.unwrap(); diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index acc97c4b8a0..18ae01a19f9 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -53,7 +53,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { abi: Abi, link_name: Symbol, args: &[OpTy<'tcx, Provenance>], - unwind: StackPopUnwind, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); @@ -106,7 +106,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { &[data.into()], None, // Directly return to caller. - StackPopCleanup::Goto { ret: Some(ret), unwind: StackPopUnwind::Skip }, + StackPopCleanup::Goto { ret: Some(ret), unwind: mir::UnwindAction::Continue }, )?; // We ourselves will return `0`, eventually (will be overwritten if we catch a panic). @@ -157,7 +157,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { &[catch_unwind.data.into(), payload.into()], None, // Directly return to caller of `try`. - StackPopCleanup::Goto { ret: Some(catch_unwind.ret), unwind: StackPopUnwind::Skip }, + StackPopCleanup::Goto { + ret: Some(catch_unwind.ret), + unwind: mir::UnwindAction::Continue, + }, )?; // We pushed a new stack frame, the engine should not do any jumping now! @@ -168,7 +171,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } /// Start a panic in the interpreter with the given message as payload. - fn start_panic(&mut self, msg: &str, unwind: StackPopUnwind) -> InterpResult<'tcx> { + fn start_panic(&mut self, msg: &str, unwind: mir::UnwindAction) -> InterpResult<'tcx> { let this = self.eval_context_mut(); // First arg: message. @@ -189,7 +192,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn assert_panic( &mut self, msg: &mir::AssertMessage<'tcx>, - unwind: Option<mir::BasicBlock>, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { use rustc_middle::mir::AssertKind::*; let this = self.eval_context_mut(); @@ -211,13 +214,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Abi::Rust, &[index.into(), len.into()], None, - StackPopCleanup::Goto { - ret: None, - unwind: match unwind { - Some(cleanup) => StackPopUnwind::Cleanup(cleanup), - None => StackPopUnwind::Skip, - }, - }, + StackPopCleanup::Goto { ret: None, unwind }, )?; } MisalignedPointerDereference { required, found } => { @@ -238,25 +235,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Abi::Rust, &[required.into(), found.into()], None, - StackPopCleanup::Goto { - ret: None, - unwind: match unwind { - Some(cleanup) => StackPopUnwind::Cleanup(cleanup), - None => StackPopUnwind::Skip, - }, - }, + StackPopCleanup::Goto { ret: None, unwind }, )?; } _ => { // Forward everything else to `panic` lang item. - this.start_panic( - msg.description(), - match unwind { - Some(cleanup) => StackPopUnwind::Cleanup(cleanup), - None => StackPopUnwind::Skip, - }, - )?; + this.start_panic(msg.description(), unwind)?; } } Ok(()) diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr index 484f703f9c1..e1631471ae2 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr @@ -1,6 +1,6 @@ thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -error: abnormal termination: the program aborted execution +error: abnormal termination: panic in a function that cannot unwind --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC | LL | / extern "C-unwind" fn nounwind() { @@ -8,7 +8,7 @@ LL | | LL | | LL | | panic!(); LL | | } - | |_^ the program aborted execution + | |_^ panic in a function that cannot unwind | = note: inside `nounwind` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr index 484f703f9c1..e1631471ae2 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr @@ -1,6 +1,6 @@ thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -error: abnormal termination: the program aborted execution +error: abnormal termination: panic in a function that cannot unwind --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC | LL | / extern "C-unwind" fn nounwind() { @@ -8,7 +8,7 @@ LL | | LL | | LL | | panic!(); LL | | } - | |_^ the program aborted execution + | |_^ panic in a function that cannot unwind | = note: inside `nounwind` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs index 554cbe09cf0..65ba3433c28 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs @@ -4,8 +4,8 @@ #[cfg_attr(any(definition, both), rustc_nounwind)] #[no_mangle] extern "C-unwind" fn nounwind() { - //~[definition]^ ERROR: abnormal termination: the program aborted execution - //~[both]^^ ERROR: abnormal termination: the program aborted execution + //~[definition]^ ERROR: abnormal termination: panic in a function that cannot unwind + //~[both]^^ ERROR: abnormal termination: panic in a function that cannot unwind panic!(); } diff --git a/src/tools/miri/tests/fail/terminate-terminator.rs b/src/tools/miri/tests/fail/terminate-terminator.rs new file mode 100644 index 00000000000..f4931659fc8 --- /dev/null +++ b/src/tools/miri/tests/fail/terminate-terminator.rs @@ -0,0 +1,27 @@ +//@compile-flags: -Zmir-opt-level=3 +// Enable MIR inlining to ensure that `TerminatorKind::Terminate` is generated +// instead of just `UnwindAction::Terminate`. + +#![feature(c_unwind)] + +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) {} +} + +#[inline(always)] +fn has_cleanup() { + //~^ ERROR: panic in a function that cannot unwind + // FIXME(nbdd0121): The error should be reported at the call site. + let _f = Foo; + panic!(); +} + +extern "C" fn panic_abort() { + has_cleanup(); +} + +fn main() { + panic_abort(); +} diff --git a/src/tools/miri/tests/fail/terminate-terminator.stderr b/src/tools/miri/tests/fail/terminate-terminator.stderr new file mode 100644 index 00000000000..c046678f73f --- /dev/null +++ b/src/tools/miri/tests/fail/terminate-terminator.stderr @@ -0,0 +1,29 @@ +warning: You have explicitly enabled MIR optimizations, overriding Miri's default which is to completely disable them. Any optimizations may hide UB that Miri would otherwise detect, and it is not necessarily possible to predict what kind of UB will be missed. If you are enabling optimizations to make Miri run faster, we advise using cfg(miri) to shrink your workload instead. The performance benefit of enabling MIR optimizations is usually marginal at best. + +thread 'main' panicked at 'explicit panic', $DIR/terminate-terminator.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: abnormal termination: panic in a function that cannot unwind + --> $DIR/terminate-terminator.rs:LL:CC + | +LL | / fn has_cleanup() { +LL | | +LL | | // FIXME(nbdd0121): The error should be reported at the call site. +LL | | let _f = Foo; +LL | | panic!(); +LL | | } + | |_^ panic in a function that cannot unwind +... +LL | has_cleanup(); + | ------------- in this inlined function call + | + = note: inside `panic_abort` at $DIR/terminate-terminator.rs:LL:CC +note: inside `main` + --> $DIR/terminate-terminator.rs:LL:CC + | +LL | panic_abort(); + | ^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error; 1 warning emitted + diff --git a/src/tools/miri/tests/fail/abort-terminator.rs b/src/tools/miri/tests/fail/unwind-action-terminate.rs index c954443a276..876b9a9ab0a 100644 --- a/src/tools/miri/tests/fail/abort-terminator.rs +++ b/src/tools/miri/tests/fail/unwind-action-terminate.rs @@ -1,7 +1,7 @@ #![feature(c_unwind)] extern "C" fn panic_abort() { - //~^ ERROR: the program aborted + //~^ ERROR: panic in a function that cannot unwind panic!() } diff --git a/src/tools/miri/tests/fail/abort-terminator.stderr b/src/tools/miri/tests/fail/unwind-action-terminate.stderr index 2d3275f6b19..52a1879cb5f 100644 --- a/src/tools/miri/tests/fail/abort-terminator.stderr +++ b/src/tools/miri/tests/fail/unwind-action-terminate.stderr @@ -1,17 +1,17 @@ -thread 'main' panicked at 'explicit panic', $DIR/abort-terminator.rs:LL:CC +thread 'main' panicked at 'explicit panic', $DIR/unwind-action-terminate.rs:LL:CC note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -error: abnormal termination: the program aborted execution - --> $DIR/abort-terminator.rs:LL:CC +error: abnormal termination: panic in a function that cannot unwind + --> $DIR/unwind-action-terminate.rs:LL:CC | LL | / extern "C" fn panic_abort() { LL | | LL | | panic!() LL | | } - | |_^ the program aborted execution + | |_^ panic in a function that cannot unwind | - = note: inside `panic_abort` at $DIR/abort-terminator.rs:LL:CC + = note: inside `panic_abort` at $DIR/unwind-action-terminate.rs:LL:CC note: inside `main` - --> $DIR/abort-terminator.rs:LL:CC + --> $DIR/unwind-action-terminate.rs:LL:CC | LL | panic_abort(); | ^^^^^^^^^^^^^ diff --git a/src/tools/miri/tests/panic/alignment-assertion.rs b/src/tools/miri/tests/panic/alignment-assertion.rs deleted file mode 100644 index 68aa19a88db..00000000000 --- a/src/tools/miri/tests/panic/alignment-assertion.rs +++ /dev/null @@ -1,9 +0,0 @@ -//@compile-flags: -Zmiri-disable-alignment-check -Cdebug-assertions=yes - -fn main() { - let mut x = [0u32; 2]; - let ptr: *mut u8 = x.as_mut_ptr().cast::<u8>(); - unsafe { - *(ptr.add(1).cast::<u32>()) = 42; - } -} diff --git a/src/tools/miri/tests/panic/alignment-assertion.stderr b/src/tools/miri/tests/panic/alignment-assertion.stderr deleted file mode 100644 index 26cf51b0cd2..00000000000 --- a/src/tools/miri/tests/panic/alignment-assertion.stderr +++ /dev/null @@ -1,2 +0,0 @@ -thread 'main' panicked at 'misaligned pointer dereference: address must be a multiple of 0x4 but is $HEX', $DIR/alignment-assertion.rs:LL:CC -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/rustbook/Cargo.toml b/src/tools/rustbook/Cargo.toml index 8286bd506bc..b9cf2617ba9 100644 --- a/src/tools/rustbook/Cargo.toml +++ b/src/tools/rustbook/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] clap = "4.0.32" -env_logger = "0.7.1" +env_logger = "0.10" [dependencies.mdbook] version = "0.4.28" diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index 25e8a024857..43779cfaecd 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -1804,13 +1804,15 @@ pub(crate) struct StaticParts<'a> { impl<'a> StaticParts<'a> { pub(crate) fn from_item(item: &'a ast::Item) -> Self { - let (defaultness, prefix, ty, mutability, expr) = match item.kind { - ast::ItemKind::Static(ref ty, mutability, ref expr) => { - (None, "static", ty, mutability, expr) - } - ast::ItemKind::Const(defaultness, ref ty, ref expr) => { - (Some(defaultness), "const", ty, ast::Mutability::Not, expr) - } + let (defaultness, prefix, ty, mutability, expr) = match &item.kind { + ast::ItemKind::Static(s) => (None, "static", &s.ty, s.mutability, &s.expr), + ast::ItemKind::Const(c) => ( + Some(c.defaultness), + "const", + &c.ty, + ast::Mutability::Not, + &c.expr, + ), _ => unreachable!(), }; StaticParts { @@ -1826,10 +1828,8 @@ impl<'a> StaticParts<'a> { } pub(crate) fn from_trait_item(ti: &'a ast::AssocItem) -> Self { - let (defaultness, ty, expr_opt) = match ti.kind { - ast::AssocItemKind::Const(defaultness, ref ty, ref expr_opt) => { - (defaultness, ty, expr_opt) - } + let (defaultness, ty, expr_opt) = match &ti.kind { + ast::AssocItemKind::Const(c) => (c.defaultness, &c.ty, &c.expr), _ => unreachable!(), }; StaticParts { @@ -1845,8 +1845,8 @@ impl<'a> StaticParts<'a> { } pub(crate) fn from_impl_item(ii: &'a ast::AssocItem) -> Self { - let (defaultness, ty, expr) = match ii.kind { - ast::AssocItemKind::Const(defaultness, ref ty, ref expr) => (defaultness, ty, expr), + let (defaultness, ty, expr) = match &ii.kind { + ast::AssocItemKind::Const(c) => (c.defaultness, &c.ty, &c.expr), _ => unreachable!(), }; StaticParts { diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml index cdf1dd36604..8c6b1eb22ec 100644 --- a/src/tools/tidy/Cargo.toml +++ b/src/tools/tidy/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" autobins = false [dependencies] -cargo_metadata = "0.14" +cargo_metadata = "0.15" cargo-platform = "0.1.2" regex = "1" miropt-test-tools = { path = "../miropt-test-tools" } diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index f582666ab28..0f08f5d0b70 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -7,8 +7,8 @@ use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; -const ENTRY_LIMIT: usize = 1000; // FIXME: The following limits should be reduced eventually. +const ENTRY_LIMIT: usize = 885; const ROOT_ENTRY_LIMIT: usize = 881; const ISSUES_ENTRY_LIMIT: usize = 1978; |
