From 5ed5b2614f6324ede3111c3a438434d18349f88a Mon Sep 17 00:00:00 2001 From: Schneems Date: Wed, 26 Nov 2025 16:28:41 -0600 Subject: [PATCH 01/14] 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 25a1eb243a4c01b5539097d24ad554312c2d2c73 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Tue, 28 Oct 2025 15:48:49 +0100 Subject: [PATCH 02/14] add `must_use` tests for `Result` --- .../must_use-result-unit-uninhabited.rs | 15 ++++++++ .../must_use-result-unit-uninhabited.stderr | 38 +++++++++++++++---- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs b/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs index 8f63e4a7f8323..0bddc9a7aa9b4 100644 --- a/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs +++ b/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs @@ -7,6 +7,11 @@ use core::ops::{ControlFlow, ControlFlow::Continue}; use dep::{MyUninhabited, MyUninhabitedNonexhaustive}; +#[must_use] +struct MustUse; + +struct Struct; + fn result_unit_unit() -> Result<(), ()> { Ok(()) } @@ -19,6 +24,14 @@ fn result_unit_never() -> Result<(), !> { Ok(()) } +fn result_struct_never() -> Result { + Ok(Struct) +} + +fn result_must_use_never() -> Result { + Ok(MustUse) +} + fn result_unit_myuninhabited() -> Result<(), MyUninhabited> { Ok(()) } @@ -80,6 +93,8 @@ fn main() { result_unit_unit(); //~ ERROR: unused `Result` that must be used result_unit_infallible(); result_unit_never(); + result_must_use_never(); //~ ERROR: unused `Result` that must be used + result_struct_never(); //~ ERROR: unused `Result` that must be used result_unit_myuninhabited(); result_unit_myuninhabited_nonexhaustive(); //~ ERROR: unused `Result` that must be used result_unit_assoctype(S1); diff --git a/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr b/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr index 31d6f6bcf2bc7..b8448465f924a 100644 --- a/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr +++ b/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr @@ -1,5 +1,5 @@ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:80:5 + --> $DIR/must_use-result-unit-uninhabited.rs:93:5 | LL | result_unit_unit(); | ^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,31 @@ LL | let _ = result_unit_unit(); | +++++++ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:84:5 + --> $DIR/must_use-result-unit-uninhabited.rs:96:5 + | +LL | result_must_use_never(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this `Result` may be an `Err` variant, which should be handled +help: use `let _ = ...` to ignore the resulting value + | +LL | let _ = result_must_use_never(); + | +++++++ + +error: unused `Result` that must be used + --> $DIR/must_use-result-unit-uninhabited.rs:97:5 + | +LL | result_struct_never(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: this `Result` may be an `Err` variant, which should be handled +help: use `let _ = ...` to ignore the resulting value + | +LL | let _ = result_struct_never(); + | +++++++ + +error: unused `Result` that must be used + --> $DIR/must_use-result-unit-uninhabited.rs:99:5 | LL | result_unit_myuninhabited_nonexhaustive(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -28,7 +52,7 @@ LL | let _ = result_unit_myuninhabited_nonexhaustive(); | +++++++ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:86:5 + --> $DIR/must_use-result-unit-uninhabited.rs:101:5 | LL | result_unit_assoctype(S2); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +64,7 @@ LL | let _ = result_unit_assoctype(S2); | +++++++ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:88:5 + --> $DIR/must_use-result-unit-uninhabited.rs:103:5 | LL | S2.method_use_assoc_type(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -52,7 +76,7 @@ LL | let _ = S2.method_use_assoc_type(); | +++++++ error: unused `ControlFlow` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:90:5 + --> $DIR/must_use-result-unit-uninhabited.rs:105:5 | LL | controlflow_unit(); | ^^^^^^^^^^^^^^^^^^ @@ -63,7 +87,7 @@ LL | let _ = controlflow_unit(); | +++++++ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:99:9 + --> $DIR/must_use-result-unit-uninhabited.rs:114:9 | LL | self.generate(); | ^^^^^^^^^^^^^^^ @@ -74,5 +98,5 @@ help: use `let _ = ...` to ignore the resulting value LL | let _ = self.generate(); | +++++++ -error: aborting due to 6 previous errors +error: aborting due to 8 previous errors From eb0d2a89c305842346b862c6ff6114bb0108081d Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Tue, 28 Oct 2025 15:24:45 +0100 Subject: [PATCH 03/14] consider `Result`/`ControlFlow` the same as `T` for must_use lint (or more accurately `Result`/`ControlFlow`). This generalizes a previous change where we only did this for `T = ()`. --- compiler/rustc_lint/src/unused/must_use.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs index 5bbe84a51fa2f..f5f22949701fa 100644 --- a/compiler/rustc_lint/src/unused/must_use.rs +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -341,7 +341,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { let ty = cx.typeck_results().expr_ty(expr); - let must_use_result = is_ty_must_use(cx, ty, expr, false); + let must_use_result = is_ty_must_use(cx, ty, expr, true); let type_lint_emitted_or_trivial = match must_use_result { IsTyMustUse::Yes(path) => { emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); From 4c4416b813f069e4573b73b2815946682a823c93 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Tue, 28 Oct 2025 15:24:45 +0100 Subject: [PATCH 04/14] remove dead code --- compiler/rustc_lint/src/unused/must_use.rs | 49 +++++-------------- .../must_use-result-unit-uninhabited.rs | 4 +- .../must_use-result-unit-uninhabited.stderr | 22 +-------- 3 files changed, 15 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs index f5f22949701fa..62da7ed6a1226 100644 --- a/compiler/rustc_lint/src/unused/must_use.rs +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -133,18 +133,11 @@ pub enum MustUsePath { /// Returns `Some(path)` if `ty` should be considered as "`must_use`" in the context of `expr` /// (`expr` is used to get the parent module, which can affect which types are considered uninhabited). -/// -/// If `simplify_uninhabited` is true, this function considers `Result` and -/// `ControlFlow` the same as `T` (we don't set this *yet* in rustc, but expose this -/// so clippy can use this). -// -// FIXME: remove `simplify_uninhabited` once clippy had a release with the new semantics. #[instrument(skip(cx, expr), level = "debug", ret)] pub fn is_ty_must_use<'tcx>( cx: &LateContext<'tcx>, ty: Ty<'tcx>, expr: &hir::Expr<'_>, - simplify_uninhabited: bool, ) -> IsTyMustUse { if ty.is_unit() { return IsTyMustUse::Trivial; @@ -157,50 +150,29 @@ pub fn is_ty_must_use<'tcx>( match *ty.kind() { _ if is_uninhabited(ty) => IsTyMustUse::Trivial, ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { - is_ty_must_use(cx, boxed, expr, simplify_uninhabited) - .map(|inner| MustUsePath::Boxed(Box::new(inner))) + is_ty_must_use(cx, boxed, expr).map(|inner| MustUsePath::Boxed(Box::new(inner))) } ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { let pinned_ty = args.type_at(0); - is_ty_must_use(cx, pinned_ty, expr, simplify_uninhabited) - .map(|inner| MustUsePath::Pinned(Box::new(inner))) + is_ty_must_use(cx, pinned_ty, expr).map(|inner| MustUsePath::Pinned(Box::new(inner))) } // Consider `Result` (e.g. `Result<(), !>`) equivalent to `T`. ty::Adt(def, args) - if simplify_uninhabited - && cx.tcx.is_diagnostic_item(sym::Result, def.did()) + if cx.tcx.is_diagnostic_item(sym::Result, def.did()) && is_uninhabited(args.type_at(1)) => { let ok_ty = args.type_at(0); - is_ty_must_use(cx, ok_ty, expr, simplify_uninhabited) - .map(|path| MustUsePath::Result(Box::new(path))) + is_ty_must_use(cx, ok_ty, expr).map(|path| MustUsePath::Result(Box::new(path))) } // Consider `ControlFlow` (e.g. `ControlFlow`) equivalent to `T`. ty::Adt(def, args) - if simplify_uninhabited - && cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) + if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) && is_uninhabited(args.type_at(0)) => { let continue_ty = args.type_at(1); - is_ty_must_use(cx, continue_ty, expr, simplify_uninhabited) + is_ty_must_use(cx, continue_ty, expr) .map(|path| MustUsePath::ControlFlow(Box::new(path))) } - // Suppress warnings on `Result<(), Uninhabited>` (e.g. `Result<(), !>`). - ty::Adt(def, args) - if cx.tcx.is_diagnostic_item(sym::Result, def.did()) - && args.type_at(0).is_unit() - && is_uninhabited(args.type_at(1)) => - { - IsTyMustUse::Trivial - } - // Suppress warnings on `ControlFlow` (e.g. `ControlFlow`). - ty::Adt(def, args) - if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) - && args.type_at(1).is_unit() - && is_uninhabited(args.type_at(0)) => - { - IsTyMustUse::Trivial - } ty::Adt(def, _) => { is_def_must_use(cx, def.did(), expr.span).map_or(IsTyMustUse::No, IsTyMustUse::Yes) } @@ -254,7 +226,7 @@ pub fn is_ty_must_use<'tcx>( let mut nested_must_use = Vec::new(); tys.iter().zip(elem_exprs).enumerate().for_each(|(i, (ty, expr))| { - let must_use = is_ty_must_use(cx, ty, expr, simplify_uninhabited); + let must_use = is_ty_must_use(cx, ty, expr); all_trivial &= matches!(must_use, IsTyMustUse::Trivial); if let IsTyMustUse::Yes(path) = must_use { @@ -276,8 +248,9 @@ pub fn is_ty_must_use<'tcx>( // If the array is empty we don't lint, to avoid false positives Some(0) | None => IsTyMustUse::No, // If the array is definitely non-empty, we can do `#[must_use]` checking. - Some(len) => is_ty_must_use(cx, ty, expr, simplify_uninhabited) - .map(|inner| MustUsePath::Array(Box::new(inner), len)), + Some(len) => { + is_ty_must_use(cx, ty, expr).map(|inner| MustUsePath::Array(Box::new(inner), len)) + } }, ty::Closure(..) | ty::CoroutineClosure(..) => { IsTyMustUse::Yes(MustUsePath::Closure(expr.span)) @@ -341,7 +314,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { let ty = cx.typeck_results().expr_ty(expr); - let must_use_result = is_ty_must_use(cx, ty, expr, true); + let must_use_result = is_ty_must_use(cx, ty, expr); let type_lint_emitted_or_trivial = match must_use_result { IsTyMustUse::Yes(path) => { emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); diff --git a/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs b/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs index 0bddc9a7aa9b4..d1b47374a11b8 100644 --- a/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs +++ b/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs @@ -93,8 +93,8 @@ fn main() { result_unit_unit(); //~ ERROR: unused `Result` that must be used result_unit_infallible(); result_unit_never(); - result_must_use_never(); //~ ERROR: unused `Result` that must be used - result_struct_never(); //~ ERROR: unused `Result` that must be used + result_must_use_never(); //~ ERROR: unused `MustUse` in a `Result` with an uninhabited error that must be used + result_struct_never(); result_unit_myuninhabited(); result_unit_myuninhabited_nonexhaustive(); //~ ERROR: unused `Result` that must be used result_unit_assoctype(S1); diff --git a/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr b/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr index b8448465f924a..b4c62c7690b49 100644 --- a/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr +++ b/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr @@ -15,29 +15,11 @@ help: use `let _ = ...` to ignore the resulting value LL | let _ = result_unit_unit(); | +++++++ -error: unused `Result` that must be used +error: unused `MustUse` in a `Result` with an uninhabited error that must be used --> $DIR/must_use-result-unit-uninhabited.rs:96:5 | LL | result_must_use_never(); | ^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this `Result` may be an `Err` variant, which should be handled -help: use `let _ = ...` to ignore the resulting value - | -LL | let _ = result_must_use_never(); - | +++++++ - -error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:97:5 - | -LL | result_struct_never(); - | ^^^^^^^^^^^^^^^^^^^^^ - | - = note: this `Result` may be an `Err` variant, which should be handled -help: use `let _ = ...` to ignore the resulting value - | -LL | let _ = result_struct_never(); - | +++++++ error: unused `Result` that must be used --> $DIR/must_use-result-unit-uninhabited.rs:99:5 @@ -98,5 +80,5 @@ help: use `let _ = ...` to ignore the resulting value LL | let _ = self.generate(); | +++++++ -error: aborting due to 8 previous errors +error: aborting due to 7 previous errors From d0ea88eeb72b652aa280a5163634ce9bae8ebf5b Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 19 Apr 2026 14:27:10 +0200 Subject: [PATCH 05/14] 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 06/14] 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 07/14] 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 e264a92936000d2a3ed7b4a472a028f483d4bae6 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Sun, 26 Apr 2026 23:53:22 -0500 Subject: [PATCH 08/14] gracefully handle invalid string/vec --- src/etc/lldb_providers.py | 68 ++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index 65f20210323eb..e2d4ac5286ccd 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -305,16 +305,33 @@ def StdStringSummaryProvider(valobj: SBValue, dict: LLDBOpaque): ) length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned() + capacity = ( + inner_vec.GetChildMemberWithName("buf") + .GetChildMemberWithName("cap") + .GetValueAsUnsigned() + ) if length <= 0: return '""' + + no_hi_bit_max: int = 1 << ((pointer.GetByteSize() * 8) - 1) + # technically length isn't a NoHighBit, but length should always be <= capacity + if length >= no_hi_bit_max or capacity >= no_hi_bit_max: + return "" + if pointer.GetValueAsUnsigned() == 0: + return "" + error = SBError() process = pointer.GetProcess() - data = process.ReadMemory(pointer.GetValueAsUnsigned(), length, error) - if error.Success(): - return '"' + data.decode("utf8", "replace") + '"' - else: - raise Exception("ReadMemory error: %s", error.GetCString()) + try: + data = process.ReadMemory(pointer.GetValueAsUnsigned(), length, error) + if error.Success(): + return '"' + data.decode("utf8", "replace") + '"' + else: + return f"" + except Exception as e: + print(f"Unable to generate String summary: {e.__cause__}") + return "" def StdOsStringSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: @@ -443,6 +460,9 @@ def has_children(self) -> bool: class StdStringSyntheticProvider: def __init__(self, valobj: SBValue, _dict: LLDBOpaque): self.valobj = valobj + ptr_size = valobj.GetTarget().GetAddressByteSize() * 8 + self.no_hi_bit_max = 1 << (ptr_size - 1) + self.update() def update(self): @@ -454,7 +474,25 @@ def update(self): .GetChildMemberWithName("pointer") .GetChildMemberWithName("pointer") ) - self.length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned() + + self.capacity = ( + inner_vec.GetChildMemberWithName("buf") + .GetChildMemberWithName("cap") + .GetValueAsUnsigned() + ) + + # As of 4/18/2026, LLDB cannot accurately determine the difference between Some("") and None + # this just makes sure we're not trying to access data when the string is clearly in an + # invalid state. + if ( + self.capacity >= self.no_hi_bit_max + or self.data_ptr.GetValueAsUnsigned() == 0 + ): + self.capacity = 0 + self.length = 0 + else: + self.length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned() + self.element_type = self.data_ptr.GetType().GetPointeeType() def has_children(self) -> bool: @@ -928,6 +966,8 @@ def __init__(self, valobj: SBValue, _dict: LLDBOpaque): # logger >> "[StdVecSyntheticProvider] for " + str(valobj.GetName()) self.valobj = valobj self.element_type = None + ptr_size = valobj.GetTarget().GetAddressByteSize() * 8 + self.no_hi_bit_max = 1 << (ptr_size - 1) self.update() def num_children(self) -> int: @@ -949,15 +989,19 @@ def get_child_at_index(self, index: int) -> Optional[SBValue]: return element def update(self): - self.length = self.valobj.GetChildMemberWithName("len").GetValueAsUnsigned() - self.buf = self.valobj.GetChildMemberWithName("buf").GetChildMemberWithName( - "inner" - ) - + buf: SBValue = self.valobj.GetChildMemberWithName("buf") self.data_ptr = unwrap_unique_or_non_null( - self.buf.GetChildMemberWithName("ptr") + buf.GetChildMemberWithName("inner").GetChildMemberWithName("ptr") ) + capacity: int = buf.GetChildMemberWithName("cap").GetValueAsUnsigned() + + if capacity >= self.no_hi_bit_max or self.data_ptr.GetValueAsUnsigned() == 0: + self.capacity = 0 + self.length = 0 + else: + self.length = self.valobj.GetChildMemberWithName("len").GetValueAsUnsigned() + self.element_type = self.valobj.GetType().GetTemplateArgumentType(0) if not self.element_type.IsValid(): From 0a4f89eac82b1bae49650dc7890c6b5c36772779 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:12:47 -0500 Subject: [PATCH 09/14] move string read logic to standalone function --- src/etc/lldb_providers.py | 47 +++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index e2d4ac5286ccd..735f333cb629a 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -15,7 +15,7 @@ from rust_types import is_tuple_fields if TYPE_CHECKING: - from lldb import SBValue, SBType, SBTypeStaticField, SBTarget + from lldb import SBValue, SBType, SBTypeStaticField, SBTarget, SBProcess # from lldb.formatters import Logger @@ -289,6 +289,29 @@ def vec_to_string(vec: SBValue) -> str: ) +def read_string( + process: SBProcess, address: int, length: int, error: Optional[SBError] = None +) -> str: + """Reads a string from running process's memory. If `error` is passed in, it will be passed + to the `SBProcess.ReadMemory` call, and will reflect any errors after the function is called. + + If any error or exception occurs, a placeholder byte array of the form "" will + be returned instead.""" + + if error is None: + error = SBError() + try: + data = process.ReadMemory(address, length, error) + if error.Success(): + # replace single quotes with double quotes + return '"' + data.decode("utf8", "replace")[1:-1] + '"' + else: + return f"" + except Exception as e: + print(f"Unable to generate String summary: {e.__cause__}") + return "" + + def StdStringSummaryProvider(valobj: SBValue, dict: LLDBOpaque): inner_vec = ( valobj.GetNonSyntheticValue() @@ -321,17 +344,9 @@ def StdStringSummaryProvider(valobj: SBValue, dict: LLDBOpaque): if pointer.GetValueAsUnsigned() == 0: return "" - error = SBError() process = pointer.GetProcess() - try: - data = process.ReadMemory(pointer.GetValueAsUnsigned(), length, error) - if error.Success(): - return '"' + data.decode("utf8", "replace") + '"' - else: - return f"" - except Exception as e: - print(f"Unable to generate String summary: {e.__cause__}") - return "" + + return read_string(process, pointer.GetValueAsAddress(), length) def StdOsStringSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: @@ -380,15 +395,9 @@ def StdPathSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: data_ptr = valobj.GetChildMemberWithName("data_ptr") start = data_ptr.GetValueAsUnsigned() - error = SBError() process = data_ptr.GetProcess() - data = process.ReadMemory(start, length, error) - if PY3: - try: - data = data.decode(encoding="UTF-8") - except UnicodeDecodeError: - return "%r" % data - return '"%s"' % data + + return read_string(process, start, length) def sequence_formatter(output: str, valobj: SBValue, _dict: LLDBOpaque): 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 10/14] 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 11/14] 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 12/14] 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 40da877450ba2f559d1b0db67f6e1714a66653f3 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 9 May 2026 17:42:42 +0000 Subject: [PATCH 13/14] 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 14/14] 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