From ded9c0e48fec47a36074fb1299fe933411d9dfa0 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:46:13 +0200 Subject: [PATCH 01/20] Stop using dlltool for generating import libraries on MinGW Older versions of ld.bfd didn't support short import libraries like the builtin implementation of rustc generates. So we worked around this by using binutils dlltool, which produces the old import library format. We have now bumped the minimum supported ld.bfd version to one which does support them, so we can drop the dlltool usage. This makes cross-compilation a bit easier and removes a bunch of code in rustc. --- .../rustc_codegen_ssa/src/back/archive.rs | 238 ++++-------------- compiler/rustc_codegen_ssa/src/diagnostics.rs | 26 -- compiler/rustc_interface/src/tests.rs | 1 - compiler/rustc_interface/src/util.rs | 26 +- compiler/rustc_session/src/options.rs | 5 +- compiler/rustc_target/src/spec/mod.rs | 2 +- src/bootstrap/src/core/build_steps/dist.rs | 2 +- src/doc/rustc/src/codegen-options/index.md | 9 +- .../src/compiler-flags/codegen-options.md | 3 +- src/tools/compiletest/src/common.rs | 8 - .../src/directives/directive_names.rs | 1 - src/tools/compiletest/src/directives/needs.rs | 28 --- src/tools/compiletest/src/directives/tests.rs | 2 - .../raw-dylib-cross-compilation/lib.rs | 23 -- .../raw-dylib-cross-compilation/rmake.rs | 41 --- .../run-make/raw-dylib-custom-dlltool/lib.rs | 10 - .../raw-dylib-custom-dlltool/output.txt | 1 - .../raw-dylib-custom-dlltool/rmake.rs | 24 -- .../raw-dylib-custom-dlltool/script.cmd | 2 - tests/run-make/raw-dylib-whitespace/main.rs | 18 -- tests/run-make/raw-dylib-whitespace/rmake.rs | 15 -- .../raw-dylib/windows/dlltool-failed.rs | 22 -- .../raw-dylib/windows/dlltool-failed.stderr | 7 - .../raw-dylib/windows/invalid-dlltool.rs | 14 -- .../raw-dylib/windows/invalid-dlltool.stderr | 4 - 25 files changed, 59 insertions(+), 473 deletions(-) delete mode 100644 tests/run-make/raw-dylib-cross-compilation/lib.rs delete mode 100644 tests/run-make/raw-dylib-cross-compilation/rmake.rs delete mode 100644 tests/run-make/raw-dylib-custom-dlltool/lib.rs delete mode 100644 tests/run-make/raw-dylib-custom-dlltool/output.txt delete mode 100644 tests/run-make/raw-dylib-custom-dlltool/rmake.rs delete mode 100644 tests/run-make/raw-dylib-custom-dlltool/script.cmd delete mode 100644 tests/run-make/raw-dylib-whitespace/main.rs delete mode 100644 tests/run-make/raw-dylib-whitespace/rmake.rs delete mode 100644 tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.rs delete mode 100644 tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.stderr delete mode 100644 tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.rs delete mode 100644 tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.stderr diff --git a/compiler/rustc_codegen_ssa/src/back/archive.rs b/compiler/rustc_codegen_ssa/src/back/archive.rs index 18b5c001f9922..ee580cf5483d6 100644 --- a/compiler/rustc_codegen_ssa/src/back/archive.rs +++ b/compiler/rustc_codegen_ssa/src/back/archive.rs @@ -1,7 +1,5 @@ use std::borrow::Cow; -use std::env; use std::error::Error; -use std::ffi::OsString; use std::fs::{self, File}; use std::io::{self, BufWriter, Write}; use std::path::{Path, PathBuf}; @@ -24,13 +22,9 @@ use tracing::trace; use super::metadata::{create_compressed_metadata_file, search_for_section}; use super::rmeta_link::{self, RmetaLinkCache}; use super::symbol_edit::{apply_edits, collect_internal_names}; -use crate::common; // Public for ArchiveBuilderBuilder::extract_bundled_libs pub use crate::diagnostics::ExtractBundledLibsError; -use crate::diagnostics::{ - ArchiveBuildFailure, DlltoolFailImportLibrary, ErrorCallingDllTool, ErrorCreatingImportLibrary, - ErrorWritingDEFFile, UnknownArchiveKind, -}; +use crate::diagnostics::{ArchiveBuildFailure, ErrorCreatingImportLibrary, UnknownArchiveKind}; /// An item to be included in an import library. /// This is a slimmed down version of `COFFShortExport` from `ar-archive-writer`. @@ -87,66 +81,57 @@ pub trait ArchiveBuilderBuilder { items: Vec, output_path: &Path, ) { - if common::is_using_dlltool(&sess.target) { - // The binutils linker used on -windows-gnu targets cannot read the import - // libraries generated by LLVM: in our attempts, the linker produced an .EXE - // that loaded but crashed with an AV upon calling one of the imported - // functions. Therefore, use binutils to create the import library instead, - // by writing a .DEF file to the temp dir and calling binutils's dlltool. - create_mingw_dll_import_lib(sess, lib_name, items, output_path); - } else { - trace!("creating import library"); - trace!(" dll_name {:#?}", lib_name); - trace!(" output_path {}", output_path.display()); - trace!( - " import names: {}", - items - .iter() - .map(|ImportLibraryItem { name, .. }| name.clone()) - .collect::>() - .join(", "), - ); - - // All import names are Rust identifiers and therefore cannot contain \0 characters. - // FIXME: when support for #[link_name] is implemented, ensure that the import names - // still don't contain any \0 characters. Also need to check that the names don't - // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved - // in definition files. - - let mut file = match fs::File::create_new(&output_path) { - Ok(file) => file, - Err(error) => sess - .dcx() - .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }), - }; + trace!("creating import library"); + trace!(" dll_name {:#?}", lib_name); + trace!(" output_path {}", output_path.display()); + trace!( + " import names: {}", + items + .iter() + .map(|ImportLibraryItem { name, .. }| name.clone()) + .collect::>() + .join(", "), + ); + + // All import names are Rust identifiers and therefore cannot contain \0 characters. + // FIXME: when support for #[link_name] is implemented, ensure that the import names + // still don't contain any \0 characters. Also need to check that the names don't + // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved + // in definition files. + + let mut file = match fs::File::create_new(&output_path) { + Ok(file) => file, + Err(error) => sess + .dcx() + .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }), + }; - let exports = - items.into_iter().map(|item| item.into_coff_short_export(sess)).collect::>(); - let machine = match &sess.target.arch { - Arch::X86_64 => MachineTypes::AMD64, - Arch::X86 => MachineTypes::I386, - Arch::AArch64 => MachineTypes::ARM64, - Arch::Arm64EC => MachineTypes::ARM64EC, - Arch::Arm => MachineTypes::ARMNT, - cpu => panic!("unsupported cpu type {cpu}"), - }; + let exports = + items.into_iter().map(|item| item.into_coff_short_export(sess)).collect::>(); + let machine = match &sess.target.arch { + Arch::X86_64 => MachineTypes::AMD64, + Arch::X86 => MachineTypes::I386, + Arch::AArch64 => MachineTypes::ARM64, + Arch::Arm64EC => MachineTypes::ARM64EC, + Arch::Arm => MachineTypes::ARMNT, + cpu => panic!("unsupported cpu type {cpu}"), + }; - if let Err(error) = ar_archive_writer::write_import_library( - &mut file, - lib_name, - &exports, - machine, - !sess.target.is_like_msvc, - // Enable compatibility with MSVC's `/WHOLEARCHIVE` flag. - // Without this flag a duplicate symbol error would be emitted - // when linking a rust staticlib using `/WHOLEARCHIVE`. - // See #129020 - true, - &[], - ) { - sess.dcx() - .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }); - } + if let Err(error) = ar_archive_writer::write_import_library( + &mut file, + lib_name, + &exports, + machine, + !sess.target.is_like_msvc, + // Enable compatibility with MSVC's `/WHOLEARCHIVE` flag. + // Without this flag a duplicate symbol error would be emitted + // when linking a rust staticlib using `/WHOLEARCHIVE`. + // See #129020 + true, + &[], + ) { + sess.dcx() + .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }); } } @@ -187,129 +172,6 @@ pub trait ArchiveBuilderBuilder { } } -fn create_mingw_dll_import_lib( - sess: &Session, - lib_name: &str, - items: Vec, - output_path: &Path, -) { - let def_file_path = output_path.with_extension("def"); - - let def_file_content = format!( - "EXPORTS\n{}", - items - .into_iter() - .map(|ImportLibraryItem { name, ordinal, .. }| { - match ordinal { - Some(n) => format!("{name} @{n} NONAME"), - None => name, - } - }) - .collect::>() - .join("\n") - ); - - match std::fs::write(&def_file_path, def_file_content) { - Ok(_) => {} - Err(e) => { - sess.dcx().emit_fatal(ErrorWritingDEFFile { error: e }); - } - }; - - // --no-leading-underscore: For the `import_name_type` feature to work, we need to be - // able to control the *exact* spelling of each of the symbols that are being imported: - // hence we don't want `dlltool` adding leading underscores automatically. - let dlltool = find_binutils_dlltool(sess); - // temp_prefix doesn't handle paths with spaces so - // use a relative path and set the current working directory - let cwd = output_path.parent().unwrap_or(output_path); - let temp_prefix = lib_name; - // dlltool target architecture args from: - // https://github.com/llvm/llvm-project-release-prs/blob/llvmorg-15.0.6/llvm/lib/ToolDrivers/llvm-dlltool/DlltoolDriver.cpp#L69 - let (dlltool_target_arch, dlltool_target_bitness) = match &sess.target.arch { - Arch::X86_64 => ("i386:x86-64", "--64"), - Arch::X86 => ("i386", "--32"), - Arch::AArch64 => ("arm64", "--64"), - Arch::Arm => ("arm", "--32"), - arch => panic!("unsupported arch {arch}"), - }; - let mut dlltool_cmd = std::process::Command::new(&dlltool); - dlltool_cmd - .arg("-d") - .arg(def_file_path) - .arg("-D") - .arg(lib_name) - .arg("-l") - .arg(&output_path) - .arg("-m") - .arg(dlltool_target_arch) - .arg("-f") - .arg(dlltool_target_bitness) - .arg("--no-leading-underscore") - .arg("--temp-prefix") - .arg(temp_prefix) - .current_dir(cwd); - - match dlltool_cmd.output() { - Err(e) => { - sess.dcx().emit_fatal(ErrorCallingDllTool { - dlltool_path: dlltool.to_string_lossy(), - error: e, - }); - } - // dlltool returns '0' on failure, so check for error output instead. - Ok(output) if !output.stderr.is_empty() => { - sess.dcx().emit_fatal(DlltoolFailImportLibrary { - dlltool_path: dlltool.to_string_lossy(), - dlltool_args: dlltool_cmd - .get_args() - .map(|arg| arg.to_string_lossy()) - .collect::>() - .join(" "), - stdout: String::from_utf8_lossy(&output.stdout), - stderr: String::from_utf8_lossy(&output.stderr), - }) - } - _ => {} - } -} - -fn find_binutils_dlltool(sess: &Session) -> OsString { - assert!(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc); - if let Some(dlltool_path) = &sess.opts.cg.dlltool { - return dlltool_path.clone().into_os_string(); - } - - let tool_name: OsString = if sess.host.options.is_like_windows { - // If we're compiling on Windows, always use "dlltool.exe". - "dlltool.exe" - } else { - // On other platforms, use the architecture-specific name. - match sess.target.arch { - Arch::X86_64 => "x86_64-w64-mingw32-dlltool", - Arch::X86 => "i686-w64-mingw32-dlltool", - Arch::AArch64 => "aarch64-w64-mingw32-dlltool", - - // For non-standard architectures (e.g., aarch32) fallback to "dlltool". - _ => "dlltool", - } - } - .into(); - - // NOTE: it's not clear how useful it is to explicitly search PATH. - for dir in env::split_paths(&env::var_os("PATH").unwrap_or_default()) { - let full_path = dir.join(&tool_name); - if full_path.is_file() { - return full_path.into_os_string(); - } - } - - // The user didn't specify the location of the dlltool binary, and we weren't able - // to find the appropriate one on the PATH. Just return the name of the tool - // and let the invocation fail with a hopefully useful error message. - tool_name -} - pub enum AddArchiveKind<'a> { Rlib(&'a mut RmetaLinkCache, /*skip*/ &'a dyn Fn(&str, ArchiveEntryKind) -> bool), Other, diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index 09faa71468bf7..f30b4850a22fc 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -1111,32 +1111,6 @@ pub struct FailedToGetLayout<'tcx> { pub err: LayoutError<'tcx>, } -#[derive(Diagnostic)] -#[diag( - "dlltool could not create import library with {$dlltool_path} {$dlltool_args}: -{$stdout} -{$stderr}" -)] -pub(crate) struct DlltoolFailImportLibrary<'a> { - pub dlltool_path: Cow<'a, str>, - pub dlltool_args: String, - pub stdout: Cow<'a, str>, - pub stderr: Cow<'a, str>, -} - -#[derive(Diagnostic)] -#[diag("error writing .DEF file: {$error}")] -pub(crate) struct ErrorWritingDEFFile { - pub error: std::io::Error, -} - -#[derive(Diagnostic)] -#[diag("error calling dlltool '{$dlltool_path}': {$error}")] -pub(crate) struct ErrorCallingDllTool<'a> { - pub dlltool_path: Cow<'a, str>, - pub error: std::io::Error, -} - #[derive(Diagnostic)] #[diag("failed to create remark directory: {$error}")] pub(crate) struct ErrorCreatingRemarkDir { diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 0d584be4ad0b0..ae852ee9d1e53 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -607,7 +607,6 @@ fn test_codegen_options_tracking_hash() { // tidy-alphabetical-start untracked!(codegen_units, Some(42)); untracked!(default_linker_libraries, true); - untracked!(dlltool, Some(PathBuf::from("custom_dlltool.exe"))); untracked!(extra_filename, String::from("extra-filename")); untracked!(incremental, Some(String::from("abc"))); // `link_arg` is omitted because it just forwards to `link_args`. diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 912f13929a349..81ec4c5100b50 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -9,7 +9,7 @@ use std::{env, thread}; use rand::{RngCore, rng}; use rustc_ast as ast; use rustc_attr_parsing::ShouldEmit; -use rustc_codegen_ssa::back::archive::{ArArchiveBuilderBuilder, ArchiveBuilderBuilder}; +use rustc_codegen_ssa::back::archive::ArArchiveBuilderBuilder; use rustc_codegen_ssa::back::link::link_binary; use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::traits::CodegenBackend; @@ -476,7 +476,7 @@ impl CodegenBackend for DummyCodegenBackend { link_binary( sess, - &DummyArchiveBuilderBuilder, + &ArArchiveBuilderBuilder, compiled_modules, crate_info, metadata, @@ -486,28 +486,6 @@ impl CodegenBackend for DummyCodegenBackend { } } -struct DummyArchiveBuilderBuilder; - -impl ArchiveBuilderBuilder for DummyArchiveBuilderBuilder { - fn new_archive_builder<'a>( - &self, - sess: &'a Session, - ) -> Box { - ArArchiveBuilderBuilder.new_archive_builder(sess) - } - - fn create_dll_import_lib( - &self, - sess: &Session, - _lib_name: &str, - _items: Vec, - output_path: &Path, - ) { - // Build an empty static library to avoid calling an external dlltool on mingw - ArArchiveBuilderBuilder.new_archive_builder(sess).build(output_path, None); - } -} - // This is used for rustdoc, but it uses similar machinery to codegen backend // loading, so we leave the code here. It is potentially useful for other tools // that want to invoke the rustc binary while linking to rustc as well. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 0e06713019f9c..7b12b88bb65b5 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2224,8 +2224,9 @@ options! { line-tables-only, limited, or full; default: 0)"), default_linker_libraries: bool = (false, parse_bool, [UNTRACKED], "allow the linker to link its default libraries (default: no)"), - dlltool: Option = (None, parse_opt_pathbuf, [UNTRACKED], - "import library generation tool (ignored except when targeting windows-gnu)"), + dlltool: () = ((), parse_ignore, [UNTRACKED], + "this option has been removed", + removed: Warn), #[rustc_lint_opt_deny_field_access("use `Session::dwarf_version` instead of this field")] dwarf_version: Option = (None, parse_opt_number, [TRACKED], "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"), diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 2d7cd049eb61c..d097333f3fc6e 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -646,7 +646,7 @@ bitflags::bitflags! { const LIBC = 1 << 1; /// libgcc/libunwind (e.g. on `windows-gnu`, `fuchsia`, `fortanix`, `gnullvm` targets) const UNWIND = 1 << 2; - /// Linker, dlltool, and their necessary libraries (e.g. on `windows-gnu` and for `rust-lld`) + /// Linker and its necessary libraries (e.g. for `rust-lld`) const LINKER = 1 << 3; /// Sanitizer runtime libraries const SANITIZERS = 1 << 4; diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 7073b334c414d..c298efdf835df 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -253,7 +253,7 @@ fn make_win_dist(plat_root: &Path, target: TargetSelection, builder: &Builder<'_ } else { "gcc.exe" }; - let target_tools = [compiler, "ld.exe", "dlltool.exe", "libwinpthread-1.dll"]; + let target_tools = [compiler, "ld.exe", "libwinpthread-1.dll"]; // Libraries necessary to link the windows-gnu toolchains. // System libraries will be preferred if they are available (see #67429). diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md index 0f85e6d00e36b..4934b66be8971 100644 --- a/src/doc/rustc/src/codegen-options/index.md +++ b/src/doc/rustc/src/codegen-options/index.md @@ -98,13 +98,6 @@ It takes one of the following values: For example, for gcc flavor linkers, this issues the `-nodefaultlibs` flag to the linker. -## dlltool - -On `windows-gnu` targets, this flag controls which dlltool `rustc` invokes to -generate import libraries when using the [`raw-dylib` link kind](../../reference/items/external-blocks.md#the-link-attribute). -It takes a path to [the dlltool executable](https://sourceware.org/binutils/docs/binutils/dlltool.html). -If this flag is not specified, a dlltool executable will be inferred based on -the host environment and target. ## dwarf-version @@ -261,7 +254,7 @@ You can pass a comma-separated list of self-contained components, individually e (`+component`) or disabled (`-component`). Currently, only the `linker` granular option is stabilized, and only on the `x86_64-unknown-linux-gnu` target: -- `linker`: toggle the usage of self-contained linker binaries (linker, dlltool, and their necessary libraries) +- `linker`: toggle the usage of self-contained linker binaries (linker and its necessary libraries) Note that only the `-linker` opt-out is stable on the `x86_64-unknown-linux-gnu` target: `+linker` is already the default on this target. diff --git a/src/doc/unstable-book/src/compiler-flags/codegen-options.md b/src/doc/unstable-book/src/compiler-flags/codegen-options.md index efde41040184a..997b04d522ad2 100644 --- a/src/doc/unstable-book/src/compiler-flags/codegen-options.md +++ b/src/doc/unstable-book/src/compiler-flags/codegen-options.md @@ -43,8 +43,7 @@ instead of those in the system. The stable boolean values for this flag are coar - `crto`: CRT objects (e.g. on `windows-gnu`, `musl`, `wasi` targets) - `libc`: libc static library (e.g. on `musl`, `wasi` targets) - `unwind`: libgcc/libunwind (e.g. on `windows-gnu`, `fuchsia`, `fortanix`, `gnullvm` targets) -- `linker`: linker, dlltool, and their necessary libraries (e.g. on `windows-gnu` and for - `rust-lld`) +- `linker`: linker and its necessary libraries (e.g. for `rust-lld`) - `sanitizers`: sanitizer runtime libraries - `mingw`: other MinGW libs and Windows import libs diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 1968f5eb73d4e..d2e6dfcf9f0e5 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -814,18 +814,10 @@ impl Config { || (arch == "i586" && self.target.starts_with("i586-")) } - pub(crate) fn matches_os(&self, os: &str) -> bool { - self.target_cfg().os == os - } - pub(crate) fn matches_env(&self, env: &str) -> bool { self.target_cfg().env == env } - pub(crate) fn matches_abi(&self, abi: &str) -> bool { - self.target_cfg().abi == abi - } - #[cfg_attr(not(test), expect(dead_code, reason = "only used by tests for `ignore-{family}`"))] pub(crate) fn matches_family(&self, family: &str) -> bool { self.target_cfg().families.iter().any(|f| f == family) diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs index 732f4ab275dd7..8b3070e69cccb 100644 --- a/src/tools/compiletest/src/directives/directive_names.rs +++ b/src/tools/compiletest/src/directives/directive_names.rs @@ -172,7 +172,6 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "needs-backends", "needs-crate-type", "needs-deterministic-layouts", - "needs-dlltool", "needs-dynamic-linking", "needs-enzyme", "needs-force-clang-based-tests", diff --git a/src/tools/compiletest/src/directives/needs.rs b/src/tools/compiletest/src/directives/needs.rs index ce67b11fba211..289fec6cd9e0c 100644 --- a/src/tools/compiletest/src/directives/needs.rs +++ b/src/tools/compiletest/src/directives/needs.rs @@ -299,11 +299,6 @@ pub(crate) fn prepare_needs_conditions(config: &Config) -> PreparedNeedsConditio }, ignore_reason: "ignored on targets without Rust's LLD", }, - Need { - name: "needs-dlltool", - condition: find_dlltool(config), - ignore_reason: "ignored when dlltool for the current architecture is not present", - }, Need { name: "needs-git-hash", condition: config.git_hash, @@ -373,29 +368,6 @@ pub(crate) fn prepare_needs_conditions(config: &Config) -> PreparedNeedsConditio PreparedNeedsConditions { simple_needs } } -fn find_dlltool(config: &Config) -> bool { - let path = std::env::var_os("PATH").expect("missing PATH environment variable"); - let path = std::env::split_paths(&path).collect::>(); - - // dlltool is used ony by GNU based `*-*-windows-gnu` - if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) { - return false; - } - - // On Windows, dlltool.exe is used for all architectures. - // For non-Windows, there are architecture specific dlltool binaries. - let dlltool_found = if cfg!(windows) { - path.iter().any(|dir| dir.join("dlltool.exe").is_file()) - } else if config.matches_arch("i686") { - path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file()) - } else if config.matches_arch("x86_64") { - path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file()) - } else { - false - }; - dlltool_found -} - // FIXME(#135928): this is actually not quite right because this detection is run on the **host**. // This however still helps the case of windows -> windows local development in case symlinks are // not available. diff --git a/src/tools/compiletest/src/directives/tests.rs b/src/tools/compiletest/src/directives/tests.rs index 6facfe362c4eb..03cfa1f3129fc 100644 --- a/src/tools/compiletest/src/directives/tests.rs +++ b/src/tools/compiletest/src/directives/tests.rs @@ -721,7 +721,6 @@ fn matches_os() { ]; for (target, os) in oss { let config = cfg().target(target).build(); - assert!(config.matches_os(os), "{target} {os}"); assert!(check_ignore(&config, &format!("//@ ignore-{os}"))); } } @@ -746,7 +745,6 @@ fn matches_abi() { let abis = [("x86_64-unknown-linux-gnux32", "x32"), ("arm-unknown-linux-gnueabi", "eabi")]; for (target, abi) in abis { let config: Config = cfg().target(target).build(); - assert!(config.matches_abi(abi), "{target} {abi}"); assert!(check_ignore(&config, &format!("//@ ignore-{abi}"))); } } diff --git a/tests/run-make/raw-dylib-cross-compilation/lib.rs b/tests/run-make/raw-dylib-cross-compilation/lib.rs deleted file mode 100644 index 8d53ac04485eb..0000000000000 --- a/tests/run-make/raw-dylib-cross-compilation/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![feature(no_core, lang_items)] -#![no_std] -#![no_core] -#![crate_type = "lib"] - -// This is needed because of #![no_core]: -#[lang = "pointee_sized"] -trait PointeeSized {} -#[lang = "meta_sized"] -trait MetaSized: PointeeSized {} -#[lang = "sized"] -trait Sized: MetaSized {} - -#[link(name = "extern_1", kind = "raw-dylib")] -extern "C" { - fn extern_fn(); -} - -pub fn extern_fn_caller() { - unsafe { - extern_fn(); - } -} diff --git a/tests/run-make/raw-dylib-cross-compilation/rmake.rs b/tests/run-make/raw-dylib-cross-compilation/rmake.rs deleted file mode 100644 index 3eb55546314df..0000000000000 --- a/tests/run-make/raw-dylib-cross-compilation/rmake.rs +++ /dev/null @@ -1,41 +0,0 @@ -// When cross-compiling using `raw-dylib`, rustc would try to fetch some -// very specific `dlltool` to complete the cross-compilation (such as `i686-w64-mingw32-dlltool`) -// when Windows only calls it `dlltool`. This test performs some cross-compilation in a -// way that previously failed due to this bug, and checks that it succeeds. -// See https://github.com/rust-lang/rust/pull/108355 - -//@ ignore-i686-pc-windows-gnu -// Reason: dlltool on this distribution is unable to produce x64 binaries -//@ needs-dlltool -// Reason: this is the utility being checked by this test - -use run_make_support::{llvm_objdump, rust_lib_name, rustc}; - -fn main() { - // Build as x86 and make sure that we have x86 objects only. - rustc() - .crate_type("lib") - .crate_name("i686_raw_dylib_test") - .target("i686-pc-windows-gnu") - .input("lib.rs") - .run(); - llvm_objdump() - .arg("-a") - .input(rust_lib_name("i686_raw_dylib_test")) - .run() - .assert_stdout_contains("file format coff-i386") - .assert_stdout_not_contains("file format coff-x86-64"); - // Build as x64 and make sure that we have x64 objects only. - rustc() - .crate_type("lib") - .crate_name("x64_raw_dylib_test") - .target("x86_64-pc-windows-gnu") - .input("lib.rs") - .run(); - llvm_objdump() - .arg("-a") - .input(rust_lib_name("x64_raw_dylib_test")) - .run() - .assert_stdout_not_contains("file format coff-i386") - .assert_stdout_contains("file format coff-x86-64"); -} diff --git a/tests/run-make/raw-dylib-custom-dlltool/lib.rs b/tests/run-make/raw-dylib-custom-dlltool/lib.rs deleted file mode 100644 index db1d645050388..0000000000000 --- a/tests/run-make/raw-dylib-custom-dlltool/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[link(name = "extern_1", kind = "raw-dylib")] -extern "C" { - fn extern_fn_1(); -} - -pub fn library_function() { - unsafe { - extern_fn_1(); - } -} diff --git a/tests/run-make/raw-dylib-custom-dlltool/output.txt b/tests/run-make/raw-dylib-custom-dlltool/output.txt deleted file mode 100644 index 6dd9466d26ddc..0000000000000 --- a/tests/run-make/raw-dylib-custom-dlltool/output.txt +++ /dev/null @@ -1 +0,0 @@ -Called dlltool via script.cmd diff --git a/tests/run-make/raw-dylib-custom-dlltool/rmake.rs b/tests/run-make/raw-dylib-custom-dlltool/rmake.rs deleted file mode 100644 index 86b952f0b4520..0000000000000 --- a/tests/run-make/raw-dylib-custom-dlltool/rmake.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Instead of using the default dlltool, the rust compiler can also accept a custom -// command file with the -C dlltool flag. This test uses it to compile some rust code -// with the raw_dylib Windows-exclusive feature, and checks that the output contains -// the string passed from the custom dlltool, confirming that the default dlltool was -// successfully overridden. -// See https://github.com/rust-lang/rust/pull/109677 - -//@ only-windows -//@ only-gnu -//@ needs-dlltool -// Reason: this test specifically checks the custom dlltool feature, only -// available on Windows-gnu. - -use run_make_support::{diff, rustc}; - -fn main() { - let out = rustc() - .crate_type("lib") - .crate_name("raw_dylib_test") - .input("lib.rs") - .arg("-Cdlltool=script.cmd") - .run(); - diff().expected_file("output.txt").actual_file("actual.txt").normalize(r#"\r"#, "").run(); -} diff --git a/tests/run-make/raw-dylib-custom-dlltool/script.cmd b/tests/run-make/raw-dylib-custom-dlltool/script.cmd deleted file mode 100644 index c88878151af48..0000000000000 --- a/tests/run-make/raw-dylib-custom-dlltool/script.cmd +++ /dev/null @@ -1,2 +0,0 @@ -echo Called dlltool via script.cmd> %~dp0\actual.txt -dlltool.exe %* diff --git a/tests/run-make/raw-dylib-whitespace/main.rs b/tests/run-make/raw-dylib-whitespace/main.rs deleted file mode 100644 index 023c3570c3262..0000000000000 --- a/tests/run-make/raw-dylib-whitespace/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -type BOOL = i32; - -#[cfg_attr( - target_arch = "x86", - link(name = "bcryptprimitives", kind = "raw-dylib", import_name_type = "undecorated") -)] -#[cfg_attr(not(target_arch = "x86"), link(name = "bcryptprimitives", kind = "raw-dylib"))] -extern "system" { - fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> BOOL; -} - -fn main() { - let mut num: u8 = 0; - unsafe { - ProcessPrng(&mut num, 1); - } - println!("{num}"); -} diff --git a/tests/run-make/raw-dylib-whitespace/rmake.rs b/tests/run-make/raw-dylib-whitespace/rmake.rs deleted file mode 100644 index 4f90eeb055142..0000000000000 --- a/tests/run-make/raw-dylib-whitespace/rmake.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Ensure that raw-dylib still works if the output directory contains spaces. - -//@ only-windows-gnu -//@ ignore-cross-compile -//@ needs-dlltool -// Reason: this test specifically checks the dlltool feature, -// which is only used on windows-gnu. - -use run_make_support::{rfs, rustc}; - -fn main() { - let out_dir = std::path::absolute("path with spaces").unwrap(); - rfs::create_dir_all(&out_dir); - rustc().crate_type("bin").input("main.rs").out_dir(&out_dir).env("TMP", &out_dir).run(); -} diff --git a/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.rs b/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.rs deleted file mode 100644 index ae326193550bc..0000000000000 --- a/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Tests that dlltool failing to generate an import library will raise an error. - -//@ needs-dlltool -//@ compile-flags: --crate-type lib --emit link -//@ normalize-stderr: "[^ ']*/dlltool.exe" -> "$$DLLTOOL" -//@ normalize-stderr: "[^ ]*/foo.dll_imports.def" -> "$$DEF_FILE" -//@ normalize-stderr: "[^ ]*/foo.dll_imports.lib" -> "$$LIB_FILE" -//@ normalize-stderr: "-m [^ ]*" -> "$$TARGET_MACHINE" -//@ normalize-stderr: "-f [^ ]*" -> "$$ASM_FLAGS" -#[link(name = "foo", kind = "raw-dylib")] -extern "C" { - // `@1` is an invalid name to export, as it usually indicates that something - // is being exported via ordinal. - #[link_name = "@1"] - fn f(x: i32); -} - -pub fn lib_main() { - unsafe { f(42); } -} - -//~? ERROR dlltool could not create import library with diff --git a/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.stderr b/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.stderr deleted file mode 100644 index b7279f23e6676..0000000000000 --- a/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: dlltool could not create import library with $DLLTOOL -d $DEF_FILE -D foo.dll -l $LIB_FILE $TARGET_MACHINE $ASM_FLAGS --no-leading-underscore --temp-prefix foo.dll: - - $DLLTOOL: Syntax error in def file $DEF_FILE:1␍ - - -error: aborting due to 1 previous error - diff --git a/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.rs b/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.rs deleted file mode 100644 index f5f92db3b501a..0000000000000 --- a/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Tests that failing to run dlltool will raise an error. - -//@ needs-dlltool -//@ compile-flags: --crate-type lib --emit link -Cdlltool=does_not_exist.exe -#[link(name = "foo", kind = "raw-dylib")] -extern "C" { - fn f(x: i32); -} - -pub fn lib_main() { - unsafe { f(42); } -} - -//~? ERROR error calling dlltool 'does_not_exist.exe': program not found diff --git a/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.stderr b/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.stderr deleted file mode 100644 index 311d8c61589a4..0000000000000 --- a/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.stderr +++ /dev/null @@ -1,4 +0,0 @@ -error: error calling dlltool 'does_not_exist.exe': program not found - -error: aborting due to 1 previous error - From 18018a884b365ec065ed29b1d23e0fb10089b183 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:55:22 +0200 Subject: [PATCH 02/20] Remove support for dlltool symbol name differences --- compiler/rustc_codegen_llvm/src/callee.rs | 8 +------- compiler/rustc_codegen_llvm/src/consts.rs | 7 ------- .../rustc_codegen_ssa/src/back/link/raw_dylib.rs | 10 ++++------ compiler/rustc_codegen_ssa/src/common.rs | 12 ++---------- 4 files changed, 7 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/callee.rs b/compiler/rustc_codegen_llvm/src/callee.rs index df18b029b02c0..677ad270258cc 100644 --- a/compiler/rustc_codegen_llvm/src/callee.rs +++ b/compiler/rustc_codegen_llvm/src/callee.rs @@ -40,14 +40,8 @@ pub(crate) fn get_fn<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'t let llfn = if tcx.sess.target.arch == Arch::X86 && let Some(dllimport) = crate::common::get_dllimport(tcx, instance_def_id, sym) { - // When calling functions in generated import libraries, - // LLVM/ar_archive_writer needs the fully decorated name - // (as would have been in the declaring object file), but dlltool - // wants the name as exported (as would be in the def file) - // which may be missing decorations. - let using_dlltool = common::is_using_dlltool(&tcx.sess.target); let llfn = cx.declare_fn( - &common::i686_decorated_name(dllimport, using_dlltool, true, !using_dlltool), + &common::i686_decorated_name(dllimport, true, true), fn_abi, Some(instance), ); diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index ec58c3cb6ad7a..283d047df6309 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -3,7 +3,6 @@ use std::ops::Range; use rustc_abi::{Align, ExternAbi, HasDataLayout, Primitive, Scalar, Size, WrappingRange}; use rustc_attr_ir::Linkage; use rustc_attr_ir::lang_items::LangItem; -use rustc_codegen_ssa::common; use rustc_codegen_ssa::traits::*; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; @@ -16,7 +15,6 @@ use rustc_middle::mono::MonoItem; use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf}; use rustc_middle::ty::{self, Instance}; use rustc_span::{Symbol, bug, span_bug}; -use rustc_target::spec::Arch; use tracing::{debug, instrument, trace}; use crate::common::CodegenCx; @@ -265,11 +263,6 @@ fn check_and_apply_linkage<'ll, 'tcx>( llvm::set_initializer(g2, initializer); g2 - } else if cx.tcx.sess.target.arch == Arch::X86 - && common::is_using_dlltool(&cx.tcx.sess.target) - && let Some(dllimport) = crate::common::get_dllimport(cx.tcx, def_id, sym) - { - cx.declare_global(&common::i686_decorated_name(dllimport, true, true, false), llty) } else { // Generate an external declaration. // FIXME(nagisa): investigate whether it can be changed into define_global diff --git a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs index 99436c4d2f84a..f5b3a8e113d6d 100644 --- a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs +++ b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs @@ -73,18 +73,16 @@ pub(super) fn create_raw_dylib_dll_import_libs<'a>( let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" }; let output_path = tmpdir.join(format!("{raw_dylib_name}{name_suffix}.lib")); - let using_dlltool = common::is_using_dlltool(&sess.target); - let items: Vec = raw_dylib_imports .iter() .map(|import: &DllImport| { if sess.target.arch == Arch::X86 { ImportLibraryItem { - name: common::i686_decorated_name(import, using_dlltool, false, false), + name: common::i686_decorated_name(import, false, false), ordinal: import.ordinal(), - symbol_name: import.is_missing_decorations().then(|| { - common::i686_decorated_name(import, using_dlltool, false, true) - }), + symbol_name: import + .is_missing_decorations() + .then(|| common::i686_decorated_name(import, false, true)), is_data: import.symbol_type != DllImportSymbolType::Function, } } else { diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index 9338a6b3769ed..2b2ec59903391 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -7,7 +7,6 @@ use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, Instance, ScalarInt, TyCtxt}; use rustc_span::{Span, bug, span_bug}; -use rustc_target::spec::{CfgAbi, Env, Os, Target}; use crate::traits::*; @@ -199,13 +198,8 @@ pub fn asm_const_ptr_clean<'tcx>(tcx: TyCtxt<'tcx>, scalar: Scalar) -> Scalar { } } -pub fn is_using_dlltool(target: &Target) -> bool { - target.os == Os::Windows && target.env == Env::Gnu && target.cfg_abi == CfgAbi::Unspecified -} - pub fn i686_decorated_name( dll_import: &DllImport, - mingw: bool, disable_name_mangling: bool, force_fully_decorated: bool, ) -> String { @@ -231,12 +225,10 @@ pub fn i686_decorated_name( let prefix = if add_prefix && dll_import.symbol_type == DllImportSymbolType::Function { match dll_import.calling_convention { DllCallingConvention::C | DllCallingConvention::Vectorcall(_) => None, - DllCallingConvention::Stdcall(_) => (!mingw - || dll_import.import_name_type == Some(PeImportNameType::Decorated)) - .then_some('_'), + DllCallingConvention::Stdcall(_) => Some('_'), DllCallingConvention::Fastcall(_) => Some('@'), } - } else if dll_import.symbol_type != DllImportSymbolType::Function && !mingw { + } else if dll_import.symbol_type != DllImportSymbolType::Function { // For static variables, prefix with '_' on MSVC. Some('_') } else { From 47c9070de56d936b72f68914dfe7b7ebbffaf2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 7 Oct 2026 14:57:00 +0200 Subject: [PATCH 03/20] Parse move exprs as a bottom expr instead of a prefix expr --- compiler/rustc_parse/src/parser/expr.rs | 31 +++++++++---------- .../feature-gate-move_expr.stderr | 2 +- .../left-operand-of-dot-call-index-try.rs | 21 +++++++++++++ 3 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 tests/ui/move-expr/left-operand-of-dot-call-index-try.rs diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index bf99bbe26f087..5577c74850f41 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -410,12 +410,6 @@ impl<'a> Parser<'a> { pre_span, )); } - token::Ident(..) - if this.token.is_keyword(kw::Move) - && this.look_ahead(1, |t| *t == token::OpenParen) => - { - make_it!(this, attrs, |this, _| this.parse_expr_move(lo)) - } token::Ident(..) if this.may_recover() && this.is_mistaken_not_ident_negation() => { make_it!(this, attrs, |this, _| this.recover_not_expr(lo)) } @@ -447,16 +441,6 @@ impl<'a> Parser<'a> { self.parse_expr_unary(lo, UnOp::Not) } - fn parse_expr_move(&mut self, move_kw: Span) -> PResult<'a, (Span, ExprKind)> { - self.bump(); - self.psess.gated_spans.gate(sym::move_expr, move_kw); - self.expect(exp!(OpenParen))?; - let expr = self.parse_expr()?; - self.expect(exp!(CloseParen))?; - let span = move_kw.to(self.prev_token.span); - Ok((span, ExprKind::Move(expr, move_kw))) - } - fn is_mistaken_not_ident_negation(&self) -> bool { let token_cannot_continue_expr = |t: &Token| match t.uninterpolate().kind { // These tokens can start an expression after `!`, but @@ -1335,6 +1319,10 @@ impl<'a> Parser<'a> { this.parse_expr_builtin() } else if this.check_path() { this.parse_expr_path_start() + } else if this.token.is_keyword(kw::Move) + && this.look_ahead(1, |t| *t == token::OpenParen) + { + this.parse_expr_move() } else if this.check_keyword(exp!(Move)) || this.check_keyword(exp!(Use)) || this.check_keyword(exp!(Static)) @@ -2216,6 +2204,17 @@ impl<'a> Parser<'a> { Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None))) } + fn parse_expr_move(&mut self) -> PResult<'a, Box> { + let move_kw = self.token.span; + self.expect_keyword(exp!(Move))?; + self.expect(exp!(OpenParen))?; + let expr = self.parse_expr()?; + self.expect(exp!(CloseParen))?; + let span = move_kw.to(self.prev_token.span); + self.psess.gated_spans.gate(sym::move_expr, span); + Ok(self.mk_expr(span, ExprKind::Move(expr, move_kw))) + } + /// Parses a closure expression (e.g., `move |args| expr`). fn parse_expr_closure(&mut self) -> PResult<'a, Box> { let lo = self.token.span; diff --git a/tests/ui/feature-gates/feature-gate-move_expr.stderr b/tests/ui/feature-gates/feature-gate-move_expr.stderr index 8b1da2c06893d..41a2ded842207 100644 --- a/tests/ui/feature-gates/feature-gate-move_expr.stderr +++ b/tests/ui/feature-gates/feature-gate-move_expr.stderr @@ -2,7 +2,7 @@ error[E0658]: `move(expr)` syntax is experimental --> $DIR/feature-gate-move_expr.rs:2:16 | LL | let _ = || move(2); - | ^^^^ + | ^^^^^^^ | = note: see issue #155050 for more information = help: add `#![feature(move_expr)]` to the crate attributes to enable diff --git a/tests/ui/move-expr/left-operand-of-dot-call-index-try.rs b/tests/ui/move-expr/left-operand-of-dot-call-index-try.rs new file mode 100644 index 0000000000000..700101a8a27f2 --- /dev/null +++ b/tests/ui/move-expr/left-operand-of-dot-call-index-try.rs @@ -0,0 +1,21 @@ +// Ensure that move exprs can be the left operand of dot, call, index & try ops. We once used to +// incorrectly parse move exprs as prefix exprs instead of bottom ones which used to prevent that. + +//@ check-pass +//@ edition: 2018.. +#![feature(move_expr)] + +fn main() { + let _ = async || { move(async {}).await }; // Await(Move(_)) + + let _ = || -> Result<(), ()> { + move(((),)).0; // Field(Move(_), ..) + move(Err(()))?; // Try(Move(_)) + move(drop)(()); // Call(Move(_), ..) + move([0])[0]; // Index(Move(_), ..) + + // Previously, we would parse this as Field(Return(Move(_)), ..) (`(return move(…)).0`) + // instead of Return(Field(Move(_), ..)) and thus fail with "no field `0` on type `!`". + return move((Ok(()),)).0; // OK! + }; +} From 604a21cab2edf48dc9b40e1a513273d52aafa167 Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Fri, 25 Sep 2026 17:37:42 +0200 Subject: [PATCH 04/20] Document zero sized allocations in `Box` and `Vec` --- library/alloc/src/boxed.rs | 133 ++++++++++++++++---- library/alloc/src/string.rs | 4 + library/alloc/src/vec/mod.rs | 237 ++++++++++++----------------------- 3 files changed, 193 insertions(+), 181 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 7504763120532..9f3a17314c356 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -53,20 +53,26 @@ //! //! # Memory layout //! -//! For non-zero-sized values, a [`Box`] will use the [`Global`] allocator for its allocation. It is -//! valid to convert both ways between a [`Box`] and a raw pointer allocated with the [`Global`] -//! allocator, given that the [`Layout`] used with the allocator is correct for the type and the raw -//! pointer points to a valid value of the right type. More precisely, a `value: *mut T` that has -//! been allocated with the [`Global`] allocator with `Layout::for_value(&*value)` may be converted -//! into a box using [`Box::::from_raw(value)`]. Conversely, the memory backing a `value: *mut T` -//! obtained from [`Box::::into_raw`] may be deallocated using the [`Global`] allocator with -//! [`Layout::for_value(&*value)`]. +//! For non-zero-sized values, a [`Box`] will use its allocator of type `A` for its allocation. +//! It is valid to convert both ways between a `Box` and a raw pointer allocated with `A`, so long as: +//! - the size of the pointee, as reported by [`size_of_val`], is non-zero, +//! - the [`Layout::for_value`] of the pointee [*fits*](Allocator#memory-fitting) the memory block, and +//! - the pointee is a valid instance of `T` (for the given metadata of the pointer). //! -//! For zero-sized values, the `Box` pointer has to be non-null and sufficiently aligned. The -//! recommended way to build a Box to a ZST if `Box::new` cannot be used is to use -//! [`ptr::NonNull::dangling`]. +//! For zero-sized values, a [`Box`] may use any sufficiently aligned non-null pointer. +//! While it is sound to convert a zero-sized allocation from `A` into a `Box`, as +//! long as it is backed by a valid value of `T`, doing so may lead to memory leaks, as +//! the destructor of [`Box`] will not call [`deallocate`](Allocator::deallocate) +//! on zero-sized values. It is recommended to use [`ptr::dangling`] instead. //! -//! On top of these basic layout requirements, a `Box` must point to a valid value of `T`. +//! Likewise, manually deallocating the raw pointer acquired from a `Box` is only sound +//! if the size of the pointee is non-zero, as zero-sized values may be represented by +//! a pointer that is not [*currently allocated*] by that allocator. It is recommended to +//! pair [`Box::into_raw_with_allocator`] with [`Box::from_raw_in`], rather than +//! doing this. +//! +//! Note that even the [`Global`] allocator might not use dangling pointers for +//! zero-sized allocations, even though *most* implementations do this. //! //! So long as `T: Sized`, a `Box` is guaranteed to be represented //! as a single pointer and is also ABI-compatible with C pointers @@ -172,6 +178,7 @@ //! and it's best to avoid relying on this edition-dependent behavior if you wish to preserve //! compatibility with future versions of the compiler. //! +//! [*currently allocated*]: Allocator#currently-allocated-memory //! [ucg#198]: https://github.com/rust-lang/unsafe-code-guidelines/issues/198 //! [ucg#326]: https://github.com/rust-lang/unsafe-code-guidelines/issues/326 //! [dereferencing]: core::ops::Deref @@ -275,12 +282,15 @@ impl Box { /// Allocates memory on the heap and then places `x` into it. /// /// This doesn't actually allocate if `T` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// /// ``` /// let five = Box::new(5); /// ``` + /// + /// [memory layout]: self#memory-layout #[cfg(not(no_global_oom_handling))] #[inline(always)] #[stable(feature = "rust1", since = "1.0.0")] @@ -367,6 +377,7 @@ impl Box { /// returning an error if the allocation fails /// /// This doesn't actually allocate if `T` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -376,6 +387,8 @@ impl Box { /// let five = Box::try_new(5)?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` + /// + /// [memory layout]: self#memory-layout #[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] #[inline] pub fn try_new(x: T) -> Result { @@ -434,6 +447,7 @@ impl Box { /// Allocates memory in the given allocator then places `x` into it. /// /// This doesn't actually allocate if `T` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -442,6 +456,8 @@ impl Box { /// /// let five = Box::new_in(5, System); /// ``` + /// + /// [memory layout]: self#memory-layout #[cfg(not(no_global_oom_handling))] #[stable(feature = "allocator_api", since = "CURRENT_RUSTC_VERSION")] #[must_use] @@ -457,6 +473,7 @@ impl Box { /// returning an error if the allocation fails /// /// This doesn't actually allocate if `T` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -468,6 +485,8 @@ impl Box { /// let five = Box::try_new_in(5, System)?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` + /// + /// [memory layout]: self#memory-layout #[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] #[inline] pub fn try_new_in(x: T, alloc: A) -> Result { @@ -774,6 +793,7 @@ impl Box { /// Allocates memory on the heap then clones `src` into it. /// /// This doesn't actually allocate if `src` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -782,6 +802,8 @@ impl Box { /// /// let hello: Box = Box::clone_from_ref("hello"); /// ``` + /// + /// [memory layout]: self#memory-layout #[cfg(not(no_global_oom_handling))] #[unstable(feature = "clone_from_ref", issue = "149075")] #[must_use] @@ -793,6 +815,7 @@ impl Box { /// Allocates memory on the heap then clones `src` into it, returning an error if allocation fails. /// /// This doesn't actually allocate if `src` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -802,6 +825,8 @@ impl Box { /// let hello: Box = Box::try_clone_from_ref("hello")?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` + /// + /// [memory layout]: self#memory-layout #[unstable(feature = "clone_from_ref", issue = "149075")] //#[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] #[inline] @@ -814,6 +839,7 @@ impl Box { /// Allocates memory in the given allocator then clones `src` into it. /// /// This doesn't actually allocate if `src` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -824,6 +850,8 @@ impl Box { /// /// let hello: Box = Box::clone_from_ref_in("hello", System); /// ``` + /// + /// [memory layout]: self#memory-layout #[cfg(not(no_global_oom_handling))] #[unstable(feature = "clone_from_ref", issue = "149075")] //#[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] @@ -840,6 +868,7 @@ impl Box { /// Allocates memory in the given allocator then clones `src` into it, returning an error if allocation fails. /// /// This doesn't actually allocate if `src` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -851,6 +880,8 @@ impl Box { /// let hello: Box = Box::try_clone_from_ref_in("hello", System)?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` + /// + /// [memory layout]: self#memory-layout #[unstable(feature = "clone_from_ref", issue = "149075")] //#[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] #[inline] @@ -1300,9 +1331,16 @@ impl Box { /// memory problems. For example, a double-free may occur if the /// function is called twice on the same raw pointer. /// - /// The raw pointer must point to a block of memory allocated by the global allocator. + /// The exact safety requirements are described in the [memory layout] section, together + /// with recommendations about how to handle the special case of zero-sized allocations + /// correctly. + /// + /// In particular, if the size of the pointee, as reported by [`size_of_val`], is + /// nonzero, then the raw pointer must point to a block of memory allocated by + /// [`Global`]. + /// If the size of the pointee is zero, then the raw pointer can be any + /// sufficiently aligned non-null pointer, e.g. [`ptr::dangling`]. /// - /// The safety conditions are described in the [memory layout] section. /// Note that the [considerations for unsafe code] apply to all `Box` values. /// /// # Examples @@ -1352,9 +1390,16 @@ impl Box { /// memory problems. For example, a double-free may occur if the /// function is called twice on the same `NonNull` pointer. /// - /// The non-null pointer must point to a block of memory allocated by the global allocator. + /// The exact safety requirements are described in the [memory layout] section, together + /// with recommendations about how to handle the special case of zero-sized allocations + /// correctly. + /// + /// In particular, if the size of the pointee, as reported by [`size_of_val`], is + /// nonzero, then the raw pointer must point to a block of memory allocated by + /// [`Global`]. + /// If the size of the pointee is zero, then the raw pointer can be any + /// sufficiently aligned pointer, e.g. [`NonNull::dangling`]. /// - /// The safety conditions are described in the [memory layout] section. /// Note that the [considerations for unsafe code] apply to all `Box` values. /// /// # Examples @@ -1403,6 +1448,11 @@ impl Box { /// [`Box::from_raw`] function, allowing the `Box` destructor to perform /// the cleanup. /// + /// While manually deallocating the pointer through [`deallocate`] + /// is possible, you must only do this if the pointee's size, as reported by + /// [`size_of_val`], is non-zero (the `Box` may be a dangling pointer + /// otherwise). See the [memory layout] section for more details. + /// /// Note: this is an associated function, which means that you have /// to call it as `Box::into_raw(b)` instead of `b.into_raw()`. This /// is so that there is no conflict with a method on the inner type. @@ -1438,6 +1488,7 @@ impl Box { /// ``` /// /// [memory layout]: self#memory-layout + /// [`deallocate`]: Allocator::deallocate #[must_use = "losing the pointer will leak memory"] #[stable(feature = "box_raw", since = "1.4.0")] #[inline] @@ -1465,6 +1516,11 @@ impl Box { /// [`Box::from_non_null`] function, allowing the `Box` destructor to /// perform the cleanup. /// + /// While manually deallocating the pointer through [`deallocate`] + /// is possible, you must only do this if the pointee's size, as reported by + /// [`size_of_val`], is non-zero (the `Box` may be a dangling pointer + /// otherwise). See the [memory layout] section for more details. + /// /// Note: this is an associated function, which means that you have /// to call it as `Box::into_non_null(b)` instead of `b.into_non_null()`. /// This is so that there is no conflict with a method on the inner type. @@ -1499,6 +1555,7 @@ impl Box { /// ``` /// /// [memory layout]: self#memory-layout + /// [`deallocate`]: Allocator::deallocate #[must_use = "losing the pointer will leak memory"] #[stable(feature = "box_vec_non_null", since = "1.99.0")] #[inline] @@ -1517,9 +1574,8 @@ impl Box { /// /// After calling this function, the raw pointer is owned by the /// resulting `Box`. Specifically, the `Box` destructor will call - /// the destructor of `T` and free the allocated memory. For this - /// to be safe, the memory must have been allocated in accordance - /// with the [memory layout] used by `Box` . + /// the destructor of `T` and free the allocated memory, except + /// in the cases described in the [memory layout] section. /// /// # Safety /// @@ -1527,9 +1583,16 @@ impl Box { /// memory problems. For example, a double-free may occur if the /// function is called twice on the same raw pointer. /// - /// The raw pointer must point to a block of memory allocated by `alloc`. + /// The exact safety requirements are described in the [memory layout] section, together + /// with recommendations about how to handle the special case of zero-sized allocations + /// correctly. + /// + /// In particular, if the size of the pointee, as reported by [`size_of_val`], is + /// nonzero, then the raw pointer must point to a block of memory allocated by + /// `alloc`. + /// If the size of the pointee is zero, then the raw pointer can be any + /// sufficiently aligned non-null pointer, e.g. [`ptr::dangling`]. /// - /// The safety conditions are described in the [memory layout] section. /// Note that the [considerations for unsafe code] apply to all `Box` values. /// /// # Examples @@ -1573,9 +1636,8 @@ impl Box { /// /// After calling this function, the `NonNull` pointer is owned by /// the resulting `Box`. Specifically, the `Box` destructor will call - /// the destructor of `T` and free the allocated memory. For this - /// to be safe, the memory must have been allocated in accordance - /// with the [memory layout] used by `Box` . + /// the destructor of `T` and free the allocated memory, except + /// in the cases described in the [memory layout] section. /// /// # Safety /// @@ -1583,9 +1645,16 @@ impl Box { /// memory problems. For example, a double-free may occur if the /// function is called twice on the same raw pointer. /// - /// The non-null pointer must point to a block of memory allocated by `alloc`. + /// The exact safety requirements are described in the [memory layout] section, together + /// with recommendations about how to handle the special case of zero-sized allocations + /// correctly. + /// + /// In particular, if the size of the pointee, as reported by [`size_of_val`], is + /// nonzero, then the raw pointer must point to a block of memory allocated by + /// `alloc`. + /// If the size of the pointee is zero, then the raw pointer can be any + /// sufficiently aligned pointer, e.g. [`NonNull::dangling`]. /// - /// The safety conditions are described in the [memory layout] section. /// Note that the [considerations for unsafe code] apply to all `Box` values. /// /// # Examples @@ -1634,6 +1703,11 @@ impl Box { /// [`Box::from_raw_in`] function, allowing the `Box` destructor to perform /// the cleanup. /// + /// While manually deallocating the pointer through [`deallocate`] + /// is possible, you must only do this if the pointee's size, as reported by + /// [`size_of_val`], is non-zero (the `Box` may be a dangling pointer + /// otherwise). See the [memory layout] section for more details. + /// /// Note: this is an associated function, which means that you have /// to call it as `Box::into_raw_with_allocator(b)` instead of `b.into_raw_with_allocator()`. This /// is so that there is no conflict with a method on the inner type. @@ -1664,6 +1738,7 @@ impl Box { /// ``` /// /// [memory layout]: self#memory-layout + /// [`deallocate`]: Allocator::deallocate #[must_use = "losing the pointer will leak memory"] #[stable(feature = "allocator_api", since = "CURRENT_RUSTC_VERSION")] #[rustc_const_unstable(feature = "const_heap", issue = "79597")] @@ -1693,6 +1768,11 @@ impl Box { /// [`Box::from_non_null_in`] function, allowing the `Box` destructor to /// perform the cleanup. /// + /// While manually deallocating the pointer through [`deallocate`] + /// is possible, you must only do this if the pointee's size, as reported by + /// [`size_of_val`], is non-zero (the `Box` may be a dangling pointer + /// otherwise). See the [memory layout] section for more details. + /// /// Note: this is an associated function, which means that you have /// to call it as `Box::into_non_null_with_allocator(b)` instead of /// `b.into_non_null_with_allocator()`. This is so that there is no @@ -1722,6 +1802,7 @@ impl Box { /// ``` /// /// [memory layout]: self#memory-layout + /// [`deallocate`]: Allocator::deallocate #[must_use = "losing the pointer will leak memory"] #[stable(feature = "allocator_api", since = "CURRENT_RUSTC_VERSION")] #[inline] diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 03c07b15712bb..1d864b061a825 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -940,6 +940,10 @@ impl String { /// into a `String` with the [`from_raw_parts`] function, allowing /// the destructor to perform the cleanup. /// + /// If `capacity == 0`, then `pointer` may be dangling. Otherwise, + /// it will always be valid for reads and writes over `capacity` + /// bytes, and point to the start of the string. + /// /// [`from_raw_parts`]: String::from_raw_parts /// /// # Examples diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index f837d963a572e..6970235e65e83 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -51,25 +51,50 @@ //! //! # Memory layout //! -//! When the type is non-zero-sized and the capacity is nonzero, [`Vec`] uses the [`Global`] -//! allocator for its allocation. It is valid to convert both ways between such a [`Vec`] and a raw -//! pointer allocated with the [`Global`] allocator, provided that the [`Layout`] used with the -//! allocator is correct for a sequence of `capacity` elements of the type, and the first `len` -//! values pointed to by the raw pointer are valid. More precisely, a `ptr: *mut T` that has been -//! allocated with the [`Global`] allocator with [`Layout::array::(capacity)`][Layout::array] may -//! be converted into a vec using -//! [`Vec::::from_raw_parts(ptr, len, capacity)`](Vec::from_raw_parts). Conversely, the memory -//! backing a `value: *mut T` obtained from [`Vec::::as_mut_ptr`] may be deallocated using the -//! [`Global`] allocator with the same layout. +//! A [`Vec`] conceptually consists of a raw pointer `ptr` to the start of the allocation, a +//! length `len`, a capacity `cap`, and an allocator `alloc`. +//! While the exact fields and their order are not specified, a [`Vec`] can be converted to and +//! from these parts using [`Vec::into_raw_parts_with_allocator`], [`Vec::from_raw_parts_in`], +//! [`Vec::into_raw_parts`], and [`Vec::from_raw_parts`], as well as their [`NonNull`]-based +//! versions. //! -//! For zero-sized types (ZSTs), or when the capacity is zero, the `Vec` pointer must be non-null -//! and sufficiently aligned. The recommended way to build a `Vec` of ZSTs if [`vec!`] cannot be -//! used is to use [`ptr::NonNull::dangling`]. +//! The parts of a [`Vec`] have the following invariants: //! +//! * If [`size_of::()`](size_of) and `cap` are non-zero, then: +//! - `ptr` must point to the start of a memory block [*currently allocated*] via `alloc`. +//! - [`Layout::array::(cap)`] must [*fit*] the memory block of `ptr`. In particular, +//! * `T` must have the same alignment as the [`Layout`] `ptr` was allocated with, and +//! * `size_of::() * cap` must be at least the size of the [`Layout`] `ptr` was +//! allocated with, and at most the size of the allocation returned the allocator. +//! - `size_of::() * cap` must be at most [`isize::MAX`]. +//! See the safety documentation of [`pointer::offset`]. +//! * If [`size_of::()`](size_of) or `cap` is zero, then `ptr` must be any sufficiently aligned non-null pointer. +//! +//! NOTE: `ptr` will not be passed to the allocator for deallocation by the destructor of [`Vec`] +//! in this case. Thus, it is unwise to use a pointer acquired from the allocator for this, as +//! overallocated memory may end up being leaked. Use [`ptr::dangling`] instead. +//! * The following must always be satisfied: +//! - `len` needs to be less than or equal to `cap`. +//! - At `ptr`, there are at least `len` valid values of type `T`, laid out contiguously +//! (like an array). These values are considered owned by the [`Vec`]. +//! +//! It is sound to create a [`Vec`] from parts that satisfy these invariants. +//! +//! Conversely, the memory backing `ptr` obtained from [`Vec::as_mut_ptr`] or +//! [`Vec::into_raw_parts_with_allocator`]-like methods may be deallocated +//! using the allocator, with layout [`Layout::array::(cap)`], provided +//! that [`size_of::()`](size_of) or `cap` is non-zero. +//! To automatically handle the case where the allocation is zero-sized, +//! it is recommended to use [`Vec::from_raw_parts_in`], instead of manually +//! deallocating. +//! +//! [*fit*]: crate::alloc::Allocator#memory-fitting +//! [*currently allocated*]: crate::alloc::Allocator#currently-allocated-memory +//! [`allocate`]: crate::alloc::Allocator::allocate //! [`push`]: Vec::push //! [`ptr::NonNull::dangling`]: NonNull::dangling //! [`Layout`]: crate::alloc::Layout -//! [Layout::array]: crate::alloc::Layout::array +//! [`Layout::array::(cap)`]: crate::alloc::Layout::array #![stable(feature = "rust1", since = "1.0.0")] @@ -303,35 +328,13 @@ mod sve_retain; /// /// # Guarantees /// +/// The following section is to be read as an addendum of the +/// [memory layout](self#memory-layout) section. +/// /// Due to its incredibly fundamental nature, `Vec` makes a lot of guarantees /// about its design. This ensures that it's as low-overhead as possible in /// the general case, and can be correctly manipulated in primitive ways -/// by unsafe code. Note that these guarantees refer to an unqualified `Vec`. -/// If additional type parameters are added (e.g., to support custom allocators), -/// overriding their defaults may change the behavior. -/// -/// Most fundamentally, `Vec` is and always will be a (pointer, capacity, length) -/// triplet. No more, no less. The order of these fields is completely -/// unspecified, and you should use the appropriate methods to modify these. -/// The pointer will never be null, so this type is null-pointer-optimized. -/// -/// However, the pointer might not actually point to allocated memory. In particular, -/// if you construct a `Vec` with capacity 0 via [`Vec::new`], [`vec![]`][`vec!`], -/// [`Vec::with_capacity(0)`][`Vec::with_capacity`], or by calling [`shrink_to_fit`] -/// on an empty Vec, it will not allocate memory. Similarly, if you store zero-sized -/// types inside a `Vec`, it will not allocate space for them. *Note that in this case -/// the `Vec` might not report a [`capacity`] of 0*. `Vec` will allocate if and only -/// if [size_of::\]\() * [capacity]\() > 0. In general, `Vec`'s allocation -/// details are very subtle --- if you intend to allocate memory using a `Vec` -/// and use it for something else (either to pass to unsafe code, or to build your -/// own memory-backed collection), be sure to deallocate this memory by using -/// `from_raw_parts` to recover the `Vec` and then dropping it. -/// -/// If a `Vec` *has* allocated memory, then the memory it points to is on the heap -/// (as defined by the allocator Rust is configured to use by default), and its -/// pointer points to [`len`] initialized, contiguous elements in order (what -/// you would see if you coerced it to a slice), followed by [capacity] - [len] -/// logically uninitialized, contiguous elements. +/// by unsafe code. /// /// A vector containing the elements `'a'` and `'b'` with capacity 4 can be /// visualized as below. The top part is the `Vec` struct, it contains a @@ -472,7 +475,7 @@ impl Vec { /// /// The vector will be able to hold at least `capacity` elements without /// reallocating. This method is allowed to allocate for more elements than - /// `capacity`. If `capacity` is zero, the vector will not allocate. + /// `capacity`. If `capacity` is zero, the vector [will not allocate]. /// /// It is important to note that although the returned vector has the /// minimum *capacity* specified, the vector will have a zero *length*. For @@ -482,11 +485,12 @@ impl Vec { /// If it is important to know the exact allocated capacity of a `Vec`, /// always use the [`capacity`] method after construction. /// - /// For `Vec` where `T` is a zero-sized type, there will be no allocation - /// and the capacity will always be `usize::MAX`. + /// For `Vec` where `T` is a zero-sized type, the vector + /// [will not allocate], and the capacity will always be `usize::MAX`. /// /// [Capacity and reallocation]: #capacity-and-reallocation /// [`capacity`]: Vec::capacity + /// [will not allocate]: self#memory-layout /// /// # Panics /// @@ -532,12 +536,14 @@ impl Vec { /// /// The vector will be able to hold at least `capacity` elements without /// reallocating. This method is allowed to allocate for more elements than - /// `capacity`. If `capacity` is zero, the vector will not allocate. + /// `capacity`. If `capacity` is zero, the vector [will not allocate]. /// /// # Errors /// /// Returns an error if the capacity exceeds `isize::MAX` _bytes_, /// or if the allocator reports allocation failure. + /// + /// [will not allocate]: self#memory-layout #[inline] #[unstable(feature = "try_with_capacity", issue = "91913")] pub fn try_with_capacity(capacity: usize) -> Result { @@ -548,34 +554,10 @@ impl Vec { /// /// # Safety /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * If `T` is not a zero-sized type and the capacity is nonzero, `ptr` must have - /// been allocated using the global allocator, such as via the [`alloc::alloc`] - /// function. If `T` is a zero-sized type or the capacity is zero, `ptr` need - /// only be non-null and aligned. - /// * `T` needs to have the same alignment as what `ptr` was allocated with, - /// if the pointer is required to be allocated. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (i.e. the allocated size in bytes), if - /// nonzero, needs to be the same size as the pointer was allocated with. - /// (Because similar to alignment, [`dealloc`] must be called with the same - /// layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to be the capacity that the pointer was allocated with, - /// if the pointer is required to be allocated. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via `Vec`. Other allocation sources are allowed if the invariants are - /// upheld. - /// - /// Violating these may cause problems like corrupting the allocator's + /// The parts must satisfy the invariants described in the [memory layout] section, + /// with allocator [`Global`]. + /// + /// Violating them may cause problems like corrupting the allocator's /// internal data structures. For example it is normally **not** safe /// to build a `Vec` from a pointer to a C `char` array with length /// `size_t`, doing so is only safe if the array was initially allocated by @@ -594,8 +576,7 @@ impl Vec { /// function. /// /// [`String`]: crate::string::String - /// [`alloc::alloc`]: crate::alloc::alloc - /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc + /// [memory layout]: self#memory-layout /// /// # Examples /// @@ -655,29 +636,10 @@ impl Vec { /// /// # Safety /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * `ptr` must have been allocated using the global allocator, such as via - /// the [`alloc::alloc`] function. - /// * `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to be the capacity that the pointer was allocated with. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via `Vec`. Other allocation sources are allowed if the invariants are - /// upheld. - /// - /// Violating these may cause problems like corrupting the allocator's + /// The parts must satisfy the invariants described in the [memory layout] section, + /// with allocator [`Global`]. + /// + /// Violating them may cause problems like corrupting the allocator's /// internal data structures. For example it is normally **not** safe /// to build a `Vec` from a pointer to a C `char` array with length /// `size_t`, doing so is only safe if the array was initially allocated by @@ -696,8 +658,7 @@ impl Vec { /// function. /// /// [`String`]: crate::string::String - /// [`alloc::alloc`]: crate::alloc::alloc - /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc + /// [memory layout]: self#memory-layout /// /// # Examples /// @@ -920,7 +881,7 @@ const impl Vec { /// /// The vector will be able to hold at least `capacity` elements without /// reallocating. This method is allowed to allocate for more elements than - /// `capacity`. If `capacity` is zero, the vector will not allocate. + /// `capacity`. If `capacity` is zero, the vector [will not allocate]. /// /// It is important to note that although the returned vector has the /// minimum *capacity* specified, the vector will have a zero *length*. For @@ -930,11 +891,12 @@ const impl Vec { /// If it is important to know the exact allocated capacity of a `Vec`, /// always use the [`capacity`] method after construction. /// - /// For `Vec` where `T` is a zero-sized type, there will be no allocation - /// and the capacity will always be `usize::MAX`. + /// For `Vec` where `T` is a zero-sized type, the vector + /// [will not allocate], and the capacity will always be `usize::MAX`. /// /// [Capacity and reallocation]: #capacity-and-reallocation /// [`capacity`]: Vec::capacity + /// [will not allocate]: self#memory-layout /// /// # Panics /// @@ -1072,12 +1034,14 @@ impl Vec { /// /// The vector will be able to hold at least `capacity` elements without /// reallocating. This method is allowed to allocate for more elements than - /// `capacity`. If `capacity` is zero, the vector will not allocate. + /// `capacity`. If `capacity` is zero, the vector [will not allocate]. /// /// # Errors /// /// Returns an error if the capacity exceeds `isize::MAX` _bytes_, /// or if the allocator reports allocation failure. + /// + /// [will not allocate]: self#memory-layout #[inline] #[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] // #[unstable(feature = "try_with_capacity", issue = "91913")] @@ -1090,28 +1054,9 @@ impl Vec { /// /// # Safety /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * `ptr` must be [*currently allocated*] via the given allocator `alloc`. - /// * `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to [*fit*] the layout size that the pointer was allocated with. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via `Vec`. Other allocation sources are allowed if the invariants are - /// upheld. - /// - /// Violating these may cause problems like corrupting the allocator's + /// The parts must satisfy the invariants described in the [memory layout] section. + /// + /// Violating them may cause problems like corrupting the allocator's /// internal data structures. For example it is **not** safe /// to build a `Vec` from a pointer to a C `char` array with length `size_t`. /// It's also not safe to build one from a `Vec` and its length, because @@ -1126,9 +1071,8 @@ impl Vec { /// function. /// /// [`String`]: crate::string::String - /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc - /// [*currently allocated*]: crate::alloc::Allocator#currently-allocated-memory - /// [*fit*]: crate::alloc::Allocator#memory-fitting + /// [`allocate`]: crate::alloc::Allocator::allocate + /// [memory layout]: self#memory-layout /// /// # Examples /// @@ -1204,28 +1148,9 @@ impl Vec { /// /// # Safety /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * `ptr` must be [*currently allocated*] via the given allocator `alloc`. - /// * `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to [*fit*] the layout size that the pointer was allocated with. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via `Vec`. Other allocation sources are allowed if the invariants are - /// upheld. - /// - /// Violating these may cause problems like corrupting the allocator's + /// The parts must satisfy the invariants described in the [memory layout] section. + /// + /// Violating them may cause problems like corrupting the allocator's /// internal data structures. For example it is **not** safe /// to build a `Vec` from a pointer to a C `char` array with length `size_t`. /// It's also not safe to build one from a `Vec` and its length, because @@ -1241,8 +1166,7 @@ impl Vec { /// /// [`String`]: crate::string::String /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc - /// [*currently allocated*]: crate::alloc::Allocator#currently-allocated-memory - /// [*fit*]: crate::alloc::Allocator#memory-fitting + /// [memory layout]: self#memory-layout /// /// # Examples /// @@ -1902,7 +1826,7 @@ impl Vec { } /// Returns a raw pointer to the vector's buffer, or a dangling raw pointer - /// valid for zero sized reads if the vector didn't allocate. + /// valid for zero sized reads if the vector [didn't allocate]. /// /// The caller must ensure that the vector outlives the pointer this /// function returns, or else it will end up dangling. @@ -1954,6 +1878,7 @@ impl Vec { /// [`as_mut_ptr`]: Vec::as_mut_ptr /// [`as_ptr`]: Vec::as_ptr /// [`as_non_null`]: Vec::as_non_null + /// [didn't allocate]: self#memory-layout #[stable(feature = "vec_as_ptr", since = "1.37.0")] #[rustc_const_stable(feature = "const_vec_string_slice", since = "1.87.0")] #[rustc_never_returns_null_ptr] @@ -1966,7 +1891,7 @@ impl Vec { } /// Returns a raw mutable pointer to the vector's buffer, or a dangling - /// raw pointer valid for zero sized reads if the vector didn't allocate. + /// raw pointer valid for zero sized reads if the vector [didn't allocate]. /// /// The caller must ensure that the vector outlives the pointer this /// function returns, or else it will end up dangling. @@ -2038,6 +1963,7 @@ impl Vec { /// [`as_non_null`]: Vec::as_non_null /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc /// [`ManuallyDrop`]: core::mem::ManuallyDrop + /// [didn't allocate]: self#memory-layout #[stable(feature = "vec_as_ptr", since = "1.37.0")] #[rustc_const_stable(feature = "const_vec_string_slice", since = "1.87.0")] #[rustc_never_returns_null_ptr] @@ -2050,7 +1976,7 @@ impl Vec { } /// Returns a `NonNull` pointer to the vector's buffer, or a dangling - /// `NonNull` pointer valid for zero sized reads if the vector didn't allocate. + /// `NonNull` pointer valid for zero sized reads if the vector [didn't allocate]. /// /// The caller must ensure that the vector outlives the pointer this /// function returns, or else it will end up dangling. @@ -2105,6 +2031,7 @@ impl Vec { /// [`as_mut_ptr`]: Vec::as_mut_ptr /// [`as_ptr`]: Vec::as_ptr /// [`as_non_null`]: Vec::as_non_null + /// [didn't allocate]: self#memory-layout #[unstable(feature = "vec_as_non_null", issue = "157843")] #[rustc_const_unstable(feature = "vec_as_non_null", issue = "157843")] #[rustc_as_ptr] From 311c6e39ad6481a30ac3ef87735299fed86b6127 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 11 Oct 2026 20:42:55 +0200 Subject: [PATCH 05/20] Revert "Rollup merge of #159562 - Joel-Wwalker:154071-stdio-handle-cache, r=aapoalas" This reverts commit 556c0e625b9f24942a6ee2cc3ef1683c60868d6a, reversing changes made to 2cb8b181007fd50c394fbbe16b115e942c5b62ab. --- library/std/src/io/stdio.rs | 45 +--------------- library/std/src/sys/stdio/windows.rs | 77 ++++------------------------ 2 files changed, 13 insertions(+), 109 deletions(-) diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs index 0848f97e69d32..527671442fb72 100644 --- a/library/std/src/io/stdio.rs +++ b/library/std/src/io/stdio.rs @@ -95,27 +95,6 @@ const fn stderr_raw() -> StderrRaw { StderrRaw(stdio::Stderr::new()) } -#[cfg(windows)] -impl StdoutRaw { - /// Starts a new lock session: stream state cached per lock session (the - /// handle and its console mode) is re-queried at the next write, so that - /// changing the process stdio handles (e.g. with `SetStdHandle`) between - /// lock sessions keeps working. - #[inline] - fn refresh(&mut self) { - self.0.refresh(); - } -} - -#[cfg(windows)] -impl StderrRaw { - /// See `StdoutRaw::refresh`. - #[inline] - fn refresh(&mut self) { - self.0.refresh(); - } -} - impl Read for StdinRaw { fn read(&mut self, buf: &mut [u8]) -> io::Result { handle_ebadf(self.0.read(buf), || Ok(0)) @@ -790,19 +769,7 @@ impl Stdout { // Locks this handle with 'static lifetime. This depends on the // implementation detail that the underlying `ReentrantMutex` is // static. - let lock = StdoutLock { inner: self.inner.lock() }; - // A new lock session begins, so let Windows re-query cached stream - // state at the next write; other platforms cache nothing, and skip - // this entirely so their `lock()` is unchanged. Skipped also if the - // cell is already borrowed (a nested lock during an ongoing write); - // such a lock is part of the outer session anyway. - #[cfg(windows)] - { - if let Ok(mut w) = lock.inner.try_borrow_mut() { - w.get_mut().refresh(); - } - } - lock + StdoutLock { inner: self.inner.lock() } } } @@ -1034,15 +1001,7 @@ impl Stderr { // Locks this handle with 'static lifetime. This depends on the // implementation detail that the underlying `ReentrantMutex` is // static. - let lock = StderrLock { inner: self.inner.lock() }; - // See `Stdout::lock`: a new lock session begins (Windows only). - #[cfg(windows)] - { - if let Ok(mut w) = lock.inner.try_borrow_mut() { - w.refresh(); - } - } - lock + StderrLock { inner: self.inner.lock() } } } diff --git a/library/std/src/sys/stdio/windows.rs b/library/std/src/sys/stdio/windows.rs index d9c9290ed21a2..1339048a9c039 100644 --- a/library/std/src/sys/stdio/windows.rs +++ b/library/std/src/sys/stdio/windows.rs @@ -22,35 +22,12 @@ pub struct Stdin { pub struct Stdout { incomplete_utf8: IncompleteUtf8, - write_mode: Option, } pub struct Stderr { incomplete_utf8: IncompleteUtf8, - write_mode: Option, } -/// How to write to a standard stream, cached for the duration of a lock -/// session (see `Stdout::refresh`). Re-querying this on every lock -/// acquisition keeps `SetStdHandle` calls made between lock sessions working -/// (see #40490), while a held lock skips the per-write console queries that -/// otherwise dominate bulk writes (see #154071). -#[derive(Clone, Copy)] -enum WriteMode { - /// The stream is a pipe or file, or a console using the UTF-8 code page: - /// bytes are written out unchanged. - Passthrough(c::HANDLE), - /// The stream is a console using a non-UTF-8 code page: data is converted - /// to UTF-16 and written with `WriteConsoleW`. - Utf16Console(c::HANDLE), -} - -// SAFETY: a `HANDLE` stored here is only an OS handle value for a standard -// stream (it is never dereferenced), and handle values are valid -// process-wide, on any thread. -unsafe impl Send for WriteMode {} -unsafe impl Sync for WriteMode {} - struct IncompleteUtf8 { bytes: [u8; 4], len: u8, @@ -122,40 +99,21 @@ fn is_utf8_console() -> bool { false } -fn write( - handle_id: u32, - data: &[u8], - incomplete_utf8: &mut IncompleteUtf8, - write_mode: &mut Option, -) -> io::Result { +fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> io::Result { if data.is_empty() { return Ok(0); } - let mode = match *write_mode { - Some(mode) => mode, - None => { - let handle = get_handle(handle_id)?; - let mode = if !is_console(handle) || is_utf8_console() { - WriteMode::Passthrough(handle) - } else { - WriteMode::Utf16Console(handle) - }; - // Only cache success; if there is no handle, keep erroring on - // every write like before. - *write_mode = Some(mode); - mode - } - }; - - match mode { - WriteMode::Passthrough(handle) => unsafe { + let handle = get_handle(handle_id)?; + if !is_console(handle) || is_utf8_console() { + unsafe { let handle = Handle::from_raw_handle(handle); let ret = handle.write(data); let _ = handle.into_raw_handle(); // Don't close the handle ret - }, - WriteMode::Utf16Console(handle) => write_console_utf16(data, incomplete_utf8, handle), + } + } else { + write_console_utf16(data, incomplete_utf8, handle) } } @@ -481,21 +439,13 @@ impl IncompleteUtf8 { impl Stdout { pub const fn new() -> Stdout { - Stdout { incomplete_utf8: IncompleteUtf8::new(), write_mode: None } - } - - /// Forgets the cached stream state, so the next write re-queries the OS. - /// Called when a new lock session begins, so that changing the process - /// stdio handles (e.g. `SetStdHandle`) between lock sessions keeps - /// working (see #40490). - pub fn refresh(&mut self) { - self.write_mode = None; + Stdout { incomplete_utf8: IncompleteUtf8::new() } } } impl io::Write for Stdout { fn write(&mut self, buf: &[u8]) -> io::Result { - write(c::STD_OUTPUT_HANDLE, buf, &mut self.incomplete_utf8, &mut self.write_mode) + write(c::STD_OUTPUT_HANDLE, buf, &mut self.incomplete_utf8) } fn flush(&mut self) -> io::Result<()> { @@ -505,18 +455,13 @@ impl io::Write for Stdout { impl Stderr { pub const fn new() -> Stderr { - Stderr { incomplete_utf8: IncompleteUtf8::new(), write_mode: None } - } - - /// See `Stdout::refresh`. - pub fn refresh(&mut self) { - self.write_mode = None; + Stderr { incomplete_utf8: IncompleteUtf8::new() } } } impl io::Write for Stderr { fn write(&mut self, buf: &[u8]) -> io::Result { - write(c::STD_ERROR_HANDLE, buf, &mut self.incomplete_utf8, &mut self.write_mode) + write(c::STD_ERROR_HANDLE, buf, &mut self.incomplete_utf8) } fn flush(&mut self) -> io::Result<()> { From 9a20a071c20fe5bd68004b5d1aee909c21c46e33 Mon Sep 17 00:00:00 2001 From: Raushan singh Date: Sat, 12 Sep 2026 13:53:31 +0000 Subject: [PATCH 06/20] test(parser): add baseline for missing turbofish with lifetime in struct expr --- .../recover/missing-turbofish-lifetime.rs | 30 ++++++++++ .../recover/missing-turbofish-lifetime.stderr | 57 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime.rs create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime.stderr diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.rs b/tests/ui/parser/recover/missing-turbofish-lifetime.rs new file mode 100644 index 0000000000000..596b197a317da --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.rs @@ -0,0 +1,30 @@ +struct Struct<'a> { + string: &'a str, +} + +fn struct_with_reserved_lifetime() { + let _ = Struct<'_> { + //~^ ERROR labels cannot use keyword names + //~| ERROR expected `while`, `for`, `loop` or `{` after a label + //~| ERROR comparison operators cannot be chained + string: "", + }; +} + +fn struct_with_named_lifetime() { + let _ = Struct<'a> { + //~^ ERROR expected `while`, `for`, `loop` or `{` after a label + //~| ERROR comparison operators cannot be chained + string: "", + }; +} + +fn struct_with_multichar_lifetime() { + let _ = Struct<'abc> { + //~^ ERROR expected `while`, `for`, `loop` or `{` after a label + //~| ERROR comparison operators cannot be chained + string: "", + }; +} + +fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.stderr b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr new file mode 100644 index 0000000000000..e14d9d6aa464c --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr @@ -0,0 +1,57 @@ +error: labels cannot use keyword names + --> $DIR/missing-turbofish-lifetime.rs:6:20 + | +LL | let _ = Struct<'_> { + | ^^ + +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime.rs:6:22 + | +LL | let _ = Struct<'_> { + | ^ expected `while`, `for`, `loop` or `{` after a label + | +help: add `'` to close the char literal + | +LL | let _ = Struct<'_'> { + | + + +error: comparison operators cannot be chained + --> $DIR/missing-turbofish-lifetime.rs:6:19 + | +LL | let _ = Struct<'_> { + | ^ ^ + +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime.rs:15:22 + | +LL | let _ = Struct<'a> { + | ^ expected `while`, `for`, `loop` or `{` after a label + | +help: add `'` to close the char literal + | +LL | let _ = Struct<'a'> { + | + + +error: comparison operators cannot be chained + --> $DIR/missing-turbofish-lifetime.rs:15:19 + | +LL | let _ = Struct<'a> { + | ^ ^ + +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime.rs:23:24 + | +LL | let _ = Struct<'abc> { + | ^ expected `while`, `for`, `loop` or `{` after a label + +error: comparison operators cannot be chained + --> $DIR/missing-turbofish-lifetime.rs:23:19 + | +LL | let _ = Struct<'abc> { + | ^ ^ + | + = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + = help: or use `(...)` if you meant to specify fn arguments + +error: aborting due to 7 previous errors + From 508d2f42744343a6096b0e642565c9c919dadb58 Mon Sep 17 00:00:00 2001 From: Robin <1187338299@qq.com> Date: Tue, 13 Oct 2026 18:00:21 +0800 Subject: [PATCH 07/20] test DSL eagerly handle false constraints --- compiler/rustc_ast/src/ast.rs | 1 + compiler/rustc_ast_lowering/src/item.rs | 3 ++- compiler/rustc_hir/src/hir.rs | 1 + compiler/rustc_hir/src/intravisit.rs | 2 +- .../rustc_hir_analysis/src/check/wfcheck.rs | 17 ++++++++++++++++- compiler/rustc_hir_analysis/src/collect.rs | 4 ++-- compiler/rustc_parse/src/parser/item.rs | 4 +++- .../test-forall-evaluates-to-false.rs | 15 +++++++++++++++ .../test-forall-evaluates-to-false.stderr | 8 ++++++++ .../test-top-level-false-constraint.rs | 11 +++++++++++ .../test-top-level-false-constraint.stderr | 8 ++++++++ 11 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.rs create mode 100644 tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.stderr create mode 100644 tests/ui/assumptions_on_binders/test-top-level-false-constraint.rs create mode 100644 tests/ui/assumptions_on_binders/test-top-level-false-constraint.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 4c7ae87556f76..4effdd42f347a 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -4073,6 +4073,7 @@ pub struct TestBinderConstraints { #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct TestBinderBody { + pub span: Span, pub foralls: ThinVec, pub exists: ThinVec, pub constraints: Vec, diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index babca94628891..8203e6f98bd4f 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -2005,7 +2005,8 @@ impl<'hir> LoweringContext<'_, 'hir> { .iter() .map(|predicate| self.lower_where_predicate(predicate, &[], &mut dedup_map)), ); - hir::TestBinderBody { foralls, exists, constraints, predicates } + let span = self.lower_span(body.span); + hir::TestBinderBody { foralls, exists, constraints, predicates, span } } fn lower_test_binder_forall( diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index dfcb8871fe982..70702c5d42b59 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -4527,6 +4527,7 @@ impl FnHeader { #[derive(Debug, Clone, Copy, StableHash)] pub struct TestBinderBody<'hir> { + pub span: Span, pub foralls: &'hir [TestBinderForall<'hir>], pub exists: &'hir [TestBinderExists<'hir>], /// Constraints to be inserted directly into constraint storage to be proven diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index ab81862b74e32..ba82a1adc7d48 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -1600,7 +1600,7 @@ pub fn walk_test_binder_body<'v, V: Visitor<'v>>( visitor: &mut V, body: &'v TestBinderBody<'v>, ) -> V::Result { - let TestBinderBody { foralls, exists, constraints, predicates } = body; + let TestBinderBody { span: _, foralls, exists, constraints, predicates } = body; walk_list!(visitor, visit_test_binder_forall, *foralls); walk_list!(visitor, visit_test_binder_exists, *exists); try_visit!(visitor.visit_test_binder_constraint(&constraints)); diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 0599f036848c5..1aa194d7e0013 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2379,7 +2379,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { #[instrument(level = "debug", skip(self))] pub(super) fn check_test_binder_body(&self, body: TestBinderBody<'tcx>) { - let TestBinderBody { foralls, exists, constraints, predicates } = body; + let TestBinderBody { span, foralls, exists, constraints, predicates } = body; if !predicates.is_empty() { for (predicate, span) in predicates { let cause = traits::ObligationCause::misc(span, self.body_def_id); @@ -2399,6 +2399,13 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { Ok(()) => constraints, Err(_guar) => ty::region_constraint::RegionConstraint::new_true(), }; + if constraints.is_false() { + self.tcx() + .dcx() + .struct_span_err(span, "top level false constraints are not supported") + .emit(); + return; + } self.infcx.register_solver_region_constraint(constraints); @@ -2478,6 +2485,13 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { u, ) .with_spans(forall.span); + if constraint.is_false() { + self.tcx().dcx().struct_span_err(forall.span, "forall evaluated to false").emit(); + self.infcx.overwrite_solver_region_constraint( + ty::region_constraint::RegionConstraint::new_true(), + ); + return; + } if let Some(assert_on_exit) = &forall.assert_on_exit { self.check_test_binder_region_constraints(forall.span, assert_on_exit, &constraint); } @@ -2797,6 +2811,7 @@ struct RedundantLifetimeArgsLint<'tcx> { #[derive(Clone, Debug, TypeFoldable, TypeVisitable)] pub(crate) struct TestBinderBody<'tcx> { + pub span: Span, pub foralls: Vec>, pub exists: Vec>, /// Constraints to be inserted directly into constraint storage to be proven diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 1965ade38204e..7f181ef2e7e04 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -328,7 +328,7 @@ impl<'tcx> ItemCtxt<'tcx> { &self, item: &hir::TestBinderBody<'tcx>, ) -> TestBinderBody<'tcx> { - let hir::TestBinderBody { foralls, exists, constraints, predicates } = item; + let hir::TestBinderBody { span, foralls, exists, constraints, predicates } = item; let foralls = foralls.iter().map(|forall| self.lower_test_binder_forall(forall)).collect(); let exists = exists.iter().map(|exists| self.lower_test_binder_exists(exists)).collect(); let constraints = self.lower_test_binder_constraint(&constraints); @@ -337,7 +337,7 @@ impl<'tcx> ItemCtxt<'tcx> { clauses_of::where_predicate_clauses(self, predicate, &mut clauses); } let predicates = clauses.into_iter().map(|(c, span)| (c.kind(), span)).collect(); - TestBinderBody { foralls, exists, constraints, predicates } + TestBinderBody { span: *span, foralls, exists, constraints, predicates } } #[instrument(level = "debug", skip(self), ret)] diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 6d3d79bbe2b5c..e7e0434c2fa2e 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -2717,6 +2717,7 @@ impl<'a> Parser<'a> { let mut exists = ThinVec::new(); let mut constraints = Vec::new(); let mut predicates = Vec::new(); + let mut span = self.token.span; self.parse_delim_comma_seq(exp!(OpenBrace), exp!(CloseBrace), |this| { match this.token.ident() { Some((Ident { name: sym::predicates, .. }, IdentKind::Normal)) => { @@ -2735,7 +2736,8 @@ impl<'a> Parser<'a> { } Ok(()) })?; - Ok(TestBinderBody { foralls, exists, constraints, predicates }) + span = span.to(self.prev_token.span); + Ok(TestBinderBody { foralls, exists, constraints, predicates, span }) } pub fn parse_test_binder_forall(&mut self) -> PResult<'a, TestBinderForall> { diff --git a/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.rs b/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.rs new file mode 100644 index 0000000000000..c98afb8474448 --- /dev/null +++ b/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.rs @@ -0,0 +1,15 @@ +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +#![feature(test_binder_constraints, non_lifetime_binders)] +#![expect(incomplete_features)] + +core::test_binder_constraints! { + impl<'a> { + forall<'b> { + //~^ ERROR forall evaluated to false + 'b: 'a + } + } +} + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.stderr b/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.stderr new file mode 100644 index 0000000000000..995363d521621 --- /dev/null +++ b/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.stderr @@ -0,0 +1,8 @@ +error: forall evaluated to false + --> $DIR/test-forall-evaluates-to-false.rs:8:9 + | +LL | forall<'b> { + | ^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/assumptions_on_binders/test-top-level-false-constraint.rs b/tests/ui/assumptions_on_binders/test-top-level-false-constraint.rs new file mode 100644 index 0000000000000..cdd7eb7a321bc --- /dev/null +++ b/tests/ui/assumptions_on_binders/test-top-level-false-constraint.rs @@ -0,0 +1,11 @@ +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +#![feature(test_binder_constraints, non_lifetime_binders)] +#![expect(incomplete_features)] + +core::test_binder_constraints! { + impl { or {} } + //~^ ERROR top level false constraints are not supported +} + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/test-top-level-false-constraint.stderr b/tests/ui/assumptions_on_binders/test-top-level-false-constraint.stderr new file mode 100644 index 0000000000000..fb5380879fe3d --- /dev/null +++ b/tests/ui/assumptions_on_binders/test-top-level-false-constraint.stderr @@ -0,0 +1,8 @@ +error: top level false constraints are not supported + --> $DIR/test-top-level-false-constraint.rs:7:10 + | +LL | impl { or {} } + | ^^^^^^^^^ + +error: aborting due to 1 previous error + From ebca0756e0812a8c971788d2df71bcec70f9aa66 Mon Sep 17 00:00:00 2001 From: Raushan singh Date: Sat, 3 Oct 2026 03:43:17 +0000 Subject: [PATCH 08/20] fix(parser): recover missing turbofish for lifetimes in exprs --- compiler/rustc_parse/src/diagnostics.rs | 9 ++ .../rustc_parse/src/parser/diagnostics.rs | 7 + compiler/rustc_parse/src/parser/path.rs | 17 +++ tests/ui/parser/issues/issue-93282.rs | 8 +- tests/ui/parser/issues/issue-93282.stderr | 47 ++++--- .../missing-turbofish-lifetime-macro.rs | 16 +++ .../missing-turbofish-lifetime-macro.stderr | 20 +++ .../recover/missing-turbofish-lifetime.fixed | 87 ++++++++++++ .../recover/missing-turbofish-lifetime.rs | 75 +++++++++-- .../recover/missing-turbofish-lifetime.stderr | 127 +++++++++++++----- .../require-parens-for-chained-comparison.rs | 16 +-- ...quire-parens-for-chained-comparison.stderr | 67 ++++----- 12 files changed, 375 insertions(+), 121 deletions(-) create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime.fixed diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index 54d91eca01327..a942d6b060dab 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -1712,6 +1712,15 @@ pub(crate) struct GenericParamsWithoutAngleBrackets { pub sugg: GenericParamsWithoutAngleBracketsSugg, } +#[derive(Diagnostic)] +#[diag("use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments")] +pub(crate) struct MissingTurbofishForLifetime { + #[primary_span] + pub span: Span, + #[suggestion("add `::`", style = "verbose", code = "::", applicability = "machine-applicable")] + pub suggest_turbofish: Span, +} + #[derive(Subdiagnostic)] #[multipart_suggestion( "surround the type parameters with angle brackets", diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 14ea328601a02..18221f2fb8d7e 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -1143,6 +1143,13 @@ impl<'a> Parser<'a> { } } + pub(super) fn recover_missing_turbofish_for_lifetimes(&self, lt_span: Span) { + self.dcx().emit_err(crate::diagnostics::MissingTurbofishForLifetime { + span: lt_span, + suggest_turbofish: lt_span.shrink_to_lo(), + }); + } + /// When writing a turbofish with multiple type parameters missing the leading `::`, we will /// encounter a parse error when encountering the first `,`. pub(super) fn check_mistyped_turbofish_with_multiple_type_params( diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 99bf592c859fb..b9ba5532f5708 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -303,9 +303,20 @@ impl<'a> Parser<'a> { is_args_start(&this.token) }; + // A lifetime followed by `>` or `,` can never start a valid expression, so `<` here + // must be the start of generic arguments with a missing `::`. + let is_missing_turbofish_for_lifetime = |this: &mut Self| { + this.token == token::Lt + && this.may_recover() + && style == PathStyle::Expr + && this.look_ahead(1, |t| t.is_lifetime()) + && this.look_ahead(2, |t| matches!(t.kind, token::Gt | token::Comma)) + }; + Ok( if style == PathStyle::Type && check_args_start(self) || style != PathStyle::Mod && self.check_path_sep_and_look_ahead(is_args_start) + || is_missing_turbofish_for_lifetime(self) { // We use `style == PathStyle::Expr` to check if this is in a recursion or not. If // it isn't, then we reset the unmatched angle bracket count as we're about to start @@ -314,12 +325,18 @@ impl<'a> Parser<'a> { self.unmatched_angle_bracket_count = 0; } + let missing_turbofish = + style == PathStyle::Expr && !self.check_path_sep_and_look_ahead(is_args_start); + // Generic arguments are found - `<`, `(`, `::<` or `::(`. // First, eat `::` if it exists. let _ = self.eat_path_sep(); let lo = self.token.span; let args = if self.eat_lt() { + if missing_turbofish { + self.recover_missing_turbofish_for_lifetimes(lo); + } // `<'a, T, A = U>` let args = self.parse_angle_args_with_leading_angle_bracket_recovery( style, diff --git a/tests/ui/parser/issues/issue-93282.rs b/tests/ui/parser/issues/issue-93282.rs index 274245f1a465f..5b96969f5b971 100644 --- a/tests/ui/parser/issues/issue-93282.rs +++ b/tests/ui/parser/issues/issue-93282.rs @@ -1,7 +1,10 @@ fn main() { + //~^ HELP consider introducing lifetime `'a` here f<'a,> - //~^ ERROR expected - //~| ERROR expected + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + //~| ERROR use of undeclared lifetime name `'a` + //~| ERROR cannot find value `f` in this scope } fn bar(a: usize, b: usize) -> usize { @@ -12,5 +15,6 @@ fn foo() { let x = 1; bar('y, x); //~^ ERROR expected + //~| HELP add `'` to close the char literal //~| ERROR mismatched types } diff --git a/tests/ui/parser/issues/issue-93282.stderr b/tests/ui/parser/issues/issue-93282.stderr index c6140bb821e48..1daacff52f066 100644 --- a/tests/ui/parser/issues/issue-93282.stderr +++ b/tests/ui/parser/issues/issue-93282.stderr @@ -1,27 +1,16 @@ -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/issue-93282.rs:2:9 - | -LL | f<'a,> - | ^ expected `while`, `for`, `loop` or `{` after a label - | -help: add `'` to close the char literal - | -LL | f<'a',> - | + - -error: expected one of `.`, `:`, `;`, `?`, `for`, `loop`, `while`, `}`, or an operator, found `,` - --> $DIR/issue-93282.rs:2:9 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/issue-93282.rs:3:6 | LL | f<'a,> - | ^ expected one of 9 possible tokens + | ^ | -help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments +help: add `::` | LL | f::<'a,> | ++ error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/issue-93282.rs:13:11 + --> $DIR/issue-93282.rs:16:11 | LL | bar('y, x); | ^ expected `while`, `for`, `loop` or `{` after a label @@ -31,8 +20,25 @@ help: add `'` to close the char literal LL | bar('y', x); | + +error[E0261]: use of undeclared lifetime name `'a` + --> $DIR/issue-93282.rs:3:7 + | +LL | f<'a,> + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'a` here + | +LL | fn main<'a>() { + | ++++ + +error[E0425]: cannot find value `f` in this scope + --> $DIR/issue-93282.rs:3:5 + | +LL | f<'a,> + | ^ not found in this scope + error[E0308]: mismatched types - --> $DIR/issue-93282.rs:13:9 + --> $DIR/issue-93282.rs:16:9 | LL | bar('y, x); | --- ^^ expected `usize`, found `char` @@ -40,11 +46,12 @@ LL | bar('y, x); | arguments to this function are incorrect | note: function defined here - --> $DIR/issue-93282.rs:7:4 + --> $DIR/issue-93282.rs:10:4 | LL | fn bar(a: usize, b: usize) -> usize { | ^^^ -------- -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors -For more information about this error, try `rustc --explain E0308`. +Some errors have detailed explanations: E0261, E0308, E0425. +For more information about an error, try `rustc --explain E0261`. diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs b/tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs new file mode 100644 index 0000000000000..366fc8ddfd982 --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs @@ -0,0 +1,16 @@ +macro_rules! m { + ($e:expr) => { "expr" }; + (a < $l:lifetime >) => { "lifetime" }; +} + +fn test_macro() { + // Ensure turbofish recovery is disabled during macro matching to avoid spurious diagnostics. + // The second arm does NOT match; the expr-fragment error is a hard error. + let _ = m!(a < 'x >); + //~^ ERROR comparison operators cannot be chained + //~| HELP use `::<...>` instead of `<...>` + //~| HELP or use `(...)` + //~| ERROR expected `while`, `for`, `loop` or `{` after a label +} + +fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr b/tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr new file mode 100644 index 0000000000000..a05d7e668a521 --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr @@ -0,0 +1,20 @@ +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime-macro.rs:9:23 + | +LL | let _ = m!(a < 'x >); + | ^ expected `while`, `for`, `loop` or `{` after a label + +error: comparison operators cannot be chained + --> $DIR/missing-turbofish-lifetime-macro.rs:9:18 + | +LL | ($e:expr) => { "expr" }; + | ------- while parsing argument for this `expr` macro fragment +... +LL | let _ = m!(a < 'x >); + | ^ ^ + | + = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + = help: or use `(...)` if you meant to specify fn arguments + +error: aborting due to 2 previous errors + diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.fixed b/tests/ui/parser/recover/missing-turbofish-lifetime.fixed new file mode 100644 index 0000000000000..f5d8e2e63e6d5 --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.fixed @@ -0,0 +1,87 @@ +//! Regression test for +//! +//! Test that omitting the turbofish when passing a lifetime to an expression +//! emits a targeted suggestion rather than a cascading syntax error. + +#![allow(dead_code)] + +//@ run-rustfix + +struct Struct<'a> { + string: &'a str, +} + +fn struct_with_reserved_lifetime() { + let _ = Struct::<'_> { + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + string: "", + }; +} + +fn struct_with_named_lifetime<'a>() { + let _ = Struct::<'a> { + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + string: "", + }; +} + +fn struct_with_multichar_lifetime<'abc>() { + let _ = Struct::<'abc> { + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + string: "", + }; +} + +struct TupleStruct<'a>(&'a str); + +fn tuple_struct_with_reserved_lifetime() { + let _ = TupleStruct::<'_>(""); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` +} + +struct Wrapper<'a>(&'a str); +impl<'a> Wrapper<'a> { fn new(s: &'a str) -> Self { Self(s) } } + +struct Struct2<'a> { x: i32, _p: std::marker::PhantomData<&'a ()> } +impl<'a> Struct2<'a> { fn method(&self) -> Option<()> { Some(()) } } + +fn f<'a: 'a>() {} + +struct StructT<'a, T> { a: &'a str, b: T } +struct Struct3<'a, 'b> { a: &'a str, b: &'b str } + +fn chaining_cases<'a, 'b, T>() -> Option<()> { + let _ = Wrapper::<'a>::new("hi"); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method(); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method()?; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + f::<'_>(); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + // struct with two lifetimes + let _ = Struct3::<'a, 'b> { a: "hi", b: "hi" }; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + // struct with lifetime and type + let _ = StructT::<'a, &str> { a: "hi", b: "hi" }; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + Some(()) +} + +fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.rs b/tests/ui/parser/recover/missing-turbofish-lifetime.rs index 596b197a317da..1bc9e97878739 100644 --- a/tests/ui/parser/recover/missing-turbofish-lifetime.rs +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.rs @@ -1,30 +1,87 @@ +//! Regression test for +//! +//! Test that omitting the turbofish when passing a lifetime to an expression +//! emits a targeted suggestion rather than a cascading syntax error. + +#![allow(dead_code)] + +//@ run-rustfix + struct Struct<'a> { string: &'a str, } fn struct_with_reserved_lifetime() { let _ = Struct<'_> { - //~^ ERROR labels cannot use keyword names - //~| ERROR expected `while`, `for`, `loop` or `{` after a label - //~| ERROR comparison operators cannot be chained + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` string: "", }; } -fn struct_with_named_lifetime() { +fn struct_with_named_lifetime<'a>() { let _ = Struct<'a> { - //~^ ERROR expected `while`, `for`, `loop` or `{` after a label - //~| ERROR comparison operators cannot be chained + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` string: "", }; } -fn struct_with_multichar_lifetime() { +fn struct_with_multichar_lifetime<'abc>() { let _ = Struct<'abc> { - //~^ ERROR expected `while`, `for`, `loop` or `{` after a label - //~| ERROR comparison operators cannot be chained + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` string: "", }; } +struct TupleStruct<'a>(&'a str); + +fn tuple_struct_with_reserved_lifetime() { + let _ = TupleStruct<'_>(""); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` +} + +struct Wrapper<'a>(&'a str); +impl<'a> Wrapper<'a> { fn new(s: &'a str) -> Self { Self(s) } } + +struct Struct2<'a> { x: i32, _p: std::marker::PhantomData<&'a ()> } +impl<'a> Struct2<'a> { fn method(&self) -> Option<()> { Some(()) } } + +fn f<'a: 'a>() {} + +struct StructT<'a, T> { a: &'a str, b: T } +struct Struct3<'a, 'b> { a: &'a str, b: &'b str } + +fn chaining_cases<'a, 'b, T>() -> Option<()> { + let _ = Wrapper<'a>::new("hi"); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method(); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method()?; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + f<'_>(); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + // struct with two lifetimes + let _ = Struct3<'a, 'b> { a: "hi", b: "hi" }; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + // struct with lifetime and type + let _ = StructT<'a, &str> { a: "hi", b: "hi" }; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + Some(()) +} + fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.stderr b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr index e14d9d6aa464c..faea5463bdfd4 100644 --- a/tests/ui/parser/recover/missing-turbofish-lifetime.stderr +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr @@ -1,57 +1,112 @@ -error: labels cannot use keyword names - --> $DIR/missing-turbofish-lifetime.rs:6:20 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:15:19 | LL | let _ = Struct<'_> { - | ^^ + | ^ + | +help: add `::` + | +LL | let _ = Struct::<'_> { + | ++ -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/missing-turbofish-lifetime.rs:6:22 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:23:19 | -LL | let _ = Struct<'_> { - | ^ expected `while`, `for`, `loop` or `{` after a label +LL | let _ = Struct<'a> { + | ^ | -help: add `'` to close the char literal +help: add `::` | -LL | let _ = Struct<'_'> { - | + +LL | let _ = Struct::<'a> { + | ++ -error: comparison operators cannot be chained - --> $DIR/missing-turbofish-lifetime.rs:6:19 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:31:19 | -LL | let _ = Struct<'_> { - | ^ ^ +LL | let _ = Struct<'abc> { + | ^ + | +help: add `::` + | +LL | let _ = Struct::<'abc> { + | ++ -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/missing-turbofish-lifetime.rs:15:22 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:41:24 | -LL | let _ = Struct<'a> { - | ^ expected `while`, `for`, `loop` or `{` after a label +LL | let _ = TupleStruct<'_>(""); + | ^ | -help: add `'` to close the char literal +help: add `::` | -LL | let _ = Struct<'a'> { - | + +LL | let _ = TupleStruct::<'_>(""); + | ++ -error: comparison operators cannot be chained - --> $DIR/missing-turbofish-lifetime.rs:15:19 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:58:20 | -LL | let _ = Struct<'a> { - | ^ ^ +LL | let _ = Wrapper<'a>::new("hi"); + | ^ + | +help: add `::` + | +LL | let _ = Wrapper::<'a>::new("hi"); + | ++ -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/missing-turbofish-lifetime.rs:23:24 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:62:20 | -LL | let _ = Struct<'abc> { - | ^ expected `while`, `for`, `loop` or `{` after a label +LL | let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method(); + | ^ + | +help: add `::` + | +LL | let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method(); + | ++ -error: comparison operators cannot be chained - --> $DIR/missing-turbofish-lifetime.rs:23:19 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:66:20 | -LL | let _ = Struct<'abc> { - | ^ ^ +LL | let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method()?; + | ^ + | +help: add `::` + | +LL | let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method()?; + | ++ + +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:70:6 + | +LL | f<'_>(); + | ^ + | +help: add `::` + | +LL | f::<'_>(); + | ++ + +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:75:20 + | +LL | let _ = Struct3<'a, 'b> { a: "hi", b: "hi" }; + | ^ + | +help: add `::` + | +LL | let _ = Struct3::<'a, 'b> { a: "hi", b: "hi" }; + | ++ + +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:80:20 + | +LL | let _ = StructT<'a, &str> { a: "hi", b: "hi" }; + | ^ + | +help: add `::` | - = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments - = help: or use `(...)` if you meant to specify fn arguments +LL | let _ = StructT::<'a, &str> { a: "hi", b: "hi" }; + | ++ -error: aborting due to 7 previous errors +error: aborting due to 10 previous errors diff --git a/tests/ui/parser/require-parens-for-chained-comparison.rs b/tests/ui/parser/require-parens-for-chained-comparison.rs index 6152fff6c0365..4162e9f27f36d 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.rs +++ b/tests/ui/parser/require-parens-for-chained-comparison.rs @@ -20,18 +20,14 @@ fn main() { //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments let _ = f<'_, i8>(); - //~^ ERROR expected one of - //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments - //~| ERROR expected - //~| HELP add `'` to close the char literal - //~| ERROR labels cannot use keyword names + //~^ ERROR cannot find function `f` in this scope + //~| ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` f<'_>(); - //~^ ERROR comparison operators cannot be chained - //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments - //~| ERROR expected - //~| HELP add `'` to close the char literal - //~| ERROR labels cannot use keyword names + //~^ ERROR cannot find function `f` in this scope + //~| ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` let _ = f; //~^ ERROR comparison operators cannot be chained diff --git a/tests/ui/parser/require-parens-for-chained-comparison.stderr b/tests/ui/parser/require-parens-for-chained-comparison.stderr index 9edfae3625027..50f66927db118 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.stderr +++ b/tests/ui/parser/require-parens-for-chained-comparison.stderr @@ -53,64 +53,30 @@ help: use `::<...>` instead of `<...>` to specify lifetime, type, or const argum LL | let _ = f::(); | ++ -error: labels cannot use keyword names - --> $DIR/require-parens-for-chained-comparison.rs:22:15 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/require-parens-for-chained-comparison.rs:22:14 | LL | let _ = f<'_, i8>(); - | ^^ - -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/require-parens-for-chained-comparison.rs:22:17 - | -LL | let _ = f<'_, i8>(); - | ^ expected `while`, `for`, `loop` or `{` after a label - | -help: add `'` to close the char literal + | ^ | -LL | let _ = f<'_', i8>(); - | + - -error: expected one of `.`, `:`, `;`, `?`, `else`, `for`, `loop`, `while`, or an operator, found `,` - --> $DIR/require-parens-for-chained-comparison.rs:22:17 - | -LL | let _ = f<'_, i8>(); - | ^ expected one of 9 possible tokens - | -help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments +help: add `::` | LL | let _ = f::<'_, i8>(); | ++ -error: labels cannot use keyword names - --> $DIR/require-parens-for-chained-comparison.rs:29:7 - | -LL | f<'_>(); - | ^^ - -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/require-parens-for-chained-comparison.rs:29:9 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/require-parens-for-chained-comparison.rs:27:6 | LL | f<'_>(); - | ^ expected `while`, `for`, `loop` or `{` after a label + | ^ | -help: add `'` to close the char literal - | -LL | f<'_'>(); - | + - -error: comparison operators cannot be chained - --> $DIR/require-parens-for-chained-comparison.rs:29:6 - | -LL | f<'_>(); - | ^ ^ - | -help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments +help: add `::` | LL | f::<'_>(); | ++ error: comparison operators cannot be chained - --> $DIR/require-parens-for-chained-comparison.rs:36:14 + --> $DIR/require-parens-for-chained-comparison.rs:32:14 | LL | let _ = f; | ^ ^ @@ -118,5 +84,18 @@ LL | let _ = f; = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments = help: or use `(...)` if you meant to specify fn arguments -error: aborting due to 12 previous errors +error[E0425]: cannot find function `f` in this scope + --> $DIR/require-parens-for-chained-comparison.rs:22:13 + | +LL | let _ = f<'_, i8>(); + | ^ not found in this scope + +error[E0425]: cannot find function `f` in this scope + --> $DIR/require-parens-for-chained-comparison.rs:27:5 + | +LL | f<'_>(); + | ^ not found in this scope + +error: aborting due to 10 previous errors +For more information about this error, try `rustc --explain E0425`. From eb85bf4a4e9e6fc4fb968ffb0f494b8ccec20328 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Oct 2026 16:12:38 +0200 Subject: [PATCH 09/20] [rustdoc] Improve display of `rustc_must_implement_one_of` note and generate links to the mentioned methods --- src/librustdoc/html/render/print_item.rs | 14 ++++++++++---- tests/rustdoc-html/impl/must_implement_one_of.rs | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index f6d5ada07f364..544d57d92613c 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -960,11 +960,17 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: )?; if let Some(list) = must_implement_one_of_functions.as_deref() { - write!( - w, - "
At least one of the `{}` methods is required.
", - fmt::from_fn(|f| list.iter().joined("`, `", f)), + w.write_str( + "\ +
At least one of the ", )?; + for (pos, method) in list.iter().enumerate() { + if pos > 0 { + w.write_str(", ")?; + } + write!(w, "{method}")?; + } + w.write_str(" methods is required.
")?; } for m in required_methods { diff --git a/tests/rustdoc-html/impl/must_implement_one_of.rs b/tests/rustdoc-html/impl/must_implement_one_of.rs index ab965bab35667..2d905df0a4566 100644 --- a/tests/rustdoc-html/impl/must_implement_one_of.rs +++ b/tests/rustdoc-html/impl/must_implement_one_of.rs @@ -3,7 +3,7 @@ #[rustc_must_implement_one_of(a, b)] //@ matches c/trait.Trait.html '//*[@class="stab must_implement"]' \ -// 'At least one of the `a`, `b` methods is required.$' +// 'At least one of the a, b methods is required.$' pub trait Trait { fn a() {} fn b() {} From fe974855d46941397c22c5901457bd8d7fff1296 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Oct 2026 16:13:22 +0200 Subject: [PATCH 10/20] [rustdoc] Generate sidebar link for "required methods" even if there are none if the `rustc_must_implement_one_of` attribute is present --- src/librustdoc/html/render/sidebar.rs | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 3befbf2cff8ff..e0bd1fc9792e6 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -337,30 +337,39 @@ fn sidebar_trait<'a>( ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const), ("required-associated-types", "Required Associated Types", req_assoc), ("provided-associated-types", "Provided Associated Types", prov_assoc), - ("required-methods", "Required Methods", req_method), - ("provided-methods", "Provided Methods", prov_method), - ("foreign-impls", "Implementations on Foreign Types", foreign_impls), ] .into_iter() .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), "", items)), ); + + let tcx = cx.tcx(); + // Because of `rustc_must_implement_one_of`, we might have a trait with no "required methods" + // but still want the link in the sidebar, so we handle it on its own. + let mut req_method_link = + LinkBlock::new(Link::new("required-methods", "Required Methods"), "", req_method); + let rustc_middle::ty::TraitDef { must_implement_one_of, .. } = tcx.trait_def(t.def_id); + req_method_link.force_render = must_implement_one_of.is_some(); + blocks.push(req_method_link); + + blocks.push(LinkBlock::new(Link::new("provided-methods", "Provided Methods"), "", prov_method)); + sidebar_assoc_items(cx, it, blocks, deref_id_map); - // Move the foreign impls block after dyn compatibility note to match the order of the headings - // in the main content. - let foreign_impls_block = blocks.pop_if(|b| b.heading.href == "foreign-impls"); - if !t.is_dyn_compatible(cx.tcx()) { + if !t.is_dyn_compatible(tcx) { blocks.push(LinkBlock::forced( Link::new("dyn-compatibility", "Dyn Compatibility"), "dyn-compatibility-note", )); } - if let Some(foreign_impls_block) = foreign_impls_block { - blocks.push(foreign_impls_block); - } + + blocks.push(LinkBlock::new( + Link::new("foreign-impls", "Implementations on Foreign Types"), + "", + foreign_impls, + )); blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors"), "impl")); - if t.is_auto(cx.tcx()) { + if t.is_auto(tcx) { blocks.push(LinkBlock::forced( Link::new("synthetic-implementors", "Auto Implementors"), "impl-auto", From 9b08cd0a18c01f87433654d6f0f49cb97fc63189 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 13 Oct 2026 09:21:07 -0600 Subject: [PATCH 11/20] Windows FileExt trait doesn't need to handle ErrorKind::Interrupted; test improvements for same --- library/std/src/fs/tests.rs | 250 +++++++++++++++++++++---------- library/std/src/os/windows/fs.rs | 9 +- 2 files changed, 174 insertions(+), 85 deletions(-) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 7fb976d5ef3da..605ad25270a63 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -852,102 +852,175 @@ fn file_test_windows_fileext_trait_case_1() { #[test] #[cfg(windows)] fn file_test_windows_fileext_trait_case_2() { + use crate::cell::RefCell; use crate::os::windows::fs::FileExt; // Test when seek_read(), seek_write() return Ok(0) struct MockFile { expected_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(expected_offset: u64) -> Self { + Self { expected_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } } impl FileExt for MockFile { fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); Ok(0) } fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); Ok(0) } } - { - let mock_file = MockFile { expected_offset: 0 }; - let mut buf = [0; 256]; - assert_eq!( - mock_file.seek_read_exact(&mut buf, 0).unwrap_err().kind(), - io::ErrorKind::UnexpectedEof - ); - assert_eq!(mock_file.seek_write_all(&buf, 0).unwrap_err().kind(), io::ErrorKind::WriteZero); - } - - { - let mock_file = MockFile { expected_offset: 420 }; - let mut buf = [0; 256]; - assert_eq!( - mock_file.seek_read_exact(&mut buf, 420).unwrap_err().kind(), - io::ErrorKind::UnexpectedEof - ); - assert_eq!( - mock_file.seek_write_all(&buf, 420).unwrap_err().kind(), - io::ErrorKind::WriteZero - ); + let mut buf = [0; 256]; + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_read_exact(&mut buf, offset).unwrap_err().kind(), + ErrorKind::UnexpectedEof + ); + assert_eq!(*mock_file.read_calls.borrow(), 1); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_write_all(&buf, offset).unwrap_err().kind(), + ErrorKind::WriteZero + ); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), 1); + } } } #[test] #[cfg(windows)] fn file_test_windows_fileext_trait_case_3() { + use crate::cell::RefCell; use crate::os::windows::fs::FileExt; - // Test that Err other than io::ErrorKind::Interrupted are propagated up. + // Test that Err are propagated up. struct MockFile { expected_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(expected_offset: u64) -> Self { + Self { expected_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } } impl FileExt for MockFile { fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); - Err(io::Error::new(io::ErrorKind::PermissionDenied, "seek_read")) + Err(io::Error::new(ErrorKind::PermissionDenied, "seek_read")) } fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); - Err(io::Error::new(io::ErrorKind::ConnectionRefused, "seek_write")) + Err(io::Error::new(ErrorKind::ConnectionRefused, "seek_write")) } } - { - let mock_file = MockFile { expected_offset: 0 }; - let mut buf = [0; 256]; - assert_eq!( - mock_file.seek_read_exact(&mut buf, 0).unwrap_err().kind(), - io::ErrorKind::PermissionDenied - ); - assert_eq!( - mock_file.seek_write_all(&buf, 0).unwrap_err().kind(), - io::ErrorKind::ConnectionRefused - ); + let mut buf = [0; 256]; + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_read_exact(&mut buf, offset).unwrap_err().kind(), + ErrorKind::PermissionDenied + ); + assert_eq!(*mock_file.read_calls.borrow(), 1); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_write_all(&buf, offset).unwrap_err().kind(), + ErrorKind::ConnectionRefused + ); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), 1); + } } +} - { - let mock_file = MockFile { expected_offset: 420 }; - let mut buf = [0; 256]; - assert_eq!( - mock_file.seek_read_exact(&mut buf, 420).unwrap_err().kind(), - io::ErrorKind::PermissionDenied - ); - assert_eq!( - mock_file.seek_write_all(&buf, 420).unwrap_err().kind(), - io::ErrorKind::ConnectionRefused - ); +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_4() { + use crate::cell::RefCell; + use crate::os::windows::fs::FileExt; + + // Test that Err ErrorKind::Interrupted are propagated up (unlike Unix versions) + struct MockFile { + expected_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(expected_offset: u64) -> Self { + Self { expected_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } + } + + impl FileExt for MockFile { + fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; + assert_eq!(offset, self.expected_offset); + Err(io::Error::new(ErrorKind::Interrupted, "seek_read")) + } + + fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; + assert_eq!(offset, self.expected_offset); + Err(io::Error::new(ErrorKind::Interrupted, "seek_write")) + } + } + + let mut buf = [0; 256]; + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_read_exact(&mut buf, offset).unwrap_err().kind(), + ErrorKind::Interrupted + ); + assert_eq!(*mock_file.read_calls.borrow(), 1); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_write_all(&buf, offset).unwrap_err().kind(), + ErrorKind::Interrupted + ); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), 1); + } } - // FIXME: Cover io::ErrorKind::Interrupted, but don't infinite loop ;) } #[test] #[cfg(windows)] -fn file_test_windows_fileext_trait_case_4() { +fn file_test_windows_fileext_trait_case_5() { + use crate::cell::RefCell; use crate::os::windows::fs::FileExt; const MSG: &[u8] = @@ -957,10 +1030,19 @@ fn file_test_windows_fileext_trait_case_4() { // seek_write(), respectively. struct MockFile { expected_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(expected_offset: u64) -> Self { + Self { expected_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } } impl FileExt for MockFile { fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); assert_eq!(buf.len(), MSG.len()); assert_eq!(buf, &[0; MSG.len()]); @@ -969,6 +1051,7 @@ fn file_test_windows_fileext_trait_case_4() { } fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); assert_eq!(buf.len(), MSG.len()); assert_eq!(buf, MSG); @@ -976,26 +1059,28 @@ fn file_test_windows_fileext_trait_case_4() { } } - { - let mock_file = MockFile { expected_offset: 0 }; - let mut buf = [0; MSG.len()]; - check!(mock_file.seek_read_exact(&mut buf, 0)); - assert_eq!(&buf, MSG); - check!(mock_file.seek_write_all(&buf, 0)); - } - - { - let mock_file = MockFile { expected_offset: 420 }; - let mut buf = [0; MSG.len()]; - check!(mock_file.seek_read_exact(&mut buf, 420)); - assert_eq!(&buf, MSG); - check!(mock_file.seek_write_all(&buf, 420)); + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, offset)); + assert_eq!(&buf, MSG); + assert_eq!(*mock_file.read_calls.borrow(), 1); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + check!(mock_file.seek_write_all(MSG, offset)); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), 1); + } } } #[test] #[cfg(windows)] -fn file_test_windows_fileext_trait_case_5() { +fn file_test_windows_fileext_trait_case_6() { + use crate::cell::RefCell; use crate::os::windows::fs::FileExt; const MSG: &[u8] = @@ -1004,36 +1089,47 @@ fn file_test_windows_fileext_trait_case_5() { // Test pathological case where seek_read(), seek_write() only do 1 byte per call, return Ok(1) struct MockFile { base_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(base_offset: u64) -> Self { + Self { base_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } } impl FileExt for MockFile { fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; let offset = (offset - self.base_offset) as usize; buf[0..1].copy_from_slice(&MSG[offset..offset + 1]); Ok(1) } fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; let offset = (offset - self.base_offset) as usize; assert_eq!(buf[0..1], MSG[offset..offset + 1]); Ok(1) } } - { - let mock_file = MockFile { base_offset: 0 }; - let mut buf = [0; MSG.len()]; - check!(mock_file.seek_read_exact(&mut buf, 0)); - assert_eq!(&buf, MSG); - check!(mock_file.seek_write_all(&buf, 0)); - } - - { - let mock_file = MockFile { base_offset: 420 }; - let mut buf = [0; MSG.len()]; - check!(mock_file.seek_read_exact(&mut buf, 420)); - assert_eq!(&buf, MSG); - check!(mock_file.seek_write_all(&buf, 420)); + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, offset)); + assert_eq!(&buf, MSG); + assert_eq!(*mock_file.read_calls.borrow(), MSG.len()); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + check!(mock_file.seek_write_all(MSG, offset)); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), MSG.len()); + } } } diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index c10766470e873..4c2506ffae66c 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -69,9 +69,6 @@ pub trait FileExt { /// /// # Errors /// - /// If this function encounters an error of the kind - /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation - /// will continue. /// /// If this function encounters an "end of file" before completely filling /// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`]. @@ -113,7 +110,6 @@ pub trait FileExt { buf = &mut buf[n..]; offset += n as u64; } - Err(ref e) if e.is_interrupted() => {} Err(e) => return Err(e), } } @@ -203,7 +199,6 @@ pub trait FileExt { let prev_written = buf.written(); match self.seek_read_buf(buf.reborrow(), offset) { Ok(()) => {} - Err(e) if e.is_interrupted() => {} Err(e) => return Err(e), } let n = buf.written() - prev_written; @@ -264,8 +259,7 @@ pub trait FileExt { /// /// # Errors /// - /// This function will return the first error of - /// non-[`io::ErrorKind::Interrupted`] kind that [`seek_write`] returns. + /// This function will return the first error that [`seek_write`] returns. /// /// [`seek_write`]: FileExt::seek_write /// @@ -298,7 +292,6 @@ pub trait FileExt { buf = &buf[n..]; offset += n as u64 } - Err(ref e) if e.is_interrupted() => {} Err(e) => return Err(e), } } From fca5ba904c8fbba5be52e5e034b2339f893114b8 Mon Sep 17 00:00:00 2001 From: Viktor Kuksgauzen <47536339+Kuksgauzen@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:41:55 +0400 Subject: [PATCH 12/20] Add regression test for higher-ranked param env normalization ICE --- ...r-ranked-param-env-normalization-155977.rs | 19 +++++++++++++++++++ ...nked-param-env-normalization-155977.stderr | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.rs create mode 100644 tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.stderr diff --git a/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.rs b/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.rs new file mode 100644 index 0000000000000..0d45d1ce9b1c4 --- /dev/null +++ b/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.rs @@ -0,0 +1,19 @@ +//@ compile-flags: -Znext-solver + +// Regression test for #155977: the new solver used to ICE while normalizing +// a higher-ranked where-clause in the param env. +// Fixed by eager param-env normalization in #156976. + +trait Trait { + type Output; +} + +fn walk2() +//~^ ERROR the trait bound +where + for<'a> F: 'a, + for<'a> >::Output: 'a, +{ +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.stderr b/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.stderr new file mode 100644 index 0000000000000..1889e3be86aba --- /dev/null +++ b/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `for<'a> F: Trait<&'a u32>` is not satisfied + --> $DIR/higher-ranked-param-env-normalization-155977.rs:11:1 + | +LL | / fn walk2() +LL | | +LL | | where +LL | | for<'a> F: 'a, +LL | | for<'a> >::Output: 'a, + | |______________________________________________^ the trait `for<'a> Trait<&'a u32>` is not implemented for `F` + | +help: consider further restricting type parameter `F` with trait `Trait` + | +LL | for<'a> F: 'a + for<'a> Trait<&'a u32>, + | ++++++++++++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From cad2ab9ffe4878410152581c90db2b42b4a1339c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Oct 2026 16:26:46 +0200 Subject: [PATCH 13/20] Add regression test for `rustc_must_implement_one_of` changes --- .../trait-rustc_must_implement_one_of.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/rustdoc-html/trait-rustc_must_implement_one_of.rs diff --git a/tests/rustdoc-html/trait-rustc_must_implement_one_of.rs b/tests/rustdoc-html/trait-rustc_must_implement_one_of.rs new file mode 100644 index 0000000000000..57accd9fa0791 --- /dev/null +++ b/tests/rustdoc-html/trait-rustc_must_implement_one_of.rs @@ -0,0 +1,50 @@ +// This test ensures a few things around the `rustc_must_implement_one_of` attribute: +// 1. The listed methods are links +// 2. It gets a heading in the sidebar even if there is no "required methods" +// Regression test for . + +#![allow(internal_features)] +#![feature(rustc_attrs)] + +#![crate_name = "foo"] + +//@ has 'foo/trait.Read.html' +//@ has - '//*[@class="stab must_implement"]/a[@href="#method.read"]' 'read' +//@ has - '//*[@class="stab must_implement"]/a[@href="#method.read_buf"]' 'read_buf' + +// There should be 3 headings in the sidebar: Required/Provided methods and "Implementors". +//@ count - '//*[@id="rustdoc-toc"]/h3' 3 +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#required-methods"]' 'Required Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#provided-methods"]' 'Provided Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#implementors"]' 'Implementors' + +#[rustc_must_implement_one_of(read_buf, read)] +pub trait Read { + fn read_buf(&self) {} + fn read(&self) {} +} + +//@ has 'foo/trait.Tadam.html' +// There should still be 3 headings in the sidebar: Required/Provided methods and "Implementors". +//@ count - '//*[@id="rustdoc-toc"]/h3' 3 +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#required-methods"]' 'Required Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#provided-methods"]' 'Provided Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#implementors"]' 'Implementors' + +#[rustc_must_implement_one_of(read_buf, read)] +pub trait Tadam { + fn read_buf(&self) {} + fn read(&self) {} + fn foo(&self) {} +} + +//@ has 'foo/trait.Other.html' +// There should be 2 headings in the sidebar: Provided methods and "Implementors". +//@ count - '//*[@id="rustdoc-toc"]/h3' 2 +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#provided-methods"]' 'Provided Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#implementors"]' 'Implementors' + +pub trait Other { + fn read_buf(&self) {} + fn read(&self) {} +} From 6ffc09af469680d468d12b0f6273e9386a9f11d5 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:07:08 +0330 Subject: [PATCH 14/20] Add regression test for hang in overlap check with GAT alias --- .../overlap-check-gat-alias-hang-151503.rs | 29 +++++++++++++++++++ ...overlap-check-gat-alias-hang-151503.stderr | 11 +++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/ui/coherence/overlap-check-gat-alias-hang-151503.rs create mode 100644 tests/ui/coherence/overlap-check-gat-alias-hang-151503.stderr diff --git a/tests/ui/coherence/overlap-check-gat-alias-hang-151503.rs b/tests/ui/coherence/overlap-check-gat-alias-hang-151503.rs new file mode 100644 index 0000000000000..21d6342dedb72 --- /dev/null +++ b/tests/ui/coherence/overlap-check-gat-alias-hang-151503.rs @@ -0,0 +1,29 @@ +//! Regression test for . +//! +//! The overlap check for the two `MyFrom` impls used to hang. + +trait HasAlias {} +struct Dummy; +trait DummyTrait { + type DummyType; +} +impl DummyTrait for Dummy { + type DummyType = T; +} +type AliasOf = ::DummyType; +struct Matrix(T, S); +type OMatrix = Matrix>; + +trait SimdValue { + type Element; +} +impl SimdValue for OMatrix { + type Element = OMatrix; +} +trait MyFrom {} +impl MyFrom for T {} +impl MyFrom for OMatrix {} +//~^ ERROR conflicting implementations of trait `MyFrom>` for type `Matrix<_, _>` +impl HasAlias for Matrix {} + +fn main() {} diff --git a/tests/ui/coherence/overlap-check-gat-alias-hang-151503.stderr b/tests/ui/coherence/overlap-check-gat-alias-hang-151503.stderr new file mode 100644 index 0000000000000..a20b12334f2fc --- /dev/null +++ b/tests/ui/coherence/overlap-check-gat-alias-hang-151503.stderr @@ -0,0 +1,11 @@ +error[E0119]: conflicting implementations of trait `MyFrom>` for type `Matrix<_, _>` + --> $DIR/overlap-check-gat-alias-hang-151503.rs:25:1 + | +LL | impl MyFrom for T {} + | ----------------------- first implementation here +LL | impl MyFrom for OMatrix {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Matrix<_, _>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0119`. From c0976820bf9638c2bb10d2d12f5a9cc31a487b8b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 6 Oct 2026 16:47:03 +1100 Subject: [PATCH 15/20] Remove the `span_use_eq_ctxt` lint PR 163857 changed the span representation and reduced `eq_ctxt` to a trivial equality check, which means `span_a.ctxt() == span_b.ctxt()` is now an ok alternative to `span_a.eq_ctxt(span_b)`. This commit removes `span_use_eq_ctxt` because it's no longer necessary. `eq_ctxt` remains because removing it would cause some churn; it might be worth doing as a follow-up. --- compiler/rustc_lint/src/diagnostics.rs | 4 -- compiler/rustc_lint/src/internal.rs | 39 +------------------ compiler/rustc_lint/src/lib.rs | 3 -- compiler/rustc_span/src/span_encoding.rs | 9 +---- compiler/rustc_span/src/symbol.rs | 1 - .../internal-lints/span_use_eq_ctxt.rs | 13 ------- .../internal-lints/span_use_eq_ctxt.stderr | 14 ------- 7 files changed, 4 insertions(+), 79 deletions(-) delete mode 100644 tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.rs delete mode 100644 tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.stderr diff --git a/compiler/rustc_lint/src/diagnostics.rs b/compiler/rustc_lint/src/diagnostics.rs index 601f1213650e1..488de4b6c7f95 100644 --- a/compiler/rustc_lint/src/diagnostics.rs +++ b/compiler/rustc_lint/src/diagnostics.rs @@ -1231,10 +1231,6 @@ pub(crate) struct QueryUntracked { pub method: Symbol, } -#[derive(Diagnostic)] -#[diag("use `.eq_ctxt()` instead of `.ctxt() == .ctxt()`")] -pub(crate) struct SpanUseEqCtxtDiag; - #[derive(Diagnostic)] #[diag("using `{$fn_name}` on a string literal")] #[help("consider adding the symbol to `compiler/rustc_span/src/symbol.rs`")] diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index bce726311e51f..03622e253c90a 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -20,8 +20,8 @@ use crate::diagnostics::{ AttributeKindInFindAttr, BadOptAccessDiag, DefaultHashTypesDiag, ImplicitSysrootCrateImportDiag, LintPassByHand, MissingGenericTypeVisitableDeriveDiag, NonGlobImportTypeIrInherent, QueryInstability, QueryUntracked, - RustcMustMatchExhaustivelyNotExhaustive, SpanUseEqCtxtDiag, SymbolInternStringLiteralDiag, - TyQualified, TykindDiag, TykindKind, TypeIrDirectUse, TypeIrInherentUsage, TypeIrTraitUsage, + RustcMustMatchExhaustivelyNotExhaustive, SymbolInternStringLiteralDiag, TyQualified, + TykindDiag, TykindKind, TypeIrDirectUse, TypeIrInherentUsage, TypeIrTraitUsage, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; @@ -570,41 +570,6 @@ impl LateLintPass<'_> for BadOptAccess { } } -declare_tool_lint! { - pub rustc::SPAN_USE_EQ_CTXT, - Allow, - "forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead", - report_in_external_macro: true -} - -declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]); - -impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { - if let hir::ExprKind::Binary( - hir::BinOp { node: hir::BinOpKind::Eq | hir::BinOpKind::Ne, .. }, - lhs, - rhs, - ) = expr.kind - { - if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) { - cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag); - } - } - } -} - -fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - match &expr.kind { - hir::ExprKind::MethodCall(..) => cx - .typeck_results() - .type_dependent_def_id(expr.hir_id) - .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)), - - _ => false, - } -} - declare_tool_lint! { /// The `symbol_intern_string_literal` lint detects `Symbol::intern` or `Ident::from_str_and_span` being called on a string literal pub rustc::SYMBOL_INTERN_STRING_LITERAL, diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 88d99449ba879..94f6183740954 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -301,7 +301,6 @@ late_lint_methods!( MissingGenericTypeVisitableDerive: MissingGenericTypeVisitableDerive, QueryStability: QueryStability, RustcMustMatchExhaustively: RustcMustMatchExhaustively, - SpanUseEqCtxt: SpanUseEqCtxt, SymbolInternStringLiteral: SymbolInternStringLiteral, TyTyKind: TyTyKind, TypeIr: TypeIr, @@ -759,8 +758,6 @@ fn register_internals(store: &mut LintStore) { LintId::of(BAD_OPT_ACCESS), // Late pass: DisallowedPassByRef LintId::of(DISALLOWED_PASS_BY_REF), - // Late pass: SpanUseEqCtxt - LintId::of(SPAN_USE_EQ_CTXT), // Late pass: SymbolInternStringLiteral // Note: this one is not included in rustc::internal because rustc_driver crates // outside the compiler can't/shouldn't add preinterned symbols. For rustc itself, diff --git a/compiler/rustc_span/src/span_encoding.rs b/compiler/rustc_span/src/span_encoding.rs index f4cba4384d589..b036e2de05b02 100644 --- a/compiler/rustc_span/src/span_encoding.rs +++ b/compiler/rustc_span/src/span_encoding.rs @@ -479,7 +479,6 @@ impl Span { /// This function is used as a fast path when decoding the full `SpanData` is not necessary. /// It's a cut-down version of `data_untracked` and doesn't require taking the interner lock. - #[cfg_attr(not(test), rustc_diagnostic_item = "SpanCtxt")] #[inline] pub fn ctxt(self) -> SyntaxContext { let ctxt32 = match_span_kind! { @@ -492,12 +491,8 @@ impl Span { SyntaxContext::from_u32(ctxt32) } - // Allow `span_use_eq_ctxt` because this is `eq_ctxt`! - // - // FIXME(nnethercote): context equality used to be much more complex, but the span - // representation changed and now simple equality is fine. This method and the - // `span_use_eq_ctxt` lint and the `SpanCtxt` diagnostic item can be removed. - #[allow(rustc::span_use_eq_ctxt)] + // With the pre-#163857 span representation this method was more complex and required. Now it's + // trivial but it hasn't been removed simply to avoid churn. #[inline] pub fn eq_ctxt(self, other: Span) -> bool { self.ctxt() == other.ctxt() diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 0f6a0b7012ad7..0422fd6658661 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -333,7 +333,6 @@ symbols! { SliceIndex, Some, Source, - SpanCtxt, StdinLock, Str, String, diff --git a/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.rs b/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.rs deleted file mode 100644 index 369c2dd9c4877..0000000000000 --- a/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Test the `rustc::span_use_eq_ctxt` internal lint -//@ compile-flags: -Z unstable-options - -#![feature(rustc_private)] -#![deny(rustc::span_use_eq_ctxt)] -#![crate_type = "lib"] - -extern crate rustc_span; -use rustc_span::Span; - -pub fn f(s: Span, t: Span) -> bool { - s.ctxt() == t.ctxt() //~ ERROR use `.eq_ctxt()` instead of `.ctxt() == .ctxt()` -} diff --git a/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.stderr b/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.stderr deleted file mode 100644 index b52df0368b16d..0000000000000 --- a/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error: use `.eq_ctxt()` instead of `.ctxt() == .ctxt()` - --> $DIR/span_use_eq_ctxt.rs:12:5 - | -LL | s.ctxt() == t.ctxt() - | ^^^^^^^^^^^^^^^^^^^^ - | -note: the lint level is defined here - --> $DIR/span_use_eq_ctxt.rs:5:9 - | -LL | #![deny(rustc::span_use_eq_ctxt)] - | ^^^^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 1 previous error - From 9c1eb804990d9cbfe3d763a275958aab1bb0dd0f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 14 Oct 2026 02:30:42 +0200 Subject: [PATCH 16/20] Prefer WriteConsoleW for stdout on Windows This drops the `GetConsoleOutputCP` call, which would otherwise do an RPC to the console server (= slow). This leaves us with `is_console` which was using another RPC via `GetConsoleMode` (= doubly slow). On Windows 8+ we can just use `NtQueryVolumeInformationFile()` to test for `FILE_DEVICE_CONSOLE`, resulting in a ~2x console stdout speedup. --- library/std/src/sys/io/is_terminal/windows.rs | 48 +++++++++++++---- library/std/src/sys/pal/windows/c.rs | 7 +-- .../std/src/sys/pal/windows/c/bindings.txt | 4 +- .../std/src/sys/pal/windows/c/windows_sys.rs | 10 +++- library/std/src/sys/stdio/windows.rs | 53 ++++++++++++------- .../miri/src/shims/windows/foreign_items.rs | 28 ++++------ 6 files changed, 98 insertions(+), 52 deletions(-) diff --git a/library/std/src/sys/io/is_terminal/windows.rs b/library/std/src/sys/io/is_terminal/windows.rs index b0c718d71f9f3..6d66e93087911 100644 --- a/library/std/src/sys/io/is_terminal/windows.rs +++ b/library/std/src/sys/io/is_terminal/windows.rs @@ -1,4 +1,5 @@ use crate::ffi::c_void; +use crate::mem::MaybeUninit; use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; use crate::sys::c; @@ -12,10 +13,44 @@ fn handle_is_console(handle: BorrowedHandle<'_>) -> bool { return false; } - let mut out = 0; - if unsafe { c::GetConsoleMode(handle.as_raw_handle(), &mut out) != 0 } { - // False positives aren't possible. If we got a console then we definitely have a console. - return true; + cfg_select! { + target_vendor = "win7" => { + let mut out = 0; + if unsafe { c::GetConsoleMode(handle.as_raw_handle(), &mut out) != 0 } { + // False positives aren't possible. If we got a console then we definitely have a console. + return true; + } + + // Early return if the handle is not FILE_TYPE_PIPE, implying it cannot be an msys PTY. + if unsafe { c::GetFileType(handle.as_raw_handle()) != 0x0003 } { + return false; + } + } + _ => unsafe { + // `GetConsoleMode` incurs a remote RPC round-trip (= bad), so it's best avoided. + // `NtQueryVolumeInformationFile` lets us test the handle directly. + // The required `FILE_DEVICE_CONSOLE` type was introduced in Windows 8. + // Fun fact: `GetFileType` uses `NtQueryVolumeInformationFile` internally. + let mut io_status = MaybeUninit::::uninit(); + let mut device_info = MaybeUninit::::uninit(); + let status = c::NtQueryVolumeInformationFile( + handle.as_raw_handle(), + io_status.as_mut_ptr(), + device_info.as_mut_ptr().cast(), + size_of_val(&device_info) as u32, + c::FileFsDeviceInformation, + ); + if status < 0 { + return false; + } + + match device_info.assume_init_ref().DeviceType { + c::FILE_DEVICE_CONSOLE => return true, + // If it's a named pipe, it may be an msys PTY. + c::FILE_DEVICE_NAMED_PIPE => {} // aka FILE_TYPE_PIPE + _ => return false, + } + }, } // Otherwise, we fall back to an msys hack to see if we can detect the presence of a pty. @@ -23,11 +58,6 @@ fn handle_is_console(handle: BorrowedHandle<'_>) -> bool { } fn msys_tty_on(handle: BorrowedHandle<'_>) -> bool { - // Early return if the handle is not a pipe. - if unsafe { c::GetFileType(handle.as_raw_handle()) != c::FILE_TYPE_PIPE } { - return false; - } - /// Mirrors [`FILE_NAME_INFO`], giving it a fixed length that we can stack /// allocate /// diff --git a/library/std/src/sys/pal/windows/c.rs b/library/std/src/sys/pal/windows/c.rs index 25031968a7e02..67345d183a2d2 100644 --- a/library/std/src/sys/pal/windows/c.rs +++ b/library/std/src/sys/pal/windows/c.rs @@ -20,6 +20,9 @@ pub type WCHAR = u16; pub const INVALID_HANDLE_VALUE: HANDLE = ::core::ptr::without_provenance_mut(-1i32 as _); +pub const FILE_DEVICE_NAMED_PIPE: u32 = 0x00000011; +pub const FILE_DEVICE_CONSOLE: u32 = 0x00000050; + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/exit-success-exit-failure?view=msvc-170 pub const EXIT_SUCCESS: u32 = 0; pub const EXIT_FAILURE: u32 = 1; @@ -188,9 +191,6 @@ pub const FILE_CURRENT: u32 = cu(windows_sys::FILE_CURRENT); pub const FSCTL_GET_REPARSE_POINT: u32 = cu(windows_sys::FSCTL_GET_REPARSE_POINT); pub const FSCTL_SET_REPARSE_POINT: u32 = cu(windows_sys::FSCTL_SET_REPARSE_POINT); -// GetFileType return value (DWORD) -pub const FILE_TYPE_PIPE: u32 = cu(windows_sys::FILE_TYPE_PIPE); - // GetACP return value (UINT) pub const CP_UTF8: u32 = windows_sys::CP_UTF8.cast_unsigned(); @@ -473,6 +473,7 @@ cfg_select! { target_vendor = "uwp" => { windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtCreateFile(filehandle : *mut HANDLE, desiredaccess : ACCESS_MASK, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, allocationsize : *const i64, fileattributes : u32, shareaccess : u32, createdisposition : u32, createoptions : u32, eabuffer : *const core::ffi::c_void, ealength : u32) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtOpenFile(filehandle : *mut HANDLE, desiredaccess : ACCESS_MASK, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, shareaccess : u32, openoptions : u32) -> NTSTATUS); + windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtQueryVolumeInformationFile(filehandle : HANDLE, iostatusblock : *mut IO_STATUS_BLOCK, fsinformation : *mut core::ffi::c_void, length : u32, fsinformationclass : FS_INFORMATION_CLASS) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtReadFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *mut core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtWriteFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *const core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn RtlNtStatusToDosError(status : NTSTATUS) -> u32); diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index 70af36f27907e..71f94954b1356 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -2032,6 +2032,7 @@ FILE_FLAG_RANDOM_ACCESS FILE_FLAG_SEQUENTIAL_SCAN FILE_FLAG_SESSION_AWARE FILE_FLAG_WRITE_THROUGH +FILE_FS_DEVICE_INFORMATION FILE_GENERIC_EXECUTE FILE_GENERIC_READ FILE_GENERIC_WRITE @@ -2084,7 +2085,6 @@ FILE_SYNCHRONOUS_IO_NONALERT FILE_TRAVERSE FILE_TYPE_CHAR FILE_TYPE_DISK -FILE_TYPE_PIPE FILE_TYPE_REMOTE FILE_TYPE_UNKNOWN FILE_WRITE_ATTRIBUTES @@ -2100,6 +2100,7 @@ FileCompressionInfo FileDispositionInfo FileDispositionInfoEx FileEndOfFileInfo +FileFsDeviceInformation FileFullDirectoryInfo FileFullDirectoryRestartInfo FileIdBothDirectoryInfo @@ -2303,6 +2304,7 @@ NO_ERROR NORMAL_PRIORITY_CLASS NtCreateFile NtOpenFile +NtQueryVolumeInformationFile NtReadFile NtSetInformationFile NtWriteFile diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index 0c865494a6e7c..ae08f790bb40a 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -78,6 +78,7 @@ windows_link::link!("kernel32.dll" "system" fn MoveFileExW(lpexistingfilename : windows_link::link!("kernel32.dll" "system" fn MultiByteToWideChar(codepage : u32, dwflags : u32, lpmultibytestr : *const i8, cbmultibyte : i32, lpwidecharstr : PWSTR, cchwidechar : i32) -> i32); windows_link::link!("ntdll.dll" "system" fn NtCreateFile(filehandle : *mut HANDLE, desiredaccess : ACCESS_MASK, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, allocationsize : *const i64, fileattributes : u32, shareaccess : u32, createdisposition : u32, createoptions : u32, eabuffer : *const core::ffi::c_void, ealength : u32) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtOpenFile(filehandle : *mut HANDLE, desiredaccess : ACCESS_MASK, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, shareaccess : u32, openoptions : u32) -> NTSTATUS); +windows_link::link!("ntdll.dll" "system" fn NtQueryVolumeInformationFile(filehandle : HANDLE, iostatusblock : *mut IO_STATUS_BLOCK, fsinformation : *mut core::ffi::c_void, length : u32, fsinformationclass : FS_INFORMATION_CLASS) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtReadFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *mut core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtSetInformationFile(filehandle : HANDLE, iostatusblock : *mut IO_STATUS_BLOCK, fileinformation : *const core::ffi::c_void, length : u32, fileinformationclass : FILE_INFORMATION_CLASS) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtWriteFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *const core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); @@ -2544,6 +2545,12 @@ pub const FILE_FLAG_RANDOM_ACCESS: i32 = 268435456; pub const FILE_FLAG_SEQUENTIAL_SCAN: i32 = 134217728; pub const FILE_FLAG_SESSION_AWARE: i32 = 8388608; pub const FILE_FLAG_WRITE_THROUGH: u32 = 2147483648; +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FILE_FS_DEVICE_INFORMATION { + pub DeviceType: u32, + pub Characteristics: u32, +} pub const FILE_GENERIC_EXECUTE: i32 = 1179808; pub const FILE_GENERIC_READ: i32 = 1179785; pub const FILE_GENERIC_WRITE: i32 = 1179926; @@ -2678,7 +2685,6 @@ pub const FILE_SYNCHRONOUS_IO_NONALERT: i32 = 32; pub const FILE_TRAVERSE: i32 = 32; pub const FILE_TYPE_CHAR: i32 = 2; pub const FILE_TYPE_DISK: i32 = 1; -pub const FILE_TYPE_PIPE: i32 = 3; pub const FILE_TYPE_REMOTE: i32 = 32768; pub const FILE_TYPE_UNKNOWN: i32 = 0; pub const FILE_WRITE_ATTRIBUTES: i32 = 256; @@ -2718,6 +2724,7 @@ pub const FORMAT_MESSAGE_IGNORE_INSERTS: i32 = 512; pub const FRS_ERR_SYSVOL_POPULATE_TIMEOUT: i32 = 8014; pub const FSCTL_GET_REPARSE_POINT: i32 = 589992; pub const FSCTL_SET_REPARSE_POINT: i32 = 589988; +pub type FS_INFORMATION_CLASS = i32; pub const FileAlignmentInfo: FILE_INFO_BY_HANDLE_CLASS = 17; pub const FileAllocationInfo: FILE_INFO_BY_HANDLE_CLASS = 5; pub const FileAttributeTagInfo: FILE_INFO_BY_HANDLE_CLASS = 9; @@ -2727,6 +2734,7 @@ pub const FileCompressionInfo: FILE_INFO_BY_HANDLE_CLASS = 8; pub const FileDispositionInfo: FILE_INFO_BY_HANDLE_CLASS = 4; pub const FileDispositionInfoEx: FILE_INFO_BY_HANDLE_CLASS = 21; pub const FileEndOfFileInfo: FILE_INFO_BY_HANDLE_CLASS = 6; +pub const FileFsDeviceInformation: FS_INFORMATION_CLASS = 4; pub const FileFullDirectoryInfo: FILE_INFO_BY_HANDLE_CLASS = 14; pub const FileFullDirectoryRestartInfo: FILE_INFO_BY_HANDLE_CLASS = 15; pub const FileIdBothDirectoryInfo: FILE_INFO_BY_HANDLE_CLASS = 10; diff --git a/library/std/src/sys/stdio/windows.rs b/library/std/src/sys/stdio/windows.rs index 1339048a9c039..82a4424d46989 100644 --- a/library/std/src/sys/stdio/windows.rs +++ b/library/std/src/sys/stdio/windows.rs @@ -77,26 +77,34 @@ pub fn get_handle(handle_id: u32) -> io::Result { } } -fn is_console(handle: c::HANDLE) -> bool { - // `GetConsoleMode` will return false (0) if this is a pipe (we don't care about the reported - // mode). This will only detect Windows Console, not other terminals connected to a pipe like - // MSYS. Which is exactly what we need, as only Windows Console needs a conversion to UTF-16. - let mut mode = 0; - unsafe { c::GetConsoleMode(handle, &mut mode) != 0 } -} - -/// Returns true if the attached console's code page is currently UTF-8. +/// Returns true if `handle` refers to a proper console (aka conhost). #[cfg(not(target_vendor = "win7"))] -fn is_utf8_console() -> bool { - unsafe { c::GetConsoleOutputCP() == c::CP_UTF8 } -} - -#[cfg(target_vendor = "win7")] -fn is_utf8_console() -> bool { - // Windows 7 has a fun "feature" where WriteFile on a console handle will return - // the number of UTF-16 code units written and not the number of bytes from the input string. - // So we always claim the console isn't UTF-8 to trigger the WriteConsole fallback code. - false +fn is_console(handle: c::HANDLE) -> bool { + cfg_select! { + target_vendor = "win7" => { + // `GetConsoleMode` will return false (0) if this is a pipe (we don't care about the reported + // mode). This will only detect Windows Console, not other terminals connected to a pipe like + // MSYS. Which is exactly what we need, as only Windows Console needs a conversion to UTF-16. + let mut mode = 0; + unsafe { c::GetConsoleMode(handle, &mut mode) != 0 } + } + _ => unsafe { + // `GetConsoleMode` incurs a remote RPC round-trip (= bad), so it's best avoided. + // `NtQueryVolumeInformationFile` lets us test the handle directly. + // The required `FILE_DEVICE_CONSOLE` type was introduced in Windows 8. + // Fun fact: `GetFileType` uses `NtQueryVolumeInformationFile` internally. + let mut io_status = MaybeUninit::::uninit(); + let mut device_info = MaybeUninit::::uninit(); + let status = c::NtQueryVolumeInformationFile( + handle, + io_status.as_mut_ptr(), + device_info.as_mut_ptr().cast(), + size_of_val(&device_info) as u32, + c::FileFsDeviceInformation, + ); + status >= 0 && device_info.assume_init_ref().DeviceType == c::FILE_DEVICE_CONSOLE + }, + } } fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> io::Result { @@ -105,7 +113,12 @@ fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> i } let handle = get_handle(handle_id)?; - if !is_console(handle) || is_utf8_console() { + + // At the time of writing, console IO on Windows is high latency: + // Windows has no TTY buffers, making every call a fully synchronous RPC. + // So, while always deferring to `write_console_utf16` wastes valuable heap for + // our UTF16 conversion, it handily beats doing 2 (or more) costly RPC round-trips. + if !is_console(handle) { unsafe { let handle = Handle::from_raw_handle(handle); let ret = handle.write(data); diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 28fe4e86bb7b5..2d31c6809da87 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -1190,6 +1190,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. + "NtQueryVolumeInformationFile" if this.frame_in_std() => { + let [_handle, _iosb, _info, _length, _class] = this.check_shim_sig( + shim_sig!(extern "system" fn(winapi::HANDLE, *_, *_, u32, i32) -> i32), + (link_name, abi, args), + )?; + // Return an error to make std think that nothing is a console. + // This means the normal NtWriteFile is used for stdout/stderr, greatly simplifying our lives. + let not_implemented = this.eval_windows("c", "STATUS_NOT_IMPLEMENTED"); + this.write_scalar(not_implemented, dest)?; + } "GetProcessHeap" if this.frame_in_std() => { let [] = this.check_shim_sig( shim_sig!(extern "system" fn() -> winapi::HANDLE), @@ -1215,24 +1225,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Pretend these does not exist / nothing happened, by returning zero. this.write_null(dest)?; } - "GetConsoleMode" if this.frame_in_std() => { - let [console, mode] = this.check_shim_sig( - shim_sig!(extern "system" fn(winapi::HANDLE, *_) -> winapi::BOOL), - (link_name, abi, args), - )?; - this.read_target_isize(console)?; - this.deref_pointer_as(mode, this.machine.layouts.u32)?; - // Indicate an error. - this.write_null(dest)?; - } - "GetFileType" if this.frame_in_std() => { - let [_file] = this.check_shim_sig( - shim_sig!(extern "system" fn(winapi::HANDLE) -> u32), - (link_name, abi, args), - )?; - // Return unknown file type. - this.write_null(dest)?; - } "AddVectoredExceptionHandler" if this.frame_in_std() => { let [_first, _handler] = this.check_shim_sig( shim_sig!(extern "system" fn(u32, *_) -> *_), From 6c150feb0fded2947740033883c718fe644749b6 Mon Sep 17 00:00:00 2001 From: Robin <1187338299@qq.com> Date: Wed, 14 Oct 2026 09:56:48 +0800 Subject: [PATCH 17/20] fix some nits --- compiler/rustc_hir_analysis/src/check/wfcheck.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 1aa194d7e0013..6401854f5dcfd 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2400,10 +2400,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { Err(_guar) => ty::region_constraint::RegionConstraint::new_true(), }; if constraints.is_false() { - self.tcx() - .dcx() - .struct_span_err(span, "top level false constraints are not supported") - .emit(); + self.tcx().dcx().span_err(span, "top level false constraints are not supported"); return; } @@ -2486,7 +2483,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { ) .with_spans(forall.span); if constraint.is_false() { - self.tcx().dcx().struct_span_err(forall.span, "forall evaluated to false").emit(); + self.tcx().dcx().span_err(forall.span, "forall evaluated to false"); self.infcx.overwrite_solver_region_constraint( ty::region_constraint::RegionConstraint::new_true(), ); From 9aa0839b0bb6ff81582e7e6059b570e2677bf2af Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Wed, 14 Oct 2026 08:36:45 +0200 Subject: [PATCH 18/20] fix issue number for feature(unsized_const_params) --- compiler/rustc_feature/src/unstable.rs | 2 +- library/core/src/marker.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index a35ea37b3bd5f..b8680924c832a 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -768,7 +768,7 @@ declare_features! ( (incomplete, unsafe_fields, "1.85.0", Some(132922)), /// Allows const generic parameters to be defined with types that /// are not `Sized`, e.g. `fn foo() {`. - (incomplete, unsized_const_params, "1.82.0", Some(95174)), + (incomplete, unsized_const_params, "1.82.0", Some(128028)), /// Allows unsized fn parameters. (internal, unsized_fn_params, "1.49.0", Some(48055)), /// Allows using the `#[used(linker)]` (or `#[used(compiler)]`) attribute. diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index 530af0c3af656..aeabacb0d6364 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -1135,7 +1135,7 @@ marker_impls! { } marker_impls! { - #[unstable(feature = "unsized_const_params", issue = "95174")] + #[unstable(feature = "unsized_const_params", issue = "128028")] #[unstable_feature_bound(unsized_const_params)] ConstParamTy_ for str, From f5e481394242aced873146a2981ad6465036957a Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:36:42 +0200 Subject: [PATCH 19/20] add test --- .../on_unknown/auxiliary/foreign.rs | 12 ++++++++ .../expressions_and_paths_correct_mod_name.rs | 20 +++++++++++++ ...ressions_and_paths_correct_mod_name.stderr | 30 +++++++++++++++++++ ...ions_and_paths_correct_mod_name_foreign.rs | 11 +++++++ ..._and_paths_correct_mod_name_foreign.stderr | 30 +++++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 tests/ui/diagnostic_namespace/on_unknown/auxiliary/foreign.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr diff --git a/tests/ui/diagnostic_namespace/on_unknown/auxiliary/foreign.rs b/tests/ui/diagnostic_namespace/on_unknown/auxiliary/foreign.rs new file mode 100644 index 0000000000000..c9269fcb84ca2 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/auxiliary/foreign.rs @@ -0,0 +1,12 @@ +#![feature(diagnostic_on_unknown)] + +mod a { + #[diagnostic::on_unknown( + message = "this={this}, unresolved={unresolved}", + )] + pub mod b {} +} + +pub mod c { + pub use super::a::b as d; +} diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs new file mode 100644 index 0000000000000..92525706e2621 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs @@ -0,0 +1,20 @@ +#![feature(diagnostic_on_unknown)] + +mod a { + #[diagnostic::on_unknown( + message = "this={this}, unresolved={unresolved}", + )] + pub mod b {} +} + +mod c { + pub use super::a::b as d; +} + +use c::d::x; //~ ERROR this=b, unresolved=x + +fn main() { + let _ = c::d::X(); //~ ERROR this=c::d, unresolved=X + + let _: c::d::X; //~ ERROR this=c::d, unresolved=X +} diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr new file mode 100644 index 0000000000000..d2230ee1a8d4d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr @@ -0,0 +1,30 @@ +error[E0432]: this=b, unresolved=x + --> $DIR/expressions_and_paths_correct_mod_name.rs:14:5 + | +LL | use c::d::x; + | ^^^^^^- + | | + | no `x` in `a::b` + | + = note: unresolved import `c::d::x` + +error[E0425]: this=c::d, unresolved=X + --> $DIR/expressions_and_paths_correct_mod_name.rs:17:19 + | +LL | let _ = c::d::X(); + | ^ not found in `c::d` + | + = note: cannot find function, tuple struct or tuple variant `X` in module `c::d` + +error[E0425]: this=c::d, unresolved=X + --> $DIR/expressions_and_paths_correct_mod_name.rs:19:18 + | +LL | let _: c::d::X; + | ^ not found in `c::d` + | + = note: cannot find type `X` in module `c::d` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0425, E0432. +For more information about an error, try `rustc --explain E0425`. diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs new file mode 100644 index 0000000000000..abf711b1d41de --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs @@ -0,0 +1,11 @@ +//@ aux-crate:foreign=foreign.rs + +extern crate foreign; + +use foreign::c::d::x; //~ ERROR this=b, unresolved=x + +fn main() { + let _ = foreign::c::d::X(); //~ ERROR this=foreign::c::d, unresolved=X + + let _: foreign::c::d::X; //~ ERROR this=foreign::c::d, unresolved=X +} diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr new file mode 100644 index 0000000000000..3b95a3e2d0ec1 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr @@ -0,0 +1,30 @@ +error[E0432]: this=b, unresolved=x + --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:5:5 + | +LL | use foreign::c::d::x; + | ^^^^^^^^^^^^^^^- + | | + | no `x` in `a::b` + | + = note: unresolved import `foreign::c::d::x` + +error[E0425]: this=foreign::c::d, unresolved=X + --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:8:28 + | +LL | let _ = foreign::c::d::X(); + | ^ not found in `foreign::c::d` + | + = note: cannot find function, tuple struct or tuple variant `X` in module `foreign::c::d` + +error[E0425]: this=foreign::c::d, unresolved=X + --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:10:27 + | +LL | let _: foreign::c::d::X; + | ^ not found in `foreign::c::d` + | + = note: cannot find type `X` in module `foreign::c::d` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0425, E0432. +For more information about an error, try `rustc --explain E0425`. From f76d6135a1dd54c50774c737cb94b12f7db08f2a Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 14 Oct 2026 09:31:41 +0200 Subject: [PATCH 20/20] Use proper mod name for on_unknown attribute --- .../rustc_resolve/src/diagnostics/impls.rs | 32 ++++++++++--------- .../rustc_resolve/src/late/diagnostics.rs | 5 +-- .../expressions_and_paths_correct_mod_name.rs | 4 +-- ...ressions_and_paths_correct_mod_name.stderr | 4 +-- ...ions_and_paths_correct_mod_name_foreign.rs | 4 +-- ..._and_paths_correct_mod_name_foreign.stderr | 4 +-- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index f1400c83e4295..18a02666cfc4a 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -206,21 +206,18 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .map(|(import, import_error)| { if let Some(ModuleOrUniformRoot::Module(module_data)) = import.imported_module.get() - && let ModuleKind::Def(DefKind::Mod, def_id, _, name) = module_data.kind + && let ModuleKind::Def(DefKind::Mod, def_id, ..) = module_data.kind { - let Some(directive) = self.on_unknown_data(def_id) else { + let Some((this, directive)) = self.on_unknown_data(def_id) else { return CustomDiagnostic::default(); }; - let this = if let Some(name) = name { - name.to_string() - } else if let Some(crate_name) = &self.tcx.sess.opts.crate_name { - crate_name.to_string() - } else { - "".to_string() - }; let unresolved = import_error.segment.map(|s| s.name).unwrap_or(kw::Underscore); - let args = FormatArgs { this, unresolved: unresolved.to_string(), .. }; + let args = FormatArgs { + this: this.to_string(), + unresolved: unresolved.to_string(), + .. + }; directive.eval(None, &args) } else { @@ -3867,12 +3864,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } - /// Gets the `#[diagnostic::on_unknown]` attribute data associated with this `DefId`. - pub(crate) fn on_unknown_data(&self, def_id: DefId) -> Option<&Directive> { - match def_id.as_local() { + /// Gets the name and `#[diagnostic::on_unknown]` attribute data associated with this `DefId`. + pub(crate) fn on_unknown_data(&self, def_id: DefId) -> Option<(Symbol, &Directive)> { + let directive = match def_id.as_local() { Some(local) => Some(self.on_unknown_data.get(&local)?.directive.as_ref()), - None => find_attr!(self.tcx, def_id, OnUnknown{ directive } => directive)?.as_deref(), - } + None => find_attr!(self.tcx, def_id, OnUnknown { directive } => directive)?.as_deref(), + }?; + + let name = + self.expect_module(def_id).name().unwrap_or_else(|| self.tcx.crate_name(def_id.krate)); + + Some((name, directive)) } } diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 821c16ceee3e5..83e5fbef9bcb9 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -558,9 +558,10 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { }; let mut notes = Vec::new(); if let Some(module_def_id) = module - && let Some(directive) = self.r.on_unknown_data(module_def_id) + && let Some((this, directive)) = self.r.on_unknown_data(module_def_id) { - let args = FormatArgs { unresolved: item_ident.to_string(), this: mod_str, .. }; + let args = + FormatArgs { unresolved: item_ident.to_string(), this: this.to_string(), .. }; let CustomDiagnostic { message, label, diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs index 92525706e2621..d8bf14ccc54c3 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs @@ -14,7 +14,7 @@ mod c { use c::d::x; //~ ERROR this=b, unresolved=x fn main() { - let _ = c::d::X(); //~ ERROR this=c::d, unresolved=X + let _ = c::d::X(); //~ ERROR this=b, unresolved=X - let _: c::d::X; //~ ERROR this=c::d, unresolved=X + let _: c::d::X; //~ ERROR this=b, unresolved=X } diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr index d2230ee1a8d4d..a269acdf42b17 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr @@ -8,7 +8,7 @@ LL | use c::d::x; | = note: unresolved import `c::d::x` -error[E0425]: this=c::d, unresolved=X +error[E0425]: this=b, unresolved=X --> $DIR/expressions_and_paths_correct_mod_name.rs:17:19 | LL | let _ = c::d::X(); @@ -16,7 +16,7 @@ LL | let _ = c::d::X(); | = note: cannot find function, tuple struct or tuple variant `X` in module `c::d` -error[E0425]: this=c::d, unresolved=X +error[E0425]: this=b, unresolved=X --> $DIR/expressions_and_paths_correct_mod_name.rs:19:18 | LL | let _: c::d::X; diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs index abf711b1d41de..6cb8872984b36 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs @@ -5,7 +5,7 @@ extern crate foreign; use foreign::c::d::x; //~ ERROR this=b, unresolved=x fn main() { - let _ = foreign::c::d::X(); //~ ERROR this=foreign::c::d, unresolved=X + let _ = foreign::c::d::X(); //~ ERROR this=b, unresolved=X - let _: foreign::c::d::X; //~ ERROR this=foreign::c::d, unresolved=X + let _: foreign::c::d::X; //~ ERROR this=b, unresolved=X } diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr index 3b95a3e2d0ec1..ee1765828718f 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr @@ -8,7 +8,7 @@ LL | use foreign::c::d::x; | = note: unresolved import `foreign::c::d::x` -error[E0425]: this=foreign::c::d, unresolved=X +error[E0425]: this=b, unresolved=X --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:8:28 | LL | let _ = foreign::c::d::X(); @@ -16,7 +16,7 @@ LL | let _ = foreign::c::d::X(); | = note: cannot find function, tuple struct or tuple variant `X` in module `foreign::c::d` -error[E0425]: this=foreign::c::d, unresolved=X +error[E0425]: this=b, unresolved=X --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:10:27 | LL | let _: foreign::c::d::X;