From 5ed5b2614f6324ede3111c3a438434d18349f88a Mon Sep 17 00:00:00 2001 From: Schneems Date: Wed, 26 Nov 2025 16:28:41 -0600 Subject: [PATCH 01/13] Add Command::get_resolved_envs This addition allows an end-user to inspect the environment variables that are visible to the process when it boots. --- library/std/src/process.rs | 42 +++++++++++++++++++++- library/std/src/sys/process/env.rs | 31 ++++++++++++++++ library/std/src/sys/process/mod.rs | 2 ++ library/std/src/sys/process/motor.rs | 6 +++- library/std/src/sys/process/uefi.rs | 6 +++- library/std/src/sys/process/unix/common.rs | 6 +++- library/std/src/sys/process/unsupported.rs | 6 +++- library/std/src/sys/process/windows.rs | 6 +++- 8 files changed, 99 insertions(+), 6 deletions(-) diff --git a/library/std/src/process.rs b/library/std/src/process.rs index 1199403b1d5ab..b8849110087de 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -1155,7 +1155,8 @@ impl Command { /// [`Command::env_remove`] can be retrieved with this method. /// /// Note that this output does not include environment variables inherited from the parent - /// process. + /// process. To see the full list of environment variables, including those inherited from the + /// parent process, use [`Command::get_resolved_envs`]. /// /// Each element is a tuple key/value pair `(&OsStr, Option<&OsStr>)`. A [`None`] value /// indicates its key was explicitly removed via [`Command::env_remove`]. The associated key for @@ -1184,6 +1185,42 @@ impl Command { CommandEnvs { iter: self.inner.get_envs() } } + /// Returns an iterator of the environment variables that will be set when the process is spawned. + /// + /// This returns the environment as it would be if the command were executed at the time of calling + /// this method. The returned environment includes: + /// - All inherited environment variables from the parent process (unless [`Command::env_clear`] was called) + /// - All environment variables explicitly set via [`Command::env`] or [`Command::envs`] + /// - Excluding any environment variables removed via [`Command::env_remove`] + /// + /// Note that the returned environment is a snapshot at the time this method is called and will not + /// reflect any subsequent changes to the `Command` or the parent process's environment. Additionally, + /// it will not reflect changes made in a `pre_exec` hook (on Unix platforms). + /// + /// Each element is a tuple `(OsString, OsString)` representing an environment variable key and value. + /// + /// # Examples + /// + /// ``` + /// #![feature(command_resolved_envs)] + /// use std::process::Command; + /// use std::ffi::{OsString, OsStr}; + /// use std::env; + /// use std::collections::HashMap; + /// + /// let mut cmd = Command::new("ls"); + /// cmd.env("TZ", "UTC"); + /// unsafe { env::set_var("EDITOR", "vim"); } + /// + /// let resolved: HashMap = cmd.get_resolved_envs().collect(); + /// assert_eq!(resolved.get(OsStr::new("TZ")), Some(&OsString::from("UTC"))); + /// assert_eq!(resolved.get(OsStr::new("EDITOR")), Some(&OsString::from("vim"))); + /// ``` + #[unstable(feature = "command_resolved_envs", issue = "149070")] + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + self.inner.get_resolved_envs() + } + /// Returns the working directory for the child process. /// /// This returns [`None`] if the working directory will not be changed. @@ -1337,6 +1374,9 @@ impl<'a> fmt::Debug for CommandEnvs<'a> { } } +#[unstable(feature = "command_resolved_envs", issue = "149070")] +pub use imp::CommandResolvedEnvs; + /// The output of a finished process. /// /// This is returned in a Result by either the [`output`] method of a diff --git a/library/std/src/sys/process/env.rs b/library/std/src/sys/process/env.rs index e08b476540ef9..15065c6e2c922 100644 --- a/library/std/src/sys/process/env.rs +++ b/library/std/src/sys/process/env.rs @@ -113,3 +113,34 @@ impl<'a> ExactSizeIterator for CommandEnvs<'a> { self.iter.is_empty() } } + +/// An iterator over the fully resolved environment variables. +/// +/// This struct is created by +/// [`Command::get_resolved_envs`][crate::process::Command::get_resolved_envs]. See its +/// documentation for more. +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +#[unstable(feature = "command_resolved_envs", issue = "149070")] +pub struct CommandResolvedEnvs { + inner: crate::collections::btree_map::IntoIter, +} + +impl CommandResolvedEnvs { + pub(crate) fn new(map: BTreeMap) -> Self { + Self { inner: map.into_iter() } + } +} + +#[unstable(feature = "command_resolved_envs", issue = "149070")] +impl Iterator for CommandResolvedEnvs { + type Item = (OsString, OsString); + + fn next(&mut self) -> Option { + self.inner.next().map(|(key, value)| (key.into(), value)) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index 46f4ebf6db421..3eb20dae083ca 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -27,6 +27,8 @@ cfg_select! { mod env; pub use env::CommandEnvs; +#[unstable(feature = "command_resolved_envs", issue = "149070")] +pub use env::CommandResolvedEnvs; #[cfg(target_family = "unix")] pub use imp::getppid; pub use imp::{ diff --git a/library/std/src/sys/process/motor.rs b/library/std/src/sys/process/motor.rs index 133633f7bc67b..080da9be3af92 100644 --- a/library/std/src/sys/process/motor.rs +++ b/library/std/src/sys/process/motor.rs @@ -1,5 +1,5 @@ use super::CommandEnvs; -use super::env::CommandEnv; +use super::env::{CommandEnv, CommandResolvedEnvs}; use crate::ffi::OsStr; pub use crate::ffi::OsString as EnvKey; use crate::num::NonZeroI32; @@ -100,6 +100,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(Path::new) } diff --git a/library/std/src/sys/process/uefi.rs b/library/std/src/sys/process/uefi.rs index 88dd4c899b377..72a75d026a0c9 100644 --- a/library/std/src/sys/process/uefi.rs +++ b/library/std/src/sys/process/uefi.rs @@ -1,6 +1,6 @@ use r_efi::protocols::{simple_text_input, simple_text_output}; -use super::env::{CommandEnv, CommandEnvs}; +use super::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; use crate::collections::BTreeMap; pub use crate::ffi::OsString as EnvKey; use crate::ffi::{OsStr, OsString}; @@ -86,6 +86,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { None } diff --git a/library/std/src/sys/process/unix/common.rs b/library/std/src/sys/process/unix/common.rs index 2d83782b7d0b9..8215b196127ac 100644 --- a/library/std/src/sys/process/unix/common.rs +++ b/library/std/src/sys/process/unix/common.rs @@ -15,7 +15,7 @@ use crate::sys::fs::File; #[cfg(not(target_os = "fuchsia"))] use crate::sys::fs::OpenOptions; use crate::sys::pipe::pipe; -use crate::sys::process::env::{CommandEnv, CommandEnvs}; +use crate::sys::process::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; use crate::sys::{FromInner, IntoInner, cvt_r}; use crate::{fmt, io, mem}; @@ -267,6 +267,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes()))) } diff --git a/library/std/src/sys/process/unsupported.rs b/library/std/src/sys/process/unsupported.rs index 9ed66a559117c..114f0001b7faf 100644 --- a/library/std/src/sys/process/unsupported.rs +++ b/library/std/src/sys/process/unsupported.rs @@ -1,4 +1,4 @@ -use super::env::{CommandEnv, CommandEnvs}; +use super::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; pub use crate::ffi::OsString as EnvKey; use crate::ffi::{OsStr, OsString}; use crate::num::NonZero; @@ -89,6 +89,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(|cs| Path::new(cs)) } diff --git a/library/std/src/sys/process/windows.rs b/library/std/src/sys/process/windows.rs index deb4243d314e2..a747ef048d901 100644 --- a/library/std/src/sys/process/windows.rs +++ b/library/std/src/sys/process/windows.rs @@ -5,7 +5,7 @@ mod tests; use core::ffi::c_void; -use super::env::{CommandEnv, CommandEnvs}; +use super::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; use crate::collections::BTreeMap; use crate::env::consts::{EXE_EXTENSION, EXE_SUFFIX}; use crate::ffi::{OsStr, OsString}; @@ -256,6 +256,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(Path::new) } From d0ea88eeb72b652aa280a5163634ce9bae8ebf5b Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 19 Apr 2026 14:27:10 +0200 Subject: [PATCH 02/13] error on empty `export_name` --- .../src/attributes/codegen_attrs.rs | 10 ++++-- .../src/session_diagnostics.rs | 7 ++++ tests/ui/attributes/invalid-export-name.rs | 22 ++++++++++++ .../ui/attributes/invalid-export-name.stderr | 34 +++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 tests/ui/attributes/invalid-export-name.rs create mode 100644 tests/ui/attributes/invalid-export-name.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 357be2f48f85e..313c0ef1a9da4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -5,8 +5,8 @@ use rustc_span::edition::Edition::Edition2024; use super::prelude::*; use crate::attributes::AttributeSafety; use crate::session_diagnostics::{ - NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector, - ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral, + EmptyExportName, NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, + NullOnObjcSelector, ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral, }; use crate::target_checking::Policy::AllowSilent; @@ -133,6 +133,12 @@ impl SingleAttributeParser for ExportNameParser { cx.emit_err(NullOnExport { span: cx.attr_span }); return None; } + if name.is_empty() { + // LLVM will make up a name if the empty string is given, but that name will be + // inconsistent between compilation units, causing linker errors. + cx.emit_err(EmptyExportName { span: cx.attr_span }); + return None; + } Some(AttributeKind::ExportName { name, span: cx.attr_span }) } } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 203c7f8ebff1b..a3dcdcb567118 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -396,6 +396,13 @@ pub(crate) struct UnusedMultiple { pub name: Symbol, } +#[derive(Diagnostic)] +#[diag("`export_name` may not be empty")] +pub(crate) struct EmptyExportName { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("`export_name` may not contain null characters", code = E0648)] pub(crate) struct NullOnExport { diff --git a/tests/ui/attributes/invalid-export-name.rs b/tests/ui/attributes/invalid-export-name.rs new file mode 100644 index 0000000000000..1fa33a24ce424 --- /dev/null +++ b/tests/ui/attributes/invalid-export-name.rs @@ -0,0 +1,22 @@ +#![crate_type = "lib"] + +#[export_name = "\0foo"] +//~^ ERROR `export_name` may not contain null characters +fn has_null_byte() {} + +#[export_name = "foo\0"] +//~^ ERROR `export_name` may not contain null characters +fn null_terminated() {} + +#[export_name = "\0"] +//~^ ERROR `export_name` may not contain null characters +fn empty_null() {} + +#[export_name = ""] +//~^ ERROR `export_name` may not be empty +fn empty() {} + +#[export_name = "\ +"] +//~^^ ERROR `export_name` may not be empty +fn empty_newline() {} diff --git a/tests/ui/attributes/invalid-export-name.stderr b/tests/ui/attributes/invalid-export-name.stderr new file mode 100644 index 0000000000000..f8553c823cd8f --- /dev/null +++ b/tests/ui/attributes/invalid-export-name.stderr @@ -0,0 +1,34 @@ +error[E0648]: `export_name` may not contain null characters + --> $DIR/invalid-export-name.rs:3:1 + | +LL | #[export_name = "\0foo"] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0648]: `export_name` may not contain null characters + --> $DIR/invalid-export-name.rs:7:1 + | +LL | #[export_name = "foo\0"] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0648]: `export_name` may not contain null characters + --> $DIR/invalid-export-name.rs:11:1 + | +LL | #[export_name = "\0"] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: `export_name` may not be empty + --> $DIR/invalid-export-name.rs:15:1 + | +LL | #[export_name = ""] + | ^^^^^^^^^^^^^^^^^^^ + +error: `export_name` may not be empty + --> $DIR/invalid-export-name.rs:19:1 + | +LL | / #[export_name = "\ +LL | | "] + | |__^ + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0648`. From c08b9abf6f0bcd7e56ae77909ffb2e3b2932229a Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 26 Apr 2026 12:41:05 +0200 Subject: [PATCH 03/13] validate link name parameter specifically, do not allow NULL bytes and the empty string --- .../src/attributes/link_attrs.rs | 27 +++++--- .../src/session_diagnostics.rs | 14 ++-- tests/ui/attributes/invalid-link-name.rs | 54 ++++++++++++++++ tests/ui/attributes/invalid-link-name.stderr | 64 +++++++++++++++++++ .../raw-dylib/elf/malformed-link-name.rs | 4 +- .../raw-dylib/elf/malformed-link-name.stderr | 8 +-- 6 files changed, 146 insertions(+), 25 deletions(-) create mode 100644 tests/ui/attributes/invalid-link-name.rs create mode 100644 tests/ui/attributes/invalid-link-name.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 105fe77eba73a..a79ee3ce8b0b7 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -17,7 +17,7 @@ use crate::session_diagnostics::{ AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange, - LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibNoNul, RawDylibOnlyWindows, + LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, RawDylibOnlyWindows, WholeArchiveNeedsStatic, }; @@ -46,6 +46,19 @@ impl SingleAttributeParser for LinkNameParser { return None; }; + if name.as_str().contains('\0') { + // `#[link_name = ...]` will be converted to a null-terminated string, + // so it may not contain any null characters. + cx.emit_err(NullOnLinkName { span: nv.value_span }); + return None; + } + if name.is_empty() { + // Otherwise LLVM will just make up a name and the linker will fail + // to find an empty symbol name. + cx.emit_err(EmptyLinkName { span: nv.value_span }); + return None; + } + Some(LinkName { name, span: cx.attr_span }) } } @@ -222,7 +235,7 @@ impl CombineAttributeParser for LinkParser { if wasm_import_module.is_some() { (name, kind) = (wasm_import_module, Some(NativeLibKind::WasmImportModule)); } - let Some((name, name_span)) = name else { + let Some((name, _name_span)) = name else { cx.emit_err(LinkRequiresName { span: cx.attr_span }); return None; }; @@ -234,12 +247,6 @@ impl CombineAttributeParser for LinkParser { } } - if let Some(NativeLibKind::RawDylib { .. }) = kind - && name.as_str().contains('\0') - { - cx.emit_err(RawDylibNoNul { span: name_span }); - } - Some(LinkEntry { span: cx.attr_span, kind: kind.unwrap_or(NativeLibKind::Unspecified), @@ -270,9 +277,13 @@ impl LinkParser { return false; }; + if link_name.as_str().contains('\0') { + cx.emit_err(NullOnLinkName { span: nv.value_span }); + } if link_name.is_empty() { cx.emit_err(EmptyLinkName { span: nv.value_span }); } + *name = Some((link_name, nv.value_span)); true } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 0a9c96033257d..5de4a425c9fd8 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -410,6 +410,13 @@ pub(crate) struct NullOnLinkSection { pub span: Span, } +#[derive(Diagnostic)] +#[diag("link name may not contain null characters", code = E0648)] +pub(crate) struct NullOnLinkName { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("`objc::class!` may not contain null characters")] pub(crate) struct NullOnObjcClass { @@ -984,13 +991,6 @@ pub(crate) struct LinkRequiresName { pub span: Span, } -#[derive(Diagnostic)] -#[diag("link name must not contain NUL characters if link kind is `raw-dylib`")] -pub(crate) struct RawDylibNoNul { - #[primary_span] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("link kind `raw-dylib` is only supported on Windows targets", code = E0455)] pub(crate) struct RawDylibOnlyWindows { diff --git a/tests/ui/attributes/invalid-link-name.rs b/tests/ui/attributes/invalid-link-name.rs new file mode 100644 index 0000000000000..579100914d3c4 --- /dev/null +++ b/tests/ui/attributes/invalid-link-name.rs @@ -0,0 +1,54 @@ +#![crate_type = "lib"] + +#[link(name = "")] +//~^ ERROR link name must not be empty +unsafe extern "C" { + #[link_name = ""] + //~^ ERROR link name must not be empty + safe fn empty(); +} + +#[link(name = " ")] +unsafe extern "C" { + #[link_name = " "] + safe fn this_is_fine(); +} + +#[export_name = " "] +extern "C" fn bar() -> i32 { + 42 +} + +#[link(name = "\0")] +//~^ ERROR link name may not contain null characters +unsafe extern "C" {} + +#[link(name = "foo\0")] +//~^ ERROR link name may not contain null characters +unsafe extern "C" {} + +#[link(name = "\0foo")] +//~^ ERROR link name may not contain null characters +unsafe extern "C" {} + +#[link(name = "fo\0o")] +//~^ ERROR link name may not contain null characters +unsafe extern "C" {} + +unsafe extern "C" { + #[link_name = "\0"] + //~^ ERROR link name may not contain null characters + safe fn empty_null(); + + #[link_name = "foo\0"] + //~^ ERROR link name may not contain null characters + safe fn trailing_null(); + + #[link_name = "\0foo"] + //~^ ERROR link name may not contain null characters + safe fn leading_null(); + + #[link_name = "fo\0o"] + //~^ ERROR link name may not contain null characters + safe fn middle_null(); +} diff --git a/tests/ui/attributes/invalid-link-name.stderr b/tests/ui/attributes/invalid-link-name.stderr new file mode 100644 index 0000000000000..b31c1d53bd81e --- /dev/null +++ b/tests/ui/attributes/invalid-link-name.stderr @@ -0,0 +1,64 @@ +error[E0454]: link name must not be empty + --> $DIR/invalid-link-name.rs:3:15 + | +LL | #[link(name = "")] + | ^^ empty link name + +error[E0454]: link name must not be empty + --> $DIR/invalid-link-name.rs:6:19 + | +LL | #[link_name = ""] + | ^^ empty link name + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:22:15 + | +LL | #[link(name = "\0")] + | ^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:26:15 + | +LL | #[link(name = "foo\0")] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:30:15 + | +LL | #[link(name = "\0foo")] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:34:15 + | +LL | #[link(name = "fo\0o")] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:39:19 + | +LL | #[link_name = "\0"] + | ^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:43:19 + | +LL | #[link_name = "foo\0"] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:47:19 + | +LL | #[link_name = "\0foo"] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:51:19 + | +LL | #[link_name = "fo\0o"] + | ^^^^^^^ + +error: aborting due to 10 previous errors + +Some errors have detailed explanations: E0454, E0648. +For more information about an error, try `rustc --explain E0454`. diff --git a/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs b/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs index 46e3798284b25..02262c2378c83 100644 --- a/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs +++ b/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs @@ -11,10 +11,8 @@ unsafe extern "C" { pub safe fn exit_0(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` #[link_name = "@GLIBC_2.2.5"] pub safe fn exit_1(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` - #[link_name = "ex\0it@GLIBC_2.2.5"] - pub safe fn exit_2(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` #[link_name = "exit@@GLIBC_2.2.5"] - pub safe fn exit_3(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` + pub safe fn exit_2(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` } fn main() {} diff --git a/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr b/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr index 5a979e7a3b1af..f83c63f06b8ba 100644 --- a/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr +++ b/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr @@ -16,11 +16,5 @@ error: link name must be well-formed if link kind is `raw-dylib` LL | pub safe fn exit_2(status: i32) -> !; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: link name must be well-formed if link kind is `raw-dylib` - --> $DIR/malformed-link-name.rs:17:5 - | -LL | pub safe fn exit_3(status: i32) -> !; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors From fd2e542110d108cdd1d57791b403d4d4e5c0f4e4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:17:51 +0200 Subject: [PATCH 04/13] Add regression test for issue 144329 --- .../issue-144329-niched-option-check.rs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/codegen-llvm/issues/issue-144329-niched-option-check.rs diff --git a/tests/codegen-llvm/issues/issue-144329-niched-option-check.rs b/tests/codegen-llvm/issues/issue-144329-niched-option-check.rs new file mode 100644 index 0000000000000..4f80608e7d34a --- /dev/null +++ b/tests/codegen-llvm/issues/issue-144329-niched-option-check.rs @@ -0,0 +1,64 @@ +//! Ensure that redundant null checks on `&mut T` from `Option<(_, &mut T)>` are eliminated. + +//@ compile-flags: -Copt-level=3 + +#![crate_type = "lib"] + +type T = [u64; 4]; + +// CHECK-LABEL: @f0( +#[no_mangle] +pub fn f0(stack: &mut Stack, f: fn(&T)) -> bool { + // CHECK-NOT: icmp eq ptr.*null.* + f_impl::<0>(stack, f) +} + +// CHECK-LABEL: @f1( +#[no_mangle] +pub fn f1(stack: &mut Stack, f: fn(&T)) -> bool { + // CHECK-NOT: icmp eq ptr.*null.* + f_impl::<1>(stack, f) +} + +// CHECK-LABEL: @f2( +#[no_mangle] +pub fn f2(stack: &mut Stack, f: fn(&T)) -> bool { + // CHECK-NOT: icmp eq ptr.*null.* + f_impl::<2>(stack, f) +} + +#[inline(always)] +fn f_impl(stack: &mut Stack, f: fn(&T)) -> bool { + let Some((a, b)) = stack.popn_top::() else { + return false; + }; + a.iter().for_each(f); + f(b); + true +} + +pub struct Stack { + data: Vec, +} + +impl Stack { + #[inline] + fn popn_top(&mut self) -> Option<([T; N], &mut T)> { + if self.data.len() < N + 1 { + return None; + } + unsafe { Some((self.popn_unchecked(), self.top_unchecked())) } + } + + unsafe fn popn_unchecked(&mut self) -> [T; N] { + core::array::from_fn(|_| unsafe { self.pop_unchecked() }) + } + + unsafe fn pop_unchecked(&mut self) -> T { + self.data.pop().unwrap_unchecked() + } + + unsafe fn top_unchecked(&mut self) -> &mut T { + self.data.last_mut().unwrap_unchecked() + } +} From 7eb6a201a89b8e36f122e2829dda707632990487 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:23:10 +0000 Subject: [PATCH 05/13] Update a bunch of bootstrap dependencies to remove windows-target --- src/bootstrap/Cargo.lock | 216 +++++++-------------------------------- 1 file changed, 39 insertions(+), 177 deletions(-) diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index a6d8d4610521b..0991be4cd02d7 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -175,14 +175,13 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -253,12 +252,12 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -269,14 +268,13 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] @@ -322,11 +320,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -347,13 +345,14 @@ dependencies = [ [[package]] name = "insta" -version = "1.43.1" +version = "1.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" dependencies = [ "console", "once_cell", "similar", + "tempfile", ] [[package]] @@ -364,12 +363,12 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "junction" -version = "1.3.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52f6e1bf39a7894f618c9d378904a11dbd7e10fe3ec20d1173600e79b1408d8" +checksum = "8cfc352a66ba903c23239ef51e809508b6fc2b0f90e3476ac7a9ff47e863ae95" dependencies = [ "scopeguard", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -380,9 +379,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libredox" @@ -397,9 +396,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" @@ -435,11 +434,11 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "normpath" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -506,13 +505,13 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opener" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771b9704f8cd8b424ec747a320b30b47517a6966ba2c7da90047c16f4a962223" +checksum = "a2fa337e0cf13357c13ef1dc108df1333eb192f75fc170bea03fcf1fd404c2ee" dependencies = [ "bstr", "normpath", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -622,15 +621,15 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -767,15 +766,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.21.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -939,11 +938,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -1134,52 +1133,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", ] [[package]] @@ -1191,102 +1149,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "wit-bindgen-rt" version = "0.39.0" From a80af229deac3a9961103a1a91e00ecc85475670 Mon Sep 17 00:00:00 2001 From: Cheese_space <99285740+Cheese-Space@users.noreply.github.com> Date: Sun, 3 May 2026 14:35:33 +0200 Subject: [PATCH 06/13] remove turbofish notation + use None / Some instead of Option:: --- library/std/src/keyword_docs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/keyword_docs.rs b/library/std/src/keyword_docs.rs index 1c8927435c3a1..e1dedad313ddc 100644 --- a/library/std/src/keyword_docs.rs +++ b/library/std/src/keyword_docs.rs @@ -961,14 +961,14 @@ mod loop_keyword {} /// returned. /// /// ```rust -/// let opt = Option::None::; +/// let opt: Option = None; /// let x = match opt { /// Some(int) => int, /// None => 10, /// }; /// assert_eq!(x, 10); /// -/// let a_number = Option::Some(10); +/// let a_number = Some(10); /// match a_number { /// Some(x) if x <= 5 => println!("0 to 5 num = {x}"), /// Some(x @ 6..=10) => println!("6 to 10 num = {x}"), From 09dc7fc2758a7dbf96a468762813351223cd63da Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 4 May 2026 10:12:36 +0200 Subject: [PATCH 07/13] mark some panicking methods around Duration as track_caller --- library/std/src/time.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/std/src/time.rs b/library/std/src/time.rs index 1805d8926098d..1f31ea5ced9d7 100644 --- a/library/std/src/time.rs +++ b/library/std/src/time.rs @@ -425,6 +425,7 @@ impl Add for Instant { /// /// This function may panic if the resulting point in time cannot be represented by the /// underlying data structure. See [`Instant::checked_add`] for a version without panic. + #[track_caller] fn add(self, other: Duration) -> Instant { self.checked_add(other).expect("overflow when adding duration to instant") } @@ -441,6 +442,7 @@ impl AddAssign for Instant { impl Sub for Instant { type Output = Instant; + #[track_caller] fn sub(self, other: Duration) -> Instant { self.checked_sub(other).expect("overflow when subtracting duration from instant") } @@ -742,8 +744,9 @@ impl Add for SystemTime { /// /// This function may panic if the resulting point in time cannot be represented by the /// underlying data structure. See [`SystemTime::checked_add`] for a version without panic. + #[track_caller] fn add(self, dur: Duration) -> SystemTime { - self.checked_add(dur).expect("overflow when adding duration to instant") + self.checked_add(dur).expect("overflow when adding duration to `SystemTime`") } } @@ -758,8 +761,9 @@ impl AddAssign for SystemTime { impl Sub for SystemTime { type Output = SystemTime; + #[track_caller] fn sub(self, dur: Duration) -> SystemTime { - self.checked_sub(dur).expect("overflow when subtracting duration from instant") + self.checked_sub(dur).expect("overflow when subtracting duration from `SystemTime`") } } From d0eab573aa79111113efbe5c5f9462065db5ba14 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 29 Apr 2026 15:06:44 +1000 Subject: [PATCH 08/13] Change `SwitchInt` handling in dataflow analysis. We call `get_switch_int_data` once for the switch and then pass that data to `apply_switch_int_edge_effect` for each switch target. The only case in practice is `MaybePlacesSwitchIntData` which does an awkward thing, maintaining an index into the discriminants and updating it on each call to `apply_switch_int_edge_effect`. This commit changes things to do more work up front in `get_switch_int_data`, in order to then do less work in `apply_switch_int_edge_effect`. This avoids the need for the `variants` and `next_discr` methods and the discriminants index. Overall it's a little simpler. --- compiler/rustc_middle/src/mir/basic_blocks.rs | 8 -- compiler/rustc_middle/src/mir/mod.rs | 2 +- .../src/framework/direction.rs | 23 ++-- .../rustc_mir_dataflow/src/framework/mod.rs | 18 ++- .../src/impls/initialized.rs | 105 +++++++++--------- compiler/rustc_mir_dataflow/src/lib.rs | 4 +- 6 files changed, 80 insertions(+), 80 deletions(-) diff --git a/compiler/rustc_middle/src/mir/basic_blocks.rs b/compiler/rustc_middle/src/mir/basic_blocks.rs index 0ba60a1213baa..1ac5ed5e3b1e3 100644 --- a/compiler/rustc_middle/src/mir/basic_blocks.rs +++ b/compiler/rustc_middle/src/mir/basic_blocks.rs @@ -21,14 +21,6 @@ pub struct BasicBlocks<'tcx> { // Typically 95%+ of basic blocks have 4 or fewer predecessors. type Predecessors = IndexVec>; -#[derive(Debug, Clone, Copy)] -pub enum SwitchTargetValue { - // A normal switch value. - Normal(u128), - // The final "otherwise" fallback value. - Otherwise, -} - #[derive(Clone, Default, Debug)] struct Cache { predecessors: OnceLock, diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 6124b92da5c10..7bdf2add79b98 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -7,7 +7,7 @@ use std::fmt::{self, Debug, Formatter}; use std::iter; use std::ops::{Index, IndexMut}; -pub use basic_blocks::{BasicBlocks, SwitchTargetValue}; +pub use basic_blocks::BasicBlocks; use either::Either; use polonius_engine::Atom; use rustc_abi::{FieldIdx, VariantIdx}; diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs index b15b5c07ce382..f8d0885a2808f 100644 --- a/compiler/rustc_mir_dataflow/src/framework/direction.rs +++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs @@ -1,12 +1,10 @@ use std::ops::RangeInclusive; use rustc_middle::bug; -use rustc_middle::mir::{ - self, BasicBlock, CallReturnPlaces, Location, SwitchTargetValue, TerminatorEdges, -}; +use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges}; use super::visitor::ResultsVisitor; -use super::{Analysis, Effect, EffectIndex}; +use super::{Analysis, Effect, EffectIndex, SwitchTargetIndex}; pub trait Direction { const IS_FORWARD: bool; @@ -113,8 +111,8 @@ impl Direction for Backward { propagate(pred, &tmp); } - mir::TerminatorKind::SwitchInt { ref discr, .. } => { - if let Some(_data) = analysis.get_switch_int_data(pred, discr) { + mir::TerminatorKind::SwitchInt { ref targets, ref discr } => { + if let Some(_data) = analysis.get_switch_int_data(pred, targets, discr) { bug!( "SwitchInt edge effects are unsupported in backward dataflow analyses" ); @@ -283,12 +281,12 @@ impl Direction for Forward { } } TerminatorEdges::SwitchInt { targets, discr } => { - if let Some(mut data) = analysis.get_switch_int_data(block, discr) { + if let Some(mut data) = analysis.get_switch_int_data(block, targets, discr) { let mut tmp = analysis.bottom_value(body); - for (value, target) in targets.iter() { + for (i, (_value, target)) in targets.iter().enumerate() { tmp.clone_from(exit_state); - let value = SwitchTargetValue::Normal(value); - analysis.apply_switch_int_edge_effect(&mut data, &mut tmp, value, targets); + let target_idx = SwitchTargetIndex::Normal(i); + analysis.apply_switch_int_edge_effect(&mut tmp, &mut data, target_idx); propagate(target, &tmp); } @@ -296,10 +294,9 @@ impl Direction for Forward { // `exit_state`, so pass it directly to `apply_switch_int_edge_effect` to save // a clone of the dataflow state. analysis.apply_switch_int_edge_effect( - &mut data, exit_state, - SwitchTargetValue::Otherwise, - targets, + &mut data, + SwitchTargetIndex::Otherwise, ); propagate(targets.otherwise(), exit_state); } else { diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs index 60b3c15d80d79..6445ba7ad27b6 100644 --- a/compiler/rustc_mir_dataflow/src/framework/mod.rs +++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs @@ -38,9 +38,7 @@ use rustc_data_structures::work_queue::WorkQueue; use rustc_index::bit_set::{DenseBitSet, MixedBitSet}; use rustc_index::{Idx, IndexVec}; use rustc_middle::bug; -use rustc_middle::mir::{ - self, BasicBlock, CallReturnPlaces, Location, SwitchTargetValue, TerminatorEdges, traversal, -}; +use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges, traversal}; use rustc_middle::ty::TyCtxt; use tracing::error; @@ -212,6 +210,7 @@ pub trait Analysis<'tcx> { fn get_switch_int_data( &self, _block: mir::BasicBlock, + _targets: &mir::SwitchTargets, _discr: &mir::Operand<'tcx>, ) -> Option { None @@ -220,10 +219,9 @@ pub trait Analysis<'tcx> { /// See comments on `get_switch_int_data`. fn apply_switch_int_edge_effect( &self, - _data: &mut Self::SwitchIntData, _state: &mut Self::Domain, - _value: SwitchTargetValue, - _targets: &mir::SwitchTargets, + _data: &mut Self::SwitchIntData, + _target_idx: SwitchTargetIndex, ) { unreachable!(); } @@ -313,6 +311,14 @@ pub trait Analysis<'tcx> { } } +#[derive(Debug, Clone, Copy)] +pub enum SwitchTargetIndex { + // Index of a normal switch target. + Normal(usize), + // The final "otherwise" fallback target. + Otherwise, +} + /// The legal operations for a transfer function in a gen/kill problem. pub trait GenKill { /// Inserts `elem` into the state vector. diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs index a59798dafbda9..7702a14357938 100644 --- a/compiler/rustc_mir_dataflow/src/impls/initialized.rs +++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs @@ -4,10 +4,7 @@ use rustc_abi::VariantIdx; use rustc_index::Idx; use rustc_index::bit_set::{DenseBitSet, MixedBitSet}; use rustc_middle::bug; -use rustc_middle::mir::{ - self, Body, CallReturnPlaces, Location, SwitchTargetValue, TerminatorEdges, -}; -use rustc_middle::ty::util::Discr; +use rustc_middle::mir::{self, Body, CallReturnPlaces, Location, TerminatorEdges}; use rustc_middle::ty::{self, TyCtxt}; use smallvec::SmallVec; use tracing::{debug, instrument}; @@ -15,37 +12,22 @@ use tracing::{debug, instrument}; use crate::drop_flag_effects::{DropFlagState, InactiveVariants}; use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex}; use crate::{ - Analysis, GenKill, MaybeReachable, drop_flag_effects, drop_flag_effects_for_function_entry, - drop_flag_effects_for_location, on_all_children_bits, on_lookup_result_bits, + Analysis, GenKill, MaybeReachable, SwitchTargetIndex, drop_flag_effects, + drop_flag_effects_for_function_entry, drop_flag_effects_for_location, on_all_children_bits, + on_lookup_result_bits, }; // Used by both `MaybeInitializedPlaces` and `MaybeUninitializedPlaces`. pub struct MaybePlacesSwitchIntData<'tcx> { enum_place: mir::Place<'tcx>, - discriminants: Vec<(VariantIdx, Discr<'tcx>)>, - index: usize, -} -impl<'tcx> MaybePlacesSwitchIntData<'tcx> { - /// Creates a `SmallVec` mapping each target in `targets` to its `VariantIdx`. - fn variants(&mut self, targets: &mir::SwitchTargets) -> SmallVec<[VariantIdx; 4]> { - self.index = 0; - targets.all_values().iter().map(|value| self.next_discr(value.get())).collect() - } - - // The discriminant order in the `SwitchInt` targets should match the order yielded by - // `AdtDef::discriminants`. We rely on this to match each discriminant in the targets to its - // corresponding variant in linear time. - fn next_discr(&mut self, value: u128) -> VariantIdx { - // An out-of-bounds abort will occur if the discriminant ordering isn't as described above. - loop { - let (variant, discr) = self.discriminants[self.index]; - self.index += 1; - if discr.val == value { - return variant; - } - } - } + // Variant indices targeted by the SwitchInt. For example, if you have: + // ``` + // enum E { A = 1, B = 3, C = 5, D = 7 } + // ``` + // and a `SwitchInt(A -> bb1, C -> bb2, _ -> bb3)`, this vec will contain `[0, 2]` because + // those are the variant indices for `A` and `C`. + variants: SmallVec<[VariantIdx; 4]>, } impl<'tcx> MaybePlacesSwitchIntData<'tcx> { @@ -53,6 +35,7 @@ impl<'tcx> MaybePlacesSwitchIntData<'tcx> { tcx: TyCtxt<'tcx>, body: &Body<'tcx>, block: mir::BasicBlock, + targets: &mir::SwitchTargets, discr: &mir::Operand<'tcx>, ) -> Option { let Some(discr) = discr.place() else { return None }; @@ -76,11 +59,29 @@ impl<'tcx> MaybePlacesSwitchIntData<'tcx> { { match enum_place.ty(body, tcx).ty.kind() { ty::Adt(enum_def, _) => { - return Some(MaybePlacesSwitchIntData { - enum_place, - discriminants: enum_def.discriminants(tcx).collect(), - index: 0, - }); + // The value of each discriminant, in AdtDef order. + let discriminant_vals: SmallVec<[u128; 4]> = + enum_def.discriminants(tcx).map(|(_, discr)| discr.val).collect(); + let mut i = 0; + + // For each value in the SwitchInt, find the VariantIdx for the variant + // with that value. This works because `discriminant_vals` and + // `targets.all_values()` are guaranteed to list variants in the same + // order. (If that ever changes we will get out-of-bounds panics here.) + let variants = targets + .all_values() + .iter() + .map(|value| { + loop { + if discriminant_vals[i] == value.get() { + return VariantIdx::new(i); + } + i += 1; + } + }) + .collect(); + + return Some(MaybePlacesSwitchIntData { enum_place, variants }); } // `Rvalue::Discriminant` is also used to get the active yield point for a @@ -452,26 +453,28 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { fn get_switch_int_data( &self, block: mir::BasicBlock, + targets: &mir::SwitchTargets, discr: &mir::Operand<'tcx>, ) -> Option { if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { return None; } - MaybePlacesSwitchIntData::new(self.tcx, self.body, block, discr) + MaybePlacesSwitchIntData::new(self.tcx, self.body, block, targets, discr) } fn apply_switch_int_edge_effect( &self, - data: &mut Self::SwitchIntData, state: &mut Self::Domain, - value: SwitchTargetValue, - targets: &mir::SwitchTargets, + data: &mut Self::SwitchIntData, + target_idx: SwitchTargetIndex, ) { - let inactive_variants = match value { - SwitchTargetValue::Normal(value) => InactiveVariants::Active(data.next_discr(value)), - SwitchTargetValue::Otherwise if self.exclude_inactive_in_otherwise => { - InactiveVariants::Inactives(data.variants(targets)) + let inactive_variants = match target_idx { + SwitchTargetIndex::Normal(target_idx) => { + InactiveVariants::Active(data.variants[target_idx]) + } + SwitchTargetIndex::Otherwise if self.exclude_inactive_in_otherwise => { + InactiveVariants::Inactives(data.variants.clone()) } _ => return, }; @@ -568,6 +571,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { fn get_switch_int_data( &self, block: mir::BasicBlock, + targets: &mir::SwitchTargets, discr: &mir::Operand<'tcx>, ) -> Option { if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { @@ -578,20 +582,21 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { return None; } - MaybePlacesSwitchIntData::new(self.tcx, self.body, block, discr) + MaybePlacesSwitchIntData::new(self.tcx, self.body, block, targets, discr) } fn apply_switch_int_edge_effect( &self, - data: &mut Self::SwitchIntData, state: &mut Self::Domain, - value: SwitchTargetValue, - targets: &mir::SwitchTargets, + data: &mut Self::SwitchIntData, + target_idx: SwitchTargetIndex, ) { - let inactive_variants = match value { - SwitchTargetValue::Normal(value) => InactiveVariants::Active(data.next_discr(value)), - SwitchTargetValue::Otherwise if self.include_inactive_in_otherwise => { - InactiveVariants::Inactives(data.variants(targets)) + let inactive_variants = match target_idx { + SwitchTargetIndex::Normal(target_idx) => { + InactiveVariants::Active(data.variants[target_idx]) + } + SwitchTargetIndex::Otherwise if self.include_inactive_in_otherwise => { + InactiveVariants::Inactives(data.variants.clone()) } _ => return, }; diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs index c5026c1c92aa6..2b290463a72c5 100644 --- a/compiler/rustc_mir_dataflow/src/lib.rs +++ b/compiler/rustc_mir_dataflow/src/lib.rs @@ -17,8 +17,8 @@ pub use self::drop_flag_effects::{ }; pub use self::framework::{ Analysis, Backward, Direction, EntryStates, Forward, GenKill, JoinSemiLattice, MaybeReachable, - Results, ResultsCursor, ResultsVisitor, fmt, graphviz, lattice, visit_reachable_results, - visit_results, + Results, ResultsCursor, ResultsVisitor, SwitchTargetIndex, fmt, graphviz, lattice, + visit_reachable_results, visit_results, }; use self::move_paths::MoveData; From a275453887155727db06313a68d5fa61977c461b Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Fri, 8 May 2026 19:03:10 +0000 Subject: [PATCH 09/13] replace leftover call to ArgParser::list with AcceptContext::expect_lits --- .../rustc_attr_parsing/src/attributes/repr.rs | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs index cc6f3c83e0475..496510f67c3f2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/repr.rs +++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs @@ -285,33 +285,29 @@ impl RustcAlignParser { const TEMPLATE: AttributeTemplate = template!(List: &[""]); fn parse(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) { - match args { - ArgParser::NoArgs | ArgParser::NameValue(_) => { - let attr_span = cx.attr_span; - cx.adcx().expected_list(attr_span, args); - } - ArgParser::List(list) => { - let Some(align) = cx.expect_single(list) else { - return; - }; - - let Some(lit) = align.lit() else { - cx.emit_err(session_diagnostics::IncorrectReprFormatExpectInteger { - span: align.span(), - }); - - return; - }; - - match parse_alignment(&lit.kind, cx) { - Ok(literal) => self.0 = Ord::max(self.0, Some((literal, cx.attr_span))), - Err(message) => { - cx.emit_err(session_diagnostics::InvalidAlignmentValue { - span: lit.span, - error_part: message, - }); - } - } + let Some(list) = cx.expect_list(args, cx.attr_span) else { + return; + }; + + let Some(align) = cx.expect_single(list) else { + return; + }; + + let Some(lit) = align.lit() else { + cx.emit_err(session_diagnostics::IncorrectReprFormatExpectInteger { + span: align.span(), + }); + + return; + }; + + match parse_alignment(&lit.kind, cx) { + Ok(literal) => self.0 = Ord::max(self.0, Some((literal, cx.attr_span))), + Err(message) => { + cx.emit_err(session_diagnostics::InvalidAlignmentValue { + span: lit.span, + error_part: message, + }); } } } From 9097f5b67a58eeaa85eb7b662a442265b526fb90 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Fri, 8 May 2026 19:10:24 +0000 Subject: [PATCH 10/13] rustc_attr_parsing: improve error message for codegen attributes --- compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 6e606def83625..27501fb3cec1d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -594,8 +594,10 @@ impl SingleAttributeParser for SanitizeParser { return; } None => { - cx.adcx() - .expected_string_literal(value.value_span, Some(value.value_as_lit())); + cx.adcx().expected_specific_argument_strings( + value.value_span, + &[sym::on, sym::off], + ); return; } }; From dcdba78e7e3ecafea3a786fcb2854e61eadfc074 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Fri, 8 May 2026 19:10:24 +0000 Subject: [PATCH 11/13] rustc_attr_parsing: add AcceptContext::expect_string_literal --- compiler/rustc_ast/src/attr/mod.rs | 2 +- .../rustc_attr_parsing/src/attributes/cfg.rs | 4 +- .../src/attributes/cfi_encoding.rs | 5 +- .../src/attributes/codegen_attrs.rs | 8 +-- .../src/attributes/confusables.rs | 5 +- .../src/attributes/crate_level.rs | 10 +-- .../src/attributes/debugger.rs | 5 +- .../rustc_attr_parsing/src/attributes/doc.rs | 6 +- .../src/attributes/inline.rs | 14 +--- .../src/attributes/link_attrs.rs | 29 +++------ .../src/attributes/must_not_suspend.rs | 8 +-- .../src/attributes/must_use.rs | 11 +--- .../rustc_attr_parsing/src/attributes/path.rs | 5 +- .../src/attributes/prototype.rs | 3 +- .../rustc_attr_parsing/src/attributes/repr.rs | 6 +- .../src/attributes/rustc_allocator.rs | 5 +- .../src/attributes/rustc_internal.rs | 54 ++++----------- .../src/attributes/stability.rs | 9 +-- .../src/attributes/test_attrs.rs | 28 ++------ .../rustc_attr_parsing/src/attributes/util.rs | 7 +- compiler/rustc_attr_parsing/src/context.rs | 65 +++++++++++++++++++ compiler/rustc_attr_parsing/src/parser.rs | 2 +- compiler/rustc_hir/src/hir.rs | 2 +- 23 files changed, 116 insertions(+), 177 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 0374a86d3eb1c..88556aa58c773 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -845,7 +845,7 @@ pub fn list_contains_name(items: &[MetaItemInner], name: Symbol) -> bool { } impl MetaItemLit { - pub fn value_str(&self) -> Option { + pub fn value_as_str(&self) -> Option { LitKind::from_token_lit(self.as_token_lit()).ok().and_then(|lit| lit.str()) } } diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 4cc07ceaf231f..f838f73838499 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -135,12 +135,12 @@ fn parse_cfg_entry_version( cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span }) ); }; - let Some(version_lit) = version.lit() else { + let Some(version_lit) = version.as_lit() else { return Err( cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() }) ); }; - let Some(version_str) = version_lit.value_str() else { + let Some(version_str) = version_lit.value_as_str() else { return Err( cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span }) ); diff --git a/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs b/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs index 1a99e8ff1a6ff..0f6b5ee8ad9d8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs @@ -13,10 +13,7 @@ impl SingleAttributeParser for CfiEncodingParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let name_value = cx.expect_name_value(args, cx.attr_span, Some(sym::cfi_encoding))?; - let Some(value_str) = name_value.value_as_str() else { - cx.adcx().expected_string_literal(name_value.value_span, None); - return None; - }; + let value_str = cx.expect_string_literal(name_value)?; if value_str.as_str().trim().is_empty() { cx.adcx().expected_non_empty_string_literal(name_value.value_span); diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 27501fb3cec1d..5c2a41a7a970b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -119,10 +119,7 @@ impl SingleAttributeParser for ExportNameParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(name) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let name = cx.expect_string_literal(nv)?; if name.as_str().contains('\0') { // `#[export_name = ...]` will be converted to a null-terminated string, // so it may not contain any null characters. @@ -478,8 +475,7 @@ fn parse_tf_attribute( } // Use value - let Some(value_str) = value.value_as_str() else { - cx.adcx().expected_string_literal(value.value_span, Some(value.value_as_lit())); + let Some(value_str) = cx.expect_string_literal(value) else { return features; }; for feature in value_str.as_str().split(",") { diff --git a/compiler/rustc_attr_parsing/src/attributes/confusables.rs b/compiler/rustc_attr_parsing/src/attributes/confusables.rs index 5877ac5819aac..311fc0d33c1f8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/confusables.rs +++ b/compiler/rustc_attr_parsing/src/attributes/confusables.rs @@ -19,10 +19,7 @@ impl AttributeParser for ConfusablesParser { } for param in list.mixed() { - let span = param.span(); - - let Some(lit) = param.lit().and_then(|i| i.value_str()) else { - cx.adcx().expected_string_literal(span, param.lit()); + let Some(lit) = cx.expect_string_literal(param) else { continue; }; diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 038246536a63f..9d6887a338739 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -17,10 +17,7 @@ impl SingleAttributeParser for CrateNameParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let n = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(name) = n.value_as_str() else { - cx.adcx().expected_string_literal(n.value_span, Some(n.value_as_lit())); - return None; - }; + let name = cx.expect_string_literal(n)?; Some(AttributeKind::CrateName { name, name_span: n.value_span, attr_span: cx.attr_span }) } @@ -44,10 +41,7 @@ impl CombineAttributeParser for CrateTypeParser { ) -> impl IntoIterator { let n = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(crate_type) = n.value_as_str() else { - cx.adcx().expected_string_literal(n.value_span, Some(n.value_as_lit())); - return None; - }; + let crate_type = cx.expect_string_literal(n)?; let Ok(crate_type) = crate_type.try_into() else { // We don't error on invalid `#![crate_type]` when not applied to a crate diff --git a/compiler/rustc_attr_parsing/src/attributes/debugger.rs b/compiler/rustc_attr_parsing/src/attributes/debugger.rs index b77fdfadf8240..0fc4166a178e4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/debugger.rs +++ b/compiler/rustc_attr_parsing/src/attributes/debugger.rs @@ -34,10 +34,7 @@ impl CombineAttributeParser for DebuggerViualizerParser { } }; - let Some(path) = args.value_as_str() else { - cx.adcx().expected_string_literal(args.value_span, Some(args.value_as_lit())); - return None; - }; + let path = cx.expect_string_literal(args)?; Some(DebugVisualizer { span: ident.span.to(args.value_span), visualizer_type, path }) } diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 10232a7c55a5f..535f67f67a263 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -255,8 +255,7 @@ impl DocParser { } ArgParser::List(list) => { for i in list.mixed() { - let Some(alias) = i.lit().and_then(|i| i.value_str()) else { - cx.adcx().expected_string_literal(i.span(), i.lit()); + let Some(alias) = cx.expect_string_literal(i) else { continue; }; @@ -264,8 +263,7 @@ impl DocParser { } } ArgParser::NameValue(nv) => { - let Some(alias) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + let Some(alias) = cx.expect_string_literal(nv) else { return; }; self.add_alias(cx, alias, nv.value_span); diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs index a02f0a89cc042..aee0537771fd4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/inline.rs +++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs @@ -77,21 +77,11 @@ impl SingleAttributeParser for RustcForceInlineParser { ArgParser::List(list) => { let l = cx.expect_single(list)?; - let Some(reason) = l.lit().and_then(|i| i.kind.str()) else { - cx.adcx().expected_string_literal(l.span(), l.lit()); - return None; - }; - - Some(reason) - } - ArgParser::NameValue(v) => { - let Some(reason) = v.value_as_str() else { - cx.adcx().expected_string_literal(v.value_span, Some(v.value_as_lit())); - return None; - }; + let reason = cx.expect_string_literal(l)?; Some(reason) } + ArgParser::NameValue(v) => cx.expect_string_literal(v), }; Some(AttributeKind::Inline( diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index d9310906dfeef..0685f989c383d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -37,10 +37,7 @@ impl SingleAttributeParser for LinkNameParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(name) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let name = cx.expect_string_literal(nv)?; Some(LinkName { name, span: cx.attr_span }) } @@ -260,8 +257,7 @@ impl LinkParser { let Some(nv) = cx.expect_name_value(item.args(), item.span(), Some(sym::name)) else { return false; }; - let Some(link_name) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.args_span(), Some(nv.value_as_lit())); + let Some(link_name) = cx.expect_string_literal(nv) else { return false; }; @@ -286,8 +282,7 @@ impl LinkParser { let Some(nv) = cx.expect_name_value(item.args(), item.span(), Some(sym::kind)) else { return true; }; - let Some(link_kind) = nv.value_as_str() else { - cx.adcx().expected_string_literal(item.span(), Some(nv.value_as_lit())); + let Some(link_kind) = cx.expect_string_literal(nv) else { return true; }; @@ -365,8 +360,7 @@ impl LinkParser { let Some(nv) = cx.expect_name_value(item.args(), item.span(), Some(sym::modifiers)) else { return true; }; - let Some(link_modifiers) = nv.value_as_str() else { - cx.adcx().expected_string_literal(item.span(), Some(nv.value_as_lit())); + let Some(link_modifiers) = cx.expect_string_literal(nv) else { return true; }; *modifiers = Some((link_modifiers, nv.value_span)); @@ -408,8 +402,7 @@ impl LinkParser { else { return true; }; - let Some(link_wasm_import_module) = nv.value_as_str() else { - cx.adcx().expected_string_literal(item.span(), Some(nv.value_as_lit())); + let Some(link_wasm_import_module) = cx.expect_string_literal(nv) else { return true; }; *wasm_import_module = Some((link_wasm_import_module, item.span())); @@ -429,8 +422,7 @@ impl LinkParser { else { return true; }; - let Some(link_import_name_type) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + let Some(link_import_name_type) = cx.expect_string_literal(nv) else { return true; }; if cx.sess().target.arch != Arch::X86 { @@ -498,10 +490,7 @@ impl SingleAttributeParser for LinkSectionParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(name) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let name = cx.expect_string_literal(nv)?; if name.as_str().contains('\0') { // `#[link_section = ...]` will be converted to a null-terminated string, // so it may not contain any null characters. @@ -630,9 +619,7 @@ impl SingleAttributeParser for LinkageParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let name_value = cx.expect_name_value(args, cx.attr_span, Some(sym::linkage))?; - let Some(value) = name_value.value_as_str() else { - cx.adcx() - .expected_string_literal(name_value.value_span, Some(name_value.value_as_lit())); + let Some(value) = cx.expect_string_literal(name_value) else { return None; }; diff --git a/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs b/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs index de7ea837b3a7c..f8e5cb37328a2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs +++ b/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs @@ -14,13 +14,7 @@ impl SingleAttributeParser for MustNotSuspendParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let reason = match args { - ArgParser::NameValue(reason) => match reason.value_as_str() { - Some(val) => Some(val), - None => { - cx.adcx().expected_nv_or_no_args(reason.value_span); - return None; - } - }, + ArgParser::NameValue(reason) => cx.expect_string_literal(reason), ArgParser::NoArgs => None, ArgParser::List(list) => { cx.adcx().expected_nv_or_no_args(list.span); diff --git a/compiler/rustc_attr_parsing/src/attributes/must_use.rs b/compiler/rustc_attr_parsing/src/attributes/must_use.rs index 6cdf8c74c0c5f..646ac69b6452a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/must_use.rs +++ b/compiler/rustc_attr_parsing/src/attributes/must_use.rs @@ -30,16 +30,7 @@ impl SingleAttributeParser for MustUseParser { span: cx.attr_span, reason: match args { ArgParser::NoArgs => None, - ArgParser::NameValue(name_value) => { - let Some(value_str) = name_value.value_as_str() else { - cx.adcx().expected_string_literal( - name_value.value_span, - Some(&name_value.value_as_lit()), - ); - return None; - }; - Some(value_str) - } + ArgParser::NameValue(name_value) => cx.expect_string_literal(name_value), ArgParser::List(list) => { cx.adcx().expected_nv_or_no_args(list.span); return None; diff --git a/compiler/rustc_attr_parsing/src/attributes/path.rs b/compiler/rustc_attr_parsing/src/attributes/path.rs index 0ff87866d6245..8b6f20478df7c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/path.rs +++ b/compiler/rustc_attr_parsing/src/attributes/path.rs @@ -14,10 +14,7 @@ impl SingleAttributeParser for PathParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(path) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let path = cx.expect_string_literal(nv)?; Some(AttributeKind::Path(path)) } diff --git a/compiler/rustc_attr_parsing/src/attributes/prototype.rs b/compiler/rustc_attr_parsing/src/attributes/prototype.rs index fbc27a8d0fd16..8b8eccab029dd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/prototype.rs +++ b/compiler/rustc_attr_parsing/src/attributes/prototype.rs @@ -74,8 +74,7 @@ fn extract_value( return; } - let Some(value_sym) = val.value_as_str() else { - cx.adcx().expected_string_literal(val.value_span, Some(val.value_as_lit())); + let Some(value_sym) = cx.expect_string_literal(val) else { *failed = true; return; }; diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs index 496510f67c3f2..0ba32c6ca8fa9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/repr.rs +++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs @@ -43,7 +43,7 @@ impl CombineAttributeParser for ReprParser { } for param in list.mixed() { - if let Some(_) = param.lit() { + if let Some(_) = param.as_lit() { cx.emit_err(session_diagnostics::ReprIdent { span: cx.attr_span }); continue; } @@ -212,7 +212,7 @@ fn parse_repr_align( return None; }; - let Some(lit) = align.lit() else { + let Some(lit) = align.as_lit() else { match align_kind { Packed => { cx.emit_err(session_diagnostics::IncorrectReprFormatPackedExpectInteger { @@ -293,7 +293,7 @@ impl RustcAlignParser { return; }; - let Some(lit) = align.lit() else { + let Some(lit) = align.as_lit() else { cx.emit_err(session_diagnostics::IncorrectReprFormatExpectInteger { span: align.span(), }); diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs index 3706fec5e770b..b21677b4f79ea 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs @@ -27,10 +27,7 @@ impl SingleAttributeParser for RustcAllocatorZeroedVariantParser { const TEMPLATE: AttributeTemplate = template!(NameValueStr: "function"); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(name) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let name = cx.expect_string_literal(nv)?; Some(AttributeKind::RustcAllocatorZeroedVariant { name }) } diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 859293bf33a1e..8a648550ae8c6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -152,14 +152,9 @@ impl SingleAttributeParser for RustcLintOptDenyFieldAccessParser { const TEMPLATE: AttributeTemplate = template!(Word); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let arg = cx.expect_single_element_list(args, cx.attr_span)?; + let lint_message = cx.expect_string_literal(arg)?; - let MetaItemOrLitParser::Lit(MetaItemLit { kind: LitKind::Str(lint_message, _), .. }) = arg - else { - cx.adcx().expected_string_literal(arg.span(), arg.lit()); - return None; - }; - - Some(AttributeKind::RustcLintOptDenyFieldAccess { lint_message: *lint_message }) + Some(AttributeKind::RustcLintOptDenyFieldAccess { lint_message }) } } @@ -204,10 +199,7 @@ fn parse_cgu_fields( } }; - let Some(str) = arg.value_as_str() else { - cx.adcx().expected_string_literal(arg.value_span, Some(arg.value_as_lit())); - continue; - }; + let str = cx.expect_string_literal(arg)?; if res.is_some() { cx.adcx().duplicate_key(ident.span.to(arg.args_span()), ident.name); @@ -324,10 +316,7 @@ impl SingleAttributeParser for RustcDeprecatedSafe2024Parser { return None; }; - let Some(suggestion) = arg.value_as_str() else { - cx.adcx().expected_string_literal(arg.value_span, Some(arg.value_as_lit())); - return None; - }; + let suggestion = cx.expect_string_literal(arg)?; Some(AttributeKind::RustcDeprecatedSafe2024 { suggestion }) } @@ -388,10 +377,7 @@ impl SingleAttributeParser for RustcNeverTypeOptionsParser { } }; - let Some(field) = arg.value_as_str() else { - cx.adcx().expected_string_literal(arg.value_span, Some(arg.value_as_lit())); - continue; - }; + let field = cx.expect_string_literal(arg)?; if res.is_some() { cx.adcx().duplicate_key(ident.span, ident.name); @@ -551,10 +537,7 @@ impl SingleAttributeParser for LangParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(name) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let name = cx.expect_string_literal(nv)?; let Some(lang_item) = LangItem::from_name(name) else { cx.emit_err(UnknownLangItem { span: cx.attr_span, name }); return None; @@ -649,10 +632,7 @@ impl CombineAttributeParser for RustcMirParser { mi.span(), Some(sym::borrowck_graphviz_postflow), )?; - let Some(path) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, None); - return None; - }; + let path = cx.expect_string_literal(nv)?; let path = PathBuf::from(path.to_string()); if path.file_name().is_some() { Some(RustcMirKind::BorrowckGraphvizPostflow { path }) @@ -757,8 +737,7 @@ impl CombineAttributeParser for RustcCleanParser { continue; }; let value_span = value.value_span; - let Some(value) = value.value_as_str() else { - cx.adcx().expected_string_literal(value_span, None); + let Some(value) = cx.expect_string_literal(value) else { continue; }; match ident.name { @@ -983,10 +962,7 @@ impl SingleAttributeParser for RustcDiagnosticItemParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - let Some(value) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let value = cx.expect_string_literal(nv)?; Some(AttributeKind::RustcDiagnosticItem(value)) } } @@ -1038,11 +1014,7 @@ impl SingleAttributeParser for RustcReservationImplParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - - let Some(value_str) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let value_str = cx.expect_string_literal(nv)?; Some(AttributeKind::RustcReservationImpl(value_str)) } @@ -1065,11 +1037,7 @@ impl SingleAttributeParser for RustcDocPrimitiveParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; - - let Some(value_str) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let value_str = cx.expect_string_literal(nv)?; Some(AttributeKind::RustcDocPrimitive(cx.attr_span, value_str)) } diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs index d70c66dc18cf4..4d8943aa36684 100644 --- a/compiler/rustc_attr_parsing/src/attributes/stability.rs +++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs @@ -105,8 +105,7 @@ impl AttributeParser for StabilityParser { let Some(nv) = cx.expect_name_value(args, cx.attr_span, None) else { return; }; - let Some(value_str) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + let Some(value_str) = cx.expect_string_literal(nv) else { return; }; this.allowed_through_unstable_modules = Some(value_str); @@ -292,11 +291,7 @@ fn insert_value_into_option_or_error( } let (_ident, arg) = cx.expect_name_value(param, param.span(), Some(name.name))?; - - let Some(s) = arg.value_as_str() else { - cx.adcx().expected_string_literal(arg.value_span, Some(arg.value_as_lit())); - return None; - }; + let s = cx.expect_string_literal(arg)?; *item = Some(s); diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index 1ae8dd0e37c20..7ffeb3d2194a3 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -60,16 +60,7 @@ impl SingleAttributeParser for ShouldPanicParser { Some(AttributeKind::ShouldPanic { reason: match args { ArgParser::NoArgs => None, - ArgParser::NameValue(name_value) => { - let Some(str_value) = name_value.value_as_str() else { - cx.adcx().expected_string_literal( - name_value.value_span, - Some(name_value.value_as_lit()), - ); - return None; - }; - Some(str_value) - } + ArgParser::NameValue(name_value) => cx.expect_string_literal(name_value), ArgParser::List(list) => { let single = cx.expect_single(list)?; let (ident, arg) = @@ -78,11 +69,7 @@ impl SingleAttributeParser for ShouldPanicParser { cx.adcx().expected_specific_argument_strings(list.span, &[sym::expected]); return None; } - let Some(expected) = arg.value_as_str() else { - cx.adcx().expected_string_literal(arg.value_span, Some(arg.value_as_lit())); - return None; - }; - Some(expected) + cx.expect_string_literal(arg) } }, }) @@ -103,10 +90,7 @@ impl SingleAttributeParser for ReexportTestHarnessMainParser { Some(sym::reexport_test_harness_main), )?; - let Some(name) = nv.value_as_str() else { - cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let name = cx.expect_string_literal(nv)?; Some(AttributeKind::ReexportTestHarnessMain(name)) } @@ -211,11 +195,7 @@ impl SingleAttributeParser for RustcTestMarkerParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let name_value = cx.expect_name_value(args, cx.attr_span, Some(sym::rustc_test_marker))?; - - let Some(value_str) = name_value.value_as_str() else { - cx.adcx().expected_string_literal(name_value.value_span, None); - return None; - }; + let value_str = cx.expect_string_literal(name_value)?; if value_str.as_str().trim().is_empty() { cx.adcx().expected_non_empty_string_literal(name_value.value_span); diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs index 438c6ba435056..82f515aff3999 100644 --- a/compiler/rustc_attr_parsing/src/attributes/util.rs +++ b/compiler/rustc_attr_parsing/src/attributes/util.rs @@ -41,7 +41,7 @@ pub(crate) fn parse_single_integer( args: &ArgParser, ) -> Option { let single = cx.expect_single_element_list(args, cx.attr_span)?; - let Some(lit) = single.lit() else { + let Some(lit) = single.as_lit() else { cx.adcx().expected_integer_literal(single.span()); return None; }; @@ -54,10 +54,7 @@ pub(crate) fn parse_single_integer( impl AcceptContext<'_, '_> { pub(crate) fn parse_limit_int(&mut self, nv: &NameValueParser) -> Option { - let Some(limit) = nv.value_as_str() else { - self.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit())); - return None; - }; + let limit = self.expect_string_literal(nv)?; let error_str = match limit.as_str().parse() { Ok(i) => return Some(Limit::new(i)), diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index bf4989b83200b..81cd32faea127 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -562,6 +562,13 @@ impl<'f, 'sess: 'f> AcceptContext<'f, 'sess> { } /// Assert that an [`ArgParser`] has no args, or emits an error and return `None`. + /// + /// This is a higher-level (and harder to misuse) wrapper over multiple + /// [`ArgParser::as_no_args`]. You may still want to use the lower-level methods for the + /// following reasons: + /// + /// - You want to emit your own diagnostics (for instance, with [`SharedContext::emit_err`]). + /// - The attribute can be parsed in multiple ways and it does not make sense to emit an error. pub(crate) fn expect_no_args<'arg>(&mut self, arg: &'arg ArgParser) -> Option<()> { if let Err(span) = arg.as_no_args() { self.adcx().expected_no_args(span); @@ -570,6 +577,26 @@ impl<'f, 'sess: 'f> AcceptContext<'f, 'sess> { Some(()) } + + /// Asserts that a node is a string literal, or emits an error and return `None` + /// + /// `arg` must be a reference to any node that may contain a name-value pair, that is: + /// + /// - [`NameValueParser`], + /// - [`MetaItemOrLitParser`], + /// + /// This is a higher-level (and harder to misuse) wrapper over multiple `as_` methods in the + /// [`parser`][crate::parser] module. You may still want to use the lower-level methods for the + /// following reasons: + /// + /// - You want to emit your own diagnostics (for instance, with [`SharedContext::emit_err`]). + /// - The attribute can be parsed in multiple ways and it does not make sense to emit an error. + pub(crate) fn expect_string_literal<'arg, Arg>(&mut self, arg: &'arg Arg) -> Option + where + Arg: ExpectStringLiteral, + { + arg.expect_string_literal(self) + } } pub(crate) trait ExpectNameValue { @@ -649,6 +676,44 @@ impl ExpectNameValue for ArgParser { } } +pub(crate) trait ExpectStringLiteral { + fn expect_string_literal<'f, 'sess>(&self, cx: &mut AcceptContext<'f, 'sess>) + -> Option; +} + +impl ExpectStringLiteral for NameValueParser { + fn expect_string_literal<'f, 'sess>( + &self, + cx: &mut AcceptContext<'f, 'sess>, + ) -> Option { + let value = self.value_as_str(); + if value.is_none() { + cx.adcx().expected_string_literal(self.value_span, Some(self.value_as_lit())); + } + value + } +} + +impl ExpectStringLiteral for MetaItemOrLitParser { + fn expect_string_literal<'f, 'sess>( + &self, + cx: &mut AcceptContext<'f, 'sess>, + ) -> Option { + let Some(lit) = self.as_lit() else { + cx.adcx().expected_string_literal(self.span(), None); + return None; + }; + + let str = lit.value_as_str(); + + if str.is_none() { + cx.adcx().expected_string_literal(self.span(), Some(lit)); + } + + str + } +} + impl<'f, 'sess> Deref for AcceptContext<'f, 'sess> { type Target = SharedContext<'f, 'sess>; diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 845f1394cc3de..48790f273adfc 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -240,7 +240,7 @@ impl MetaItemOrLitParser { } } - pub fn lit(&self) -> Option<&MetaItemLit> { + pub fn as_lit(&self) -> Option<&MetaItemLit> { match self { MetaItemOrLitParser::Lit(meta_item_lit) => Some(meta_item_lit), MetaItemOrLitParser::MetaItemParser(_) => None, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 7747d9920a321..34785b0401189 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1340,7 +1340,7 @@ impl AttributeExt for Attribute { #[inline] fn value_str(&self) -> Option { - self.value_lit().and_then(|x| x.value_str()) + self.value_lit().and_then(|x| x.value_as_str()) } #[inline] From 40da877450ba2f559d1b0db67f6e1714a66653f3 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 9 May 2026 17:42:42 +0000 Subject: [PATCH 12/13] Add `str::word_to_titlecase()` to `alloc` * Add `str::word_to_titlecase()` to `alloc` * Address review comment --- library/alloc/src/str.rs | 168 +++++++++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 16 deletions(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index 2966f3ccc1791..ab04e84c0da73 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -185,6 +185,23 @@ where result } +/// Helper for final sigma lowercase +#[cfg(not(no_global_oom_handling))] +fn map_uppercase_sigma(from: &str, i: usize) -> char { + fn case_ignorable_then_cased>(iter: I) -> bool { + match iter.skip_while(|&c| c.is_case_ignorable()).next() { + Some(c) => c.is_cased(), + None => false, + } + } + + // See https://www.unicode.org/versions/latest/core-spec/chapter-3/#G54277 + // for the definition of `Final_Sigma`. + let is_word_final = case_ignorable_then_cased(from[..i].chars().rev()) + && !case_ignorable_then_cased(from[i + const { 'Σ'.len_utf8() }..].chars()); + if is_word_final { 'ς' } else { 'σ' } +} + #[stable(feature = "rust1", since = "1.0.0")] impl Borrow for String { #[inline] @@ -345,7 +362,7 @@ impl str { /// /// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent /// casing of Greek sigma. However, like that method, it does not handle locale-specific - /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation + /// casing, like Turkish and Azeri I/ı/İ/i. See its documentation /// for more information. /// /// # Examples @@ -353,12 +370,12 @@ impl str { /// Basic usage: /// /// ``` - /// let s = "HELLO"; + /// let s = "HELLO WORLD"; /// - /// assert_eq!("hello", s.to_lowercase()); + /// assert_eq!("hello world", s.to_lowercase()); /// ``` /// - /// A tricky example, with sigma: + /// Tricky examples, with sigma: /// /// ``` /// let sigma = "Σ"; @@ -369,6 +386,10 @@ impl str { /// let odysseus = "ὈΔΥΣΣΕΎΣ"; /// /// assert_eq!("ὀδυσσεύς", odysseus.to_lowercase()); + /// + /// let odysseus_king_of_ithaca = "Ο ΟΔΥΣΣΈΑΣ ΒΑΣΙΛΙΆΣ ΤΗΣ ΙΘΆΚΗΣ"; + /// + /// assert_eq!("ο οδυσσέας βασιλιάς της ιθάκης", odysseus_king_of_ithaca.to_lowercase()); /// ``` /// /// Languages without case are not changed: @@ -415,21 +436,136 @@ impl str { } } return s; + } - fn map_uppercase_sigma(from: &str, i: usize) -> char { - // See https://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G33992 - // for the definition of `Final_Sigma`. - let is_word_final = case_ignorable_then_cased(from[..i].chars().rev()) - && !case_ignorable_then_cased(from[i + const { 'Σ'.len_utf8() }..].chars()); - if is_word_final { 'ς' } else { 'σ' } + /// Returns the titlecase equivalent of this string slice, + /// which is assumed to represent a single word, + /// as a new [`String`]. + /// + /// Essentially, this consists of uppercasing the first cased letter + /// (with [`char::to_titlecase()`]), and lowercasing everything that follows. + /// + /// 'Titlecase' is defined according to the terms of + /// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G34082) + /// of the Unicode standard. + /// + /// Since some characters can expand into multiple characters when changing + /// the case, this function returns a [`String`] instead of modifying the + /// parameter in-place. + /// + /// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent + /// casing of Greek sigma. However, like that method, it does not handle locale-specific + /// casing, like Turkish and Azeri I/ı/İ/i. See its documentation + /// for more information. + /// + /// This method does not perform any kind of word segmentation. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(titlecase)] + /// let s = "HELLO WORLD"; + /// + /// assert_eq!("Hello world", s.word_to_titlecase()); + /// ``` + /// + /// The first *cased* letter is uppercased: + /// + /// ``` + /// #![feature(titlecase)] + /// let the_night_before_christmas = "'twas"; + /// + /// assert_eq!("'Twas", the_night_before_christmas.word_to_titlecase()); + /// ``` + /// + /// Languages without case are not changed: + /// + /// ``` + /// #![feature(titlecase)] + /// let new_year = "农历新年"; + /// + /// assert_eq!(new_year, new_year.word_to_titlecase()); + /// ``` + /// + /// Georgian uppercase ("Mtavruli") letters are not used in titlecase: + /// + /// ``` + /// #![feature(titlecase)] + /// let georgian = "ერთობაშია"; + /// + /// assert_eq!(georgian, georgian.word_to_titlecase()); + /// ``` + /// + /// No word segmentation is performed, + /// so only the first cased letter in the whole string gets uppercased: + /// + /// ``` + /// #![feature(titlecase)] + /// let blazingly_fast = "ferris and I"; + /// + /// assert_eq!("Ferris and i", blazingly_fast.word_to_titlecase()); + /// ``` + /// + /// Tricky examples, with sigma: + /// + /// ``` + /// #![feature(titlecase)] + /// let odysseus = "ὈΔΥΣΣΕΎΣ"; + /// + /// assert_eq!("Ὀδυσσεύς", odysseus.word_to_titlecase()); + /// + /// let odysseus_king_of_ithaca = "Ο ΟΔΥΣΣΈΑΣ ΒΑΣΙΛΙΆΣ ΤΗΣ ΙΘΆΚΗΣ"; + /// + /// assert_eq!("Ο οδυσσέας βασιλιάς της ιθάκης", odysseus_king_of_ithaca.word_to_titlecase()); + /// ``` + #[cfg(not(no_global_oom_handling))] + #[rustc_allow_incoherent_impl] + #[must_use = "this returns the titlecase word as a new String, \ + without modifying the original"] + #[unstable(feature = "titlecase", issue = "153892")] + pub fn word_to_titlecase(&self) -> String { + // FIXME: add ASCII fast path + + let mut s = String::with_capacity(self.len()); + let mut chars = self.char_indices(); + + 'until_first_cased_char: for (_, c) in chars.by_ref() { + if c.is_cased() { + s.extend(c.to_titlecase()); + break 'until_first_cased_char; + } else { + s.push(c); + } } - fn case_ignorable_then_cased>(iter: I) -> bool { - match iter.skip_while(|&c| c.is_case_ignorable()).next() { - Some(c) => c.is_cased(), - None => false, + for (i, c) in chars { + if c == 'Σ' { + // Σ maps to σ, except at the end of a word where it maps to ς. + // This is the only conditional (contextual) but language-independent mapping + // in `SpecialCasing.txt`, + // so hard-code it rather than have a generic "condition" mechanism. + // See https://github.com/rust-lang/rust/issues/26035 + let sigma_lowercase = map_uppercase_sigma(self, i); + s.push(sigma_lowercase); + } else { + match conversions::to_lower(c) { + [a, '\0', _] => s.push(a), + [a, b, '\0'] => { + s.push(a); + s.push(b); + } + [a, b, c] => { + s.push(a); + s.push(b); + s.push(c); + } + } } } + + s } /// Returns the uppercase equivalent of this string slice, as a new [`String`]. @@ -451,9 +587,9 @@ impl str { /// Basic usage: /// /// ``` - /// let s = "hello"; + /// let s = "hello world"; /// - /// assert_eq!("HELLO", s.to_uppercase()); + /// assert_eq!("HELLO WORLD", s.to_uppercase()); /// ``` /// /// Scripts without case are not changed: From f4adc40bbe87fdfe2645373c4a2dbc15dd06f4d2 Mon Sep 17 00:00:00 2001 From: Tomi Leppikangas Date: Sat, 9 May 2026 17:43:33 +0000 Subject: [PATCH 13/13] Add mention of sendfile(2) and splice(2) to fs::copy() documentation. * Add mention of sendfile(2) and splice(2) to fs::copy() documentation. * Oxford comma fix --- library/std/src/fs.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index c2ffd0793a816..3702850a2b136 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2790,8 +2790,9 @@ pub fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> /// with `O_RDONLY` for `from` and `O_WRONLY`, `O_CREAT`, and `O_TRUNC` for `to`. /// `O_CLOEXEC` is set for returned file descriptors. /// -/// On Linux (including Android), this function attempts to use `copy_file_range(2)`, -/// and falls back to reading and writing if that is not possible. +/// On Linux (including Android), this function uses copy_file_range(2), +/// sendfile(2), or splice(2) syscalls to move data directly between files +/// if possible. /// /// On Windows, this function currently corresponds to `CopyFileEx`. Alternate /// NTFS streams are copied but only the size of the main stream is returned by