From d48bdef5523dd5c077a4c619f4426991164cd416 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 17 Aug 2026 12:40:17 +0200 Subject: [PATCH 01/23] implement `Add` and `Sub` for `Complex` --- library/core/src/num/complex.rs | 40 ++++++++++++++++++++++- library/coretests/tests/lib.rs | 1 + library/coretests/tests/num/complex.rs | 44 ++++++++++++++++++++++++++ library/coretests/tests/num/mod.rs | 1 + 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 library/coretests/tests/num/complex.rs diff --git a/library/core/src/num/complex.rs b/library/core/src/num/complex.rs index 9321718899cdd..73b904d81fc53 100644 --- a/library/core/src/num/complex.rs +++ b/library/core/src/num/complex.rs @@ -1,5 +1,7 @@ +use crate::ops::{Add, Sub}; + /// A complex number. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[unstable(feature = "complex_numbers", issue = "154023")] #[repr(C)] #[lang = "complex"] @@ -18,3 +20,39 @@ impl Complex { Complex { re, im } } } + +#[unstable(feature = "complex_numbers", issue = "154023")] +impl Add for Complex { + type Output = Complex; + + fn add(self, rhs: Self) -> Self::Output { + Complex::new(self.re + rhs.re, self.im + rhs.im) + } +} + +#[unstable(feature = "complex_numbers", issue = "154023")] +impl> Add for Complex { + type Output = Complex; + + fn add(self, rhs: T) -> Self::Output { + Complex::new(self.re + rhs, self.im) + } +} + +#[unstable(feature = "complex_numbers", issue = "154023")] +impl Sub for Complex { + type Output = Complex; + + fn sub(self, rhs: Self) -> Self::Output { + Complex::new(self.re - rhs.re, self.im - rhs.im) + } +} + +#[unstable(feature = "complex_numbers", issue = "154023")] +impl> Sub for Complex { + type Output = Complex; + + fn sub(self, rhs: T) -> Self::Output { + Complex::new(self.re - rhs, self.im) + } +} diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 1f629f01f38dd..3d471a9af1691 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -16,6 +16,7 @@ #![feature(clone_to_uninit)] #![feature(cmp_minmax)] #![feature(cmp_splat)] +#![feature(complex_numbers)] #![feature(const_array)] #![feature(const_bool)] #![feature(const_cell_traits)] diff --git a/library/coretests/tests/num/complex.rs b/library/coretests/tests/num/complex.rs new file mode 100644 index 0000000000000..4260c0a9a27a9 --- /dev/null +++ b/library/coretests/tests/num/complex.rs @@ -0,0 +1,44 @@ +use core::num::{Complex, Wrapping}; + +#[test] +fn complex_addition() { + let a = Complex::new(1, 2); + let b = Complex::new(3, 4); + assert_eq!(a + b, Complex::new(a.re + b.re, a.im + b.im)); + assert_eq!(a + b, b + a); + assert_eq!(a + 8, Complex::new(a.re + 8, a.im)); + + let a = Complex::new(Wrapping(1u8), Wrapping(2)); + let b = Complex::new(Wrapping(3u8), Wrapping(4)); + assert_eq!(a + b, Complex::new(a.re + b.re, a.im + b.im)); + assert_eq!(a + b, b + a); + let c = a + Wrapping(u8::MAX); + assert_eq!(c, Complex::new(a.re + Wrapping(u8::MAX), a.im)); + assert_eq!(c.re.0, 1u8.wrapping_add(u8::MAX)); + + let a = Complex::new(1.0, 2.0); + let b = Complex::new(3.0, 4.0); + assert_eq!(a + b, Complex::new(a.re + b.re, a.im + b.im)); + assert_eq!(a + b, b + a); + assert_eq!(a + 8.0, Complex::new(a.re + 8.0, a.im)); +} + +#[test] +fn complex_subtraction() { + let a = Complex::new(1, 2); + let b = Complex::new(3, 4); + assert_eq!(a - b, Complex::new(a.re - b.re, a.im - b.im)); + assert_eq!(a - 8, Complex::new(a.re - 8, a.im)); + + let a = Complex::new(Wrapping(1u8), Wrapping(2)); + let b = Complex::new(Wrapping(3u8), Wrapping(4)); + assert_eq!(a - b, Complex::new(a.re - b.re, a.im - b.im)); + let c = a - Wrapping(u8::MAX); + assert_eq!(c, Complex::new(a.re - Wrapping(u8::MAX), a.im)); + assert_eq!(c.re.0, 1u8.wrapping_sub(u8::MAX)); + + let a = Complex::new(1.0, 2.0); + let b = Complex::new(3.0, 4.0); + assert_eq!(a - b, Complex::new(a.re - b.re, a.im - b.im)); + assert_eq!(a - 8.0, Complex::new(a.re - 8.0, a.im)); +} diff --git a/library/coretests/tests/num/mod.rs b/library/coretests/tests/num/mod.rs index ac65a3b59c66a..19fa431c5a990 100644 --- a/library/coretests/tests/num/mod.rs +++ b/library/coretests/tests/num/mod.rs @@ -24,6 +24,7 @@ mod u8; mod bignum; mod carryless_mul; mod cast; +mod complex; mod const_from; mod dec2flt; mod float_ieee754_flt2dec_dec2flt; From dd4b00dc592404bbbe5862e76d6e7a8966e8a2cc Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 18 Aug 2026 11:17:10 +0200 Subject: [PATCH 02/23] make target feature ABI check a hard error on ARM --- compiler/rustc_interface/src/diagnostics.rs | 11 ++++--- compiler/rustc_interface/src/util.rs | 30 +++++++++++++++---- ...t-feature-missing-in-target-cpu.arm.stderr | 7 ++--- ...ed-target-feature-missing-in-target-cpu.rs | 13 ++++---- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_interface/src/diagnostics.rs b/compiler/rustc_interface/src/diagnostics.rs index 2a2757f814715..191f36ee2f9f1 100644 --- a/compiler/rustc_interface/src/diagnostics.rs +++ b/compiler/rustc_interface/src/diagnostics.rs @@ -122,13 +122,16 @@ pub(crate) struct MultipleOutputTypesToStdout; #[diag( "target feature `{$feature}` must be {$enabled} to ensure that the ABI of the current target can be implemented correctly" )] -#[note( - "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" -)] -#[note("for more information, see issue #116344 ")] pub(crate) struct AbiRequiredTargetFeature<'a> { pub feature: &'a str, pub enabled: &'a str, + #[note( + "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" + )] + #[note( + "for more information, see issue #116344 " + )] + pub fcw: bool, } #[derive(Diagnostic)] diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 1af2094c93d0d..33389b1ff0be5 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -29,7 +29,7 @@ use rustc_span::edition::Edition; use rustc_span::source_map::SourceMapInputs; use rustc_span::{SessionGlobals, Symbol, sym}; use rustc_structures::CrateType; -use rustc_target::spec::Target; +use rustc_target::spec::{Arch, Target}; use tracing::info; use crate::diagnostics; @@ -102,16 +102,36 @@ pub(crate) fn check_abi_required_features(sess: &Session) { ); } + // Make this a hard error on ARM since starting with LLVM24, the backend will otherwise + // emit a (less friendly) hard error. + let hard_error = matches!(sess.target.arch, Arch::Arm); + for feature in abi_feature_constraints.required { if !sess.internal_target_features.contains(&Symbol::intern(feature)) { - sess.dcx() - .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "enabled" }); + let diag = diagnostics::AbiRequiredTargetFeature { + feature, + enabled: "enabled", + fcw: !hard_error, + }; + if hard_error { + sess.dcx().emit_err(diag); + } else { + sess.dcx().emit_warn(diag); + } } } for feature in abi_feature_constraints.incompatible { if sess.internal_target_features.contains(&Symbol::intern(feature)) { - sess.dcx() - .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "disabled" }); + let diag = diagnostics::AbiRequiredTargetFeature { + feature, + enabled: "disabled", + fcw: !hard_error, + }; + if hard_error { + sess.dcx().emit_err(diag); + } else { + sess.dcx().emit_warn(diag); + } } } } diff --git a/tests/ui/target-feature/abi-required-target-feature-missing-in-target-cpu.arm.stderr b/tests/ui/target-feature/abi-required-target-feature-missing-in-target-cpu.arm.stderr index 52862ec5151b3..73b3e5bacd906 100644 --- a/tests/ui/target-feature/abi-required-target-feature-missing-in-target-cpu.arm.stderr +++ b/tests/ui/target-feature/abi-required-target-feature-missing-in-target-cpu.arm.stderr @@ -1,7 +1,4 @@ -warning: target feature `fpregs` must be enabled to ensure that the ABI of the current target can be implemented correctly - | - = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #116344 +error: target feature `fpregs` must be enabled to ensure that the ABI of the current target can be implemented correctly -warning: 1 warning emitted +error: aborting due to 1 previous error diff --git a/tests/ui/target-feature/abi-required-target-feature-missing-in-target-cpu.rs b/tests/ui/target-feature/abi-required-target-feature-missing-in-target-cpu.rs index e652f321ad33b..59d0ba1a0ba4f 100644 --- a/tests/ui/target-feature/abi-required-target-feature-missing-in-target-cpu.rs +++ b/tests/ui/target-feature/abi-required-target-feature-missing-in-target-cpu.rs @@ -9,14 +9,14 @@ //@[arm] compile-flags: --target=armv8r-none-eabihf -Ctarget-cpu=cortex-r4 //@[arm] needs-llvm-components: arm -// LLVM 24 refuses to compile ARM minicore due to mismatched target features. -// FIXME(#161276): With LLVM rejecting this, we should make Rust's own warning an error. -//@[arm] max-llvm-major-version: 23 +// On x86 this is just a warning. +//@[x86] check-pass +//@[arm] check-fail -// For now this is just a warning. -//@ build-pass //@ ignore-backends: gcc //@ add-minicore +// Don't inherit the target-cpu above for minicore, to avoid errors when building that. +//@ minicore-compile-flags: -Ctarget-cpu=generic #![feature(no_core)] #![no_core] @@ -24,4 +24,5 @@ extern crate minicore; use minicore::*; -//~? WARN must be enabled to ensure that the ABI of the current target can be implemented correctly +//[x86]~? WARN must be enabled to ensure that the ABI of the current target can be implemented correctly +//[arm]~? ERROR must be enabled to ensure that the ABI of the current target can be implemented correctly From 8c4486bc034869f27c4218c3d0037dbf2ed484e9 Mon Sep 17 00:00:00 2001 From: Joe Isaacs Date: Tue, 1 Sep 2026 21:11:34 +0100 Subject: [PATCH 03/23] fix[154166]: closure debug capture print --- compiler/rustc_middle/src/mir/pretty.rs | 24 ++++++++++-------------- tests/mir-opt/issues/issue_154166.rs | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 tests/mir-opt/issues/issue_154166.rs diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 2bb886ee167a3..82ac4f89faa09 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1256,13 +1256,11 @@ impl<'tcx> Debug for Rvalue<'tcx> { }; let mut struct_fmt = fmt.debug_struct(&name); - // FIXME(project-rfc-2229#48): This should be a list of capture names/places - if let Some(def_id) = def_id.as_local() - && let Some(upvars) = tcx.upvars_mentioned(def_id) - { - for (&var_id, place) in iter::zip(upvars.keys(), places) { - let var_name = tcx.hir_name(var_id); - struct_fmt.field(var_name.as_str(), place); + if let Some(def_id) = def_id.as_local() { + let captures = tcx.closure_captures(def_id); + assert_eq!(captures.len(), places.len()); + for (&capture, place) in iter::zip(captures, places) { + struct_fmt.field(capture.to_symbol().as_str(), place); } } else { for (index, place) in places.iter().enumerate() { @@ -1277,13 +1275,11 @@ impl<'tcx> Debug for Rvalue<'tcx> { let name = format!("{{coroutine@{:?}}}", tcx.def_span(def_id)); let mut struct_fmt = fmt.debug_struct(&name); - // FIXME(project-rfc-2229#48): This should be a list of capture names/places - if let Some(def_id) = def_id.as_local() - && let Some(upvars) = tcx.upvars_mentioned(def_id) - { - for (&var_id, place) in iter::zip(upvars.keys(), places) { - let var_name = tcx.hir_name(var_id); - struct_fmt.field(var_name.as_str(), place); + if let Some(def_id) = def_id.as_local() { + let captures = tcx.closure_captures(def_id); + assert_eq!(captures.len(), places.len()); + for (&capture, place) in iter::zip(captures, places) { + struct_fmt.field(capture.to_symbol().as_str(), place); } } else { for (index, place) in places.iter().enumerate() { diff --git a/tests/mir-opt/issues/issue_154166.rs b/tests/mir-opt/issues/issue_154166.rs new file mode 100644 index 0000000000000..e65c59ea8fca4 --- /dev/null +++ b/tests/mir-opt/issues/issue_154166.rs @@ -0,0 +1,20 @@ +// Check that closure debug implementation correctly displays all captures precisely. + +//@ revisions: e2018 e2021 +//@[e2018] edition: 2018 +//@[e2021] edition: 2021 + +#![crate_type = "lib"] + +pub fn foo(x: (String, String)) { + // CHECK-LABEL: foo( + // e2018: {closure{{.*}}issue_154166{{.*}}} { x: {{.*}} }; + // e2021: {closure{{.*}}issue_154166{{.*}}} { x__0: {{.*}}, x__1: {{.*}} }; + let _closure = || { + if std::hint::black_box(true) { + let _a = &x.1; + } else { + let _b = x.0; + } + }; +} From e7e2b01f62b0d5a5fa3b201d60b98c282f874527 Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:57:16 +0200 Subject: [PATCH 04/23] fix supposedly unreachable `bug!` being reachable --- .../rustc_trait_selection/src/traits/wf.rs | 21 ++++++++++++++----- compiler/rustc_type_ir/src/term_kind.rs | 16 ++++++++++++++ .../gca/wf-inherentimpl.old.stderr | 10 +++++++++ .../ui/const-generics/gca/wf-inherentimpl.rs | 16 ++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 tests/ui/const-generics/gca/wf-inherentimpl.old.stderr create mode 100644 tests/ui/const-generics/gca/wf-inherentimpl.rs diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index dc29b6311cc7e..fc16b6d44c310 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -504,7 +504,16 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // (*) The predicates of an inherent associated type include the // predicates of the impl that it's contained in. - if !data.self_ty().has_escaping_bound_vars() { + // In an ideal world, there are no escaping bound vars here. However, WF is jank, and + // sometimes there are. We can only `compute_inherent_assoc_term_args` if the Self ty in the + // args has no escaping bound vars. If we already have impl format args, though, + // `compute_inherent_assoc_term_args` is a no-op (and we have no Self type), so no need to + // check for escaping bound vars. + let can_compute_impl_args = + matches!(data.kind, ty::AliasTermKind::InherentConstImpl { .. }) + || !data.self_ty().has_escaping_bound_vars(); + + if can_compute_impl_args { // FIXME(inherent_associated_types): Should this happen inside of a snapshot? // FIXME(inherent_associated_types): This is incompatible with the new solver and lazy norm! let args = traits::project::compute_inherent_assoc_term_args( @@ -1099,10 +1108,12 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { self.add_wf_preds_for_inherent_projection(alias_const.into()); return; // Subtree is handled by above function } - // please ping khyperia and/or BoxyUwU if this `bug!` fires - ty::AliasConstKind::InherentImpl { .. } => bug!( - "This ought to be unreachable, the entrypoints of WF should still have InherentSelf-form alias consts." - ), + // FIXME: This should be unreachable but isn't because we normalize in item + // wfck before computing wf requirements + ty::AliasConstKind::InherentImpl { .. } => { + self.add_wf_preds_for_inherent_projection(alias_const.into()); + return; + } ty::AliasConstKind::Projection { def_id } | ty::AliasConstKind::Free { def_id } | ty::AliasConstKind::Anon { def_id } => { diff --git a/compiler/rustc_type_ir/src/term_kind.rs b/compiler/rustc_type_ir/src/term_kind.rs index bb4ebf054d263..ed23b196fe269 100644 --- a/compiler/rustc_type_ir/src/term_kind.rs +++ b/compiler/rustc_type_ir/src/term_kind.rs @@ -1,3 +1,5 @@ +use std::debug_assert_matches; + use derive_where::derive_where; #[cfg(feature = "nightly")] use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash_NoContext}; @@ -265,11 +267,25 @@ impl AliasTerm { /// The following methods work only with (trait) associated term projections. // FIXME: Replace by an impl on Alias impl AliasTerm { + fn debug_assert_has_self(self) { + // InherentConstImpl is deliberately omitted here, it is not self-format args + debug_assert_matches!( + self.kind, + AliasTermKind::ProjectionTy { .. } + | AliasTermKind::ProjectionConst { .. } + | AliasTermKind::InherentTy { .. } + | AliasTermKind::InherentConstSelf { .. }, + "AliasTerm::self_ty is only valid on projection and inherent aliases" + ); + } + pub fn self_ty(self) -> I::Ty { + self.debug_assert_has_self(); self.args.type_at(0) } pub fn with_replaced_self_ty(self, interner: I, self_ty: I::Ty) -> Self { + self.debug_assert_has_self(); AliasTerm::new( interner, self.kind, diff --git a/tests/ui/const-generics/gca/wf-inherentimpl.old.stderr b/tests/ui/const-generics/gca/wf-inherentimpl.old.stderr new file mode 100644 index 0000000000000..0766847a93b18 --- /dev/null +++ b/tests/ui/const-generics/gca/wf-inherentimpl.old.stderr @@ -0,0 +1,10 @@ +error: `generic_const_args` requires -Znext-solver=globally to be enabled + --> $DIR/wf-inherentimpl.rs:7:12 + | +LL | #![feature(generic_const_args, min_generic_const_args)] + | ^^^^^^^^^^^^^^^^^^ + | + = help: enable all of these features + +error: aborting due to 1 previous error + diff --git a/tests/ui/const-generics/gca/wf-inherentimpl.rs b/tests/ui/const-generics/gca/wf-inherentimpl.rs new file mode 100644 index 0000000000000..cb3df20daa2dc --- /dev/null +++ b/tests/ui/const-generics/gca/wf-inherentimpl.rs @@ -0,0 +1,16 @@ +//@[next] check-pass +//@ revisions: next old +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +#![feature(inherent_associated_types)] +#![feature(macroless_generic_const_args)] +#![feature(generic_const_args, min_generic_const_args)] +//[old]~^ ERROR `generic_const_args` requires -Znext-solver=globally to be enabled +struct Foo; +impl Foo { + const SIZE: usize = { todo!() }; + fn to_bytes() -> [u8; Self::SIZE] { + todo!() + } +} +fn main() {} From aea4dd4b0377fb5881542815dc3c2352394e8514 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 2 Sep 2026 12:03:24 +0200 Subject: [PATCH 05/23] remove outdated next-solver FIXMEs --- compiler/rustc_hir_typeck/src/method/probe.rs | 22 ------------------- .../src/error_reporting/infer/mod.rs | 6 +---- .../traits/fulfillment_errors.rs | 2 -- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index f5b8b9d6a1e6f..40bf435e6d110 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -2184,28 +2184,6 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } } - // See . - // - // In the new solver, check the well-formedness of the return type. - // This emulates, in a way, the predicates that fall out of - // normalizing the return type in the old solver. - // - // FIXME(-Znext-solver): We alternatively could check the predicates of - // the method itself hold, but we intentionally do not do this in the old - // solver b/c of cycles, and doing it in the new solver would be stronger. - // This should be fixed in the future, since it likely leads to much better - // method winnowing. - if let Some(xform_ret_ty) = xform_ret_ty - && self.infcx.next_trait_solver() - { - ocx.register_obligation(traits::Obligation::new( - self.tcx, - cause.clone(), - self.param_env, - ty::ClauseKind::WellFormed(xform_ret_ty.into()), - )); - } - // Evaluate those obligations to see if they might possibly hold. for error in ocx.try_evaluate_obligations() { result = ProbeResult::NoMatch; diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 1210a3ef57e32..4f4bf0a8cc01a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -79,7 +79,6 @@ use crate::error_reporting::traits::ambiguity::{ use crate::infer; use crate::infer::relate::{self, RelateResult, TypeRelation}; use crate::infer::{InferCtxt, InferCtxtExt as _, TypeTrace, ValuePairs}; -use crate::solve::deeply_normalize_for_diagnostics; use crate::traits::{ MatchExpressionArmCause, Obligation, ObligationCause, ObligationCauseCode, ObligationCtxt, specialization_graph, @@ -1572,10 +1571,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let (expected_found, exp_found, is_simple_error, values, param_env) = match values { None => (None, Mismatch::Fixed("type"), false, None, None), Some(ty::ParamEnvAnd { param_env, value: values }) => { - let mut values = self.resolve_vars_if_possible(values); - if self.next_trait_solver() { - values = deeply_normalize_for_diagnostics(self, param_env, values); - } + let values = self.resolve_vars_if_possible(values); let (is_simple_error, exp_found) = match values { ValuePairs::Terms(ExpectedFound { expected, found }) => { match (expected.kind(), found.kind()) { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 11b051b530198..29ae564d9eb9a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -1669,8 +1669,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { bound_predicate.rebind(data), ); let unnormalized_term = data.projection_term.to_term(self.tcx, ty::IsRigid::No); - // FIXME(-Znext-solver): For diagnostic purposes, it would be nice - // to deeply normalize this type. let normalized_term = ocx.normalize( &obligation.cause, obligation.param_env, From 07c009005bebd09e778516e7dd46cc37cd658d3c Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Wed, 2 Sep 2026 00:19:58 +0000 Subject: [PATCH 06/23] core: mark float `ClampBounds` methods as `#[inline]` Commit bd174e1b20a8 ("Implement clamp_to") added a few float methods that are not marked `#[inline]`. This causes `core` to require new symbols in soft-float builds, even if the methods are unused, e.g. from the Linux kernel: ld.lld: error: undefined symbol: fmaximum_numf >>> referenced by core.1f440ee8661e09f9-cgu.0 >>> rust/core.o:( as core::cmp::clamp::ClampBounds>::clamp) in archive vmlinux.a (and similar for `f{min,max}imum_num{f,}` and `__gt{s,d}f2`). It is possible to work around this in the Linux side, but these methods should probably be `#[inline]` to begin with, like many other similar methods are. Thus mark them as inline. Signed-off-by: Miguel Ojeda --- library/core/src/cmp/clamp.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/core/src/cmp/clamp.rs b/library/core/src/cmp/clamp.rs index a737b59bbbe51..e42cd7fb39f29 100644 --- a/library/core/src/cmp/clamp.rs +++ b/library/core/src/cmp/clamp.rs @@ -66,6 +66,7 @@ macro impl_for_float($t:ty) { #[unstable(feature = "clamp_bounds", issue = "147781")] #[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] const impl ClampBounds<$t> for RangeFrom<$t> { + #[inline] fn clamp(self, value: $t) -> $t { assert!(!self.start.is_nan(), "start was NaN"); value.max(self.start) @@ -75,6 +76,7 @@ macro impl_for_float($t:ty) { #[unstable(feature = "clamp_bounds", issue = "147781")] #[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] const impl ClampBounds<$t> for RangeToInclusive<$t> { + #[inline] fn clamp(self, value: $t) -> $t { assert!(!self.end.is_nan(), "end was NaN"); value.min(self.end) @@ -88,6 +90,7 @@ macro impl_for_float($t:ty) { clippy::neg_cmp_op_on_partial_ord, reason = "NaN check is intentionally included in comparison" )] + #[inline] fn clamp(self, value: $t) -> $t { let (start, end) = self.into_inner(); assert!(start <= end, "start > end, or either was NaN"); From 75ff1f50b69c380d8880ceb4d89db2bfe3e903af Mon Sep 17 00:00:00 2001 From: Shun Sakai Date: Wed, 2 Sep 2026 22:50:47 +0900 Subject: [PATCH 07/23] docs(time): clarify exact seconds for week and day --- library/core/src/time.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 816da7a2fb7f2..1dafd6cbe080d 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -345,6 +345,8 @@ impl Duration { /// Creates a new `Duration` from the specified number of weeks. /// + /// For this method, one week is defined as 7 days, or 604,800 seconds. + /// /// # Panics /// /// Panics if the given number of weeks overflows the `Duration` size. @@ -373,6 +375,8 @@ impl Duration { /// Creates a new `Duration` from the specified number of days. /// + /// For this method, one day is defined as 24 hours, or 86,400 seconds. + /// /// # Panics /// /// Panics if the given number of days overflows the `Duration` size. From cf0c54cda4035e2667bd8af37dc44106153a4100 Mon Sep 17 00:00:00 2001 From: Shun Sakai Date: Wed, 2 Sep 2026 23:50:07 +0900 Subject: [PATCH 08/23] docs(time): clarify exact seconds for hour and minute --- library/core/src/time.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 816da7a2fb7f2..5126733a675a6 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -401,6 +401,8 @@ impl Duration { /// Creates a new `Duration` from the specified number of hours. /// + /// For this method, one hour is defined as 60 minutes, or 3,600 seconds. + /// /// # Panics /// /// Panics if the given number of hours overflows the `Duration` size. @@ -429,6 +431,8 @@ impl Duration { /// Creates a new `Duration` from the specified number of minutes. /// + /// For this method, one minute is defined as 60 seconds. + /// /// # Panics /// /// Panics if the given number of minutes overflows the `Duration` size. From 9c3dad95b6cadbc7674f016e00222a498fbe5124 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 3 Sep 2026 16:23:22 +1000 Subject: [PATCH 09/23] Decide the effective DefId in `extract_hir_info` without recursion --- .../src/coverage/hir_info.rs | 32 +++++++++++-------- .../rustc_mir_transform/src/coverage/mod.rs | 6 ++-- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/hir_info.rs b/compiler/rustc_mir_transform/src/coverage/hir_info.rs index ab66bf1a733ef..afde28100146a 100644 --- a/compiler/rustc_mir_transform/src/coverage/hir_info.rs +++ b/compiler/rustc_mir_transform/src/coverage/hir_info.rs @@ -1,6 +1,7 @@ use rustc_hir as hir; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_middle::hir::nested_filter; +use rustc_middle::mir; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::Span; use rustc_span::def_id::LocalDefId; @@ -20,21 +21,24 @@ pub(crate) struct ExtractedHirInfo { pub(crate) hole_spans: Vec, } -pub(crate) fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHirInfo { - // FIXME(#79625): Consider improving MIR to provide the information needed, to avoid going back - // to HIR for it. - - // Synthetic by-move coroutine bodies don't have useful HIR of their own. - // Use the original coroutine body instead. These synthetic bodies are - // created with a coroutine type, so we can inspect that type as-is. - if tcx.is_synthetic_mir(def_id) { - let effective_def_id = +pub(crate) fn extract_hir_info<'tcx>( + tcx: TyCtxt<'tcx>, + mir_body: &mir::Body<'tcx>, +) -> ExtractedHirInfo { + let def_id: LocalDefId = { + let mut def_id = mir_body.source.def_id().expect_local(); + + // Synthetic by-move coroutine bodies don't have useful HIR of their own. + // Use the original coroutine body instead. These synthetic bodies are + // created with a coroutine type, so we can inspect that type as-is. + if tcx.is_synthetic_mir(def_id) { match *tcx.type_of(def_id).instantiate_identity().skip_normalization().kind() { - ty::Coroutine(coroutine_def_id, _) => coroutine_def_id.expect_local(), - _ => tcx.local_parent(def_id), - }; - return extract_hir_info(tcx, effective_def_id); - } + ty::Coroutine(coroutine_def_id, _) => def_id = coroutine_def_id.expect_local(), + _ => def_id = tcx.local_parent(def_id), + } + } + def_id + }; let hir_node = tcx.hir_node_by_def_id(def_id); let fn_body_id = hir_node.body_id().expect("HIR node is a function with body"); diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index fdca5e9bfdc9b..d8c4ba4b40b04 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -58,10 +58,10 @@ impl<'tcx> crate::MirPass<'tcx> for InstrumentCoverage { } fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir::Body<'tcx>) { - let def_id = mir_body.source.def_id(); - let _span = debug_span!("instrument_function_for_coverage", ?def_id).entered(); + let _span = debug_span!("instrument_function_for_coverage", def_id = ?mir_body.source.def_id()) + .entered(); - let hir_info = hir_info::extract_hir_info(tcx, def_id.expect_local()); + let hir_info = hir_info::extract_hir_info(tcx, mir_body); // Build the coverage graph, which is a simplified view of the MIR control-flow // graph that ignores some details not relevant to coverage instrumentation. From 2a3529c8b81223ba4b2db541b717577a3a1d7b22 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 3 Sep 2026 16:25:35 +1000 Subject: [PATCH 10/23] Use let-chains to simplify a deep HIR pattern --- compiler/rustc_mir_transform/src/coverage/hir_info.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/hir_info.rs b/compiler/rustc_mir_transform/src/coverage/hir_info.rs index afde28100146a..246104ee97843 100644 --- a/compiler/rustc_mir_transform/src/coverage/hir_info.rs +++ b/compiler/rustc_mir_transform/src/coverage/hir_info.rs @@ -49,14 +49,15 @@ pub(crate) fn extract_hir_info<'tcx>( let mut body_span = hir_body.value.span; - use hir::{Closure, Expr, ExprKind, Node}; // Unexpand a closure's body span back to the context of its declaration. // This helps with closure bodies that consist of just a single bang-macro, // and also with closure bodies produced by async desugaring. - if let Node::Expr(&Expr { kind: ExprKind::Closure(&Closure { fn_decl_span, .. }), .. }) = - hir_node + if let hir::Node::Expr(expr) = hir_node + && let hir::ExprKind::Closure(closure) = expr.kind + && let Some(effective_body_span) = + body_span.find_ancestor_in_same_ctxt(closure.fn_decl_span) { - body_span = body_span.find_ancestor_in_same_ctxt(fn_decl_span).unwrap_or(body_span); + body_span = effective_body_span; } // The actual signature span is only used if it has the same context and From 68fcd1b56409453eca12701aa9d9b2931f6aaf31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 30 Jul 2026 10:14:45 +0200 Subject: [PATCH 11/23] Bless bootstrap tests And only include the target name when rendering test metadata, to avoid including filenames in it. --- src/bootstrap/src/core/builder/tests.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 1f08ee9c11864..6af25c8a1ce2f 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1941,6 +1941,8 @@ mod snapshot { [test] compiletest-coverage 1 [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 [test] compiletest-codegen-llvm 1 [test] compiletest-codegen-units 1 [test] compiletest-assembly-llvm 1 @@ -2122,6 +2124,9 @@ mod snapshot { [test] compiletest-coverage 2 [build] rustc 2 -> std 2 [test] compiletest-mir-opt 2 + [build] rustc 1 -> std 1 + [build] rustc 2 -> std 2 + [test] compiletest-mir-opt 2 [test] compiletest-codegen-llvm 2 [test] compiletest-codegen-units 2 [test] compiletest-assembly-llvm 2 @@ -3184,7 +3189,7 @@ fn render_metadata(metadata: &StepMetadata, config: &RenderConfig) -> String { } fn normalize_target(target: TargetSelection, config: &RenderConfig) -> String { - let mut target = target.to_string(); + let mut target = target.triple.to_string(); if config.normalize_host { target = target.replace(&host_target(), "host"); } From d44c98f24b6b7dbb2678eac661302ca96b986345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 09:59:58 +0200 Subject: [PATCH 12/23] Generalize `MirOptPanicAbortSyntheticTarget` to `SyntheticTargetWithPanicStrategy` --- .../src/core/build_steps/synthetic_targets.rs | 26 ++++++++++++++++--- src/bootstrap/src/core/build_steps/test.rs | 8 +++--- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 2b5039214f62c..75999c1fa355b 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -12,17 +12,37 @@ use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct MirOptPanicAbortSyntheticTarget { +pub(crate) enum PanicStrategy { + Unwind, + Abort, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct SyntheticTargetWithPanicStrategy { pub(crate) compiler: Compiler, pub(crate) base: TargetSelection, + pub(crate) strategy: PanicStrategy, +} + +impl SyntheticTargetWithPanicStrategy { + pub(crate) fn panic_abort(compiler: Compiler, base: TargetSelection) -> Self { + Self { compiler, base, strategy: PanicStrategy::Abort } + } + pub(crate) fn panic_unwind(compiler: Compiler, base: TargetSelection) -> Self { + Self { compiler, base, strategy: PanicStrategy::Unwind } + } } -impl Step for MirOptPanicAbortSyntheticTarget { +impl Step for SyntheticTargetWithPanicStrategy { type Output = TargetSelection; fn run(self, builder: &Builder<'_>) -> Self::Output { + let strategy = match self.strategy { + PanicStrategy::Unwind => "unwind", + PanicStrategy::Abort => "abort", + }; create_synthetic_target(builder, self.compiler, "miropt-abort", self.base, |spec| { - spec.insert("panic-strategy".into(), "abort".into()); + spec.insert("panic-strategy".into(), strategy.into()); }) } } diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 47318d3c086f5..81c7d7e9f5278 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -22,7 +22,7 @@ use crate::core::build_steps::format::InternalRustfmt; use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; -use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget; +use crate::core::build_steps::synthetic_targets::SyntheticTargetWithPanicStrategy; use crate::core::build_steps::test::compiletest::CompiletestMode; use crate::core::build_steps::test::failed_tests::{RecordFailedTests, SetupFailedTestsFile}; use crate::core::build_steps::tool::{ @@ -2216,10 +2216,8 @@ impl CommandLineStep for MirOpt { for target in ["x86_64-apple-darwin", "i686-unknown-linux-musl"] { let target = TargetSelection::from_user(target); - let panic_abort_target = builder.ensure(MirOptPanicAbortSyntheticTarget { - compiler: self.compiler, - base: target, - }); + let panic_abort_target = builder + .ensure(SyntheticTargetWithPanicStrategy::panic_abort(self.compiler, target)); run(panic_abort_target); } } From fccffee254f56f75b58f564ff3b2433514d3d3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 10:11:25 +0200 Subject: [PATCH 13/23] Create targets for `mir-opt` tests explicitly and use the minimal set of targets to check --- .../src/core/build_steps/synthetic_targets.rs | 30 +++-- src/bootstrap/src/core/build_steps/test.rs | 108 +++++++++++++----- src/bootstrap/src/core/builder/tests.rs | 43 +++++++ 3 files changed, 143 insertions(+), 38 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 75999c1fa355b..4afbdff464e34 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -69,16 +69,7 @@ fn create_synthetic_target( return TargetSelection::create_synthetic(&name, path.to_str().unwrap()); } - let mut cmd = builder.rustc_cmd(compiler); - cmd.arg("--target").arg(base.rustc_target_arg()); - cmd.args(["-Zunstable-options", "--print", "target-spec-json"]); - - // If `rust.channel` is set to either beta or stable, rustc will complain that - // we cannot use nightly features. So `RUSTC_BOOTSTRAP` is needed here. - cmd.env("RUSTC_BOOTSTRAP", "1"); - - let output = cmd.run_capture(builder).stdout(); - let mut spec: serde_json::Value = serde_json::from_slice(output.as_bytes()).unwrap(); + let mut spec = get_target_specs(builder, compiler, base); let spec_map = spec.as_object_mut().unwrap(); // The `is-builtin` attribute of a spec needs to be removed, otherwise rustc will complain. @@ -89,3 +80,22 @@ fn create_synthetic_target( std::fs::write(&path, serde_json::to_vec_pretty(&spec).unwrap()).unwrap(); TargetSelection::create_synthetic(&name, path.to_str().unwrap()) } + +/// Get the JSON target specs from the given compiler. +pub fn get_target_specs( + builder: &Builder<'_>, + compiler: Compiler, + target: TargetSelection, +) -> serde_json::Value { + let mut cmd = builder.rustc_cmd(compiler); + cmd.arg("--target").arg(target.rustc_target_arg()); + cmd.args(["-Zunstable-options", "--print", "target-spec-json"]); + + // If `rust.channel` is set to either beta or stable, rustc will complain that + // we cannot use nightly features. So `RUSTC_BOOTSTRAP` is needed here. + cmd.env("RUSTC_BOOTSTRAP", "1"); + + let output = cmd.cached().run_capture(builder).stdout(); + let spec: serde_json::Value = serde_json::from_slice(output.as_bytes()).unwrap(); + spec +} diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 81c7d7e9f5278..8bf5189382c96 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -22,7 +22,9 @@ use crate::core::build_steps::format::InternalRustfmt; use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; -use crate::core::build_steps::synthetic_targets::SyntheticTargetWithPanicStrategy; +use crate::core::build_steps::synthetic_targets::{ + SyntheticTargetWithPanicStrategy, get_target_specs, +}; use crate::core::build_steps::test::compiletest::CompiletestMode; use crate::core::build_steps::test::failed_tests::{RecordFailedTests, SetupFailedTestsFile}; use crate::core::build_steps::tool::{ @@ -2168,8 +2170,8 @@ test!(CoverageRunRustdoc { // For the mir-opt suite we do not use macros, as we need custom behavior when blessing. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MirOpt { - pub compiler: Compiler, - pub target: TargetSelection, + compiler: Compiler, + target: TargetSelection, } impl CommandLineStep for MirOpt { @@ -2185,43 +2187,93 @@ impl CommandLineStep for MirOpt { fn make_run(run: RunConfig<'_>) { let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple()); - run.builder.ensure(MirOpt { compiler, target: run.target }); - } - fn run(self, builder: &Builder<'_>) { - let run = |target| { - builder.ensure(Compiletest { - test_compiler: self.compiler, - target, - mode: CompiletestMode::MirOpt, - suite: "mir-opt", - path: "tests/mir-opt", - compare_mode: None, - }) - }; + // The mir-opt tests check four distinct configurations, the cross-product of the + // following two axes: + // - Bit-width: 32-bit and 64-bit + // - Panic strategy: unwind and abort - run(self.target); + // Here we generate several configurations of this step to evaluate multiple targets. + let targets = if run.builder.config.cmd.bless() { + // When blessing, we generate a fixed set of 4 targets that cover all the + // possible combinations. This selection covers all our tier 1 operating systems and + // architectures using only tier 1 targets. - // Run more targets with `--bless`. But we always run the host target first, since some - // tests use very specific `only` clauses that are not covered by the target set below. - if builder.config.cmd.bless() { - // All that we really need to do is cover all combinations of 32/64-bit and unwind/abort, - // but while we're at it we might as well flex our cross-compilation support. This - // selection covers all our tier 1 operating systems and architectures using only tier - // 1 targets. + // We also include the host target, since some tests use very specific `only` clauses + // that are not covered by the target set below. + let mut targets = vec![run.target]; + + // 64-bit and 32-bit panic=unwind for target in ["aarch64-unknown-linux-gnu", "i686-pc-windows-msvc"] { - run(TargetSelection::from_user(target)); + targets.push(TargetSelection::from_user(target)); } + // 64-bit and 32-bit panic=abort for target in ["x86_64-apple-darwin", "i686-unknown-linux-musl"] { let target = TargetSelection::from_user(target); - let panic_abort_target = builder - .ensure(SyntheticTargetWithPanicStrategy::panic_abort(self.compiler, target)); - run(panic_abort_target); + let panic_abort_target = run + .builder + .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, target)); + targets.push(panic_abort_target); + } + targets + } else { + // When not blessing, we could also test all four configurations. But that would make + // local tests quite slow. So instead, we check the current target, and then the + // current target with switched panic strategy. + // On CI, we should be running this test for both 32-bit and 64-bit targets, so together + // this should check all possible configurations on CI. + + // The complicated thing here is how to figure out the panic strategy of the current + // target. In theory, we could just assume that in most situations, the target is + // panic=unwind, and force generation of panic=abort. But to ensure that we do this + // properly, we actually query the compiler to figure out the panic strategy, and then + // generate a synthetic target with the opposite strategy. + if !run.builder.config.dry_run() { + let target_specs = get_target_specs(run.builder, compiler, run.target); + let panic_strategy = target_specs + .as_object() + .and_then(|obj| obj.get("panic-strategy")) + .and_then(|v| v.as_str()) + // The default panic strategy is unwind + .unwrap_or("unwind"); + let synthetic_target = if panic_strategy == "unwind" { + run.builder + .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, run.target)) + } else { + run.builder.ensure(SyntheticTargetWithPanicStrategy::panic_unwind( + compiler, run.target, + )) + }; + vec![run.target, synthetic_target] + } else { + // Note: in a dry run, we just hardcode the other target to be panic=abort, + // so that we still see two targets in snapshot tests. + vec![ + run.target, + run.builder.ensure(SyntheticTargetWithPanicStrategy::panic_abort( + compiler, run.target, + )), + ] } + }; + + for target in targets { + run.builder.ensure(MirOpt { compiler, target }); } } + + fn run(self, builder: &Builder<'_>) { + builder.ensure(Compiletest { + test_compiler: self.compiler, + target: self.target, + mode: CompiletestMode::MirOpt, + suite: "mir-opt", + path: "tests/mir-opt", + compare_mode: None, + }); + } } /// Executes the `compiletest` tool to run a suite of tests. diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 6af25c8a1ce2f..e023d9d7f2b16 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2394,6 +2394,49 @@ mod snapshot { "); } + #[test] + fn test_mir_opt() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + prepare_test_config(&ctx) + .path("tests/mir-opt") + .render_steps(), @" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Compiletest 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + "); + } + + #[test] + fn test_mir_opt_bless() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + prepare_test_config(&ctx) + .path("tests/mir-opt") + .arg("--bless") + .targets(&[TEST_TRIPLE_1]) + .render_steps(), @" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Compiletest 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + "); + } + #[test] fn doc_all() { let ctx = TestCtx::new(); From d07e492612ad5d36e0ea650b37a7624287a1b9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 10:49:17 +0200 Subject: [PATCH 14/23] Fix host normalization --- src/bootstrap/src/core/builder/tests.rs | 27 +++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index e023d9d7f2b16..895b1849efdf4 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2416,23 +2416,28 @@ mod snapshot { let ctx = TestCtx::new(); insta::assert_snapshot!( prepare_test_config(&ctx) - .path("tests/mir-opt") .arg("--bless") .targets(&[TEST_TRIPLE_1]) - .render_steps(), @" - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [build] rustc 0 -> Compiletest 1 - [build] rustc 1 -> std 1 + .path("tests/mir-opt") + .get_steps() + // When blessing, the step executes for a pinned set of targets, so we cannot + // normalize here. + .render_with(RenderConfig { + normalize_host: false + }), @" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Compiletest 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 "); } From 00f0834818e697a11292f3c8a6ce6959a0f2090e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 11:15:16 +0200 Subject: [PATCH 15/23] Bless at most four individual targets --- .../src/core/build_steps/synthetic_targets.rs | 2 +- src/bootstrap/src/core/build_steps/test.rs | 83 +++++++++++-------- src/bootstrap/src/core/builder/tests.rs | 27 +++--- 3 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 4afbdff464e34..04b815743bafd 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -11,7 +11,7 @@ use crate::core::builder::{Builder, Step}; use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum PanicStrategy { Unwind, Abort, diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 8bf5189382c96..aba8d52959c88 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -23,7 +23,7 @@ use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; use crate::core::build_steps::synthetic_targets::{ - SyntheticTargetWithPanicStrategy, get_target_specs, + PanicStrategy, SyntheticTargetWithPanicStrategy, get_target_specs, }; use crate::core::build_steps::test::compiletest::CompiletestMode; use crate::core::build_steps::test::failed_tests::{RecordFailedTests, SetupFailedTestsFile}; @@ -2193,6 +2193,31 @@ impl CommandLineStep for MirOpt { // - Bit-width: 32-bit and 64-bit // - Panic strategy: unwind and abort + // Return the bitwidth and panic strategy of the default (usually host) target + let get_bitwidth_and_panic_strategy = || -> (u64, PanicStrategy) { + if run.builder.config.dry_run() { + return (64, PanicStrategy::Unwind); + } + + let specs = get_target_specs(run.builder, compiler, run.target); + let specs = specs.as_object(); + let bitwidth = specs + .and_then(|obj| obj.get("target-pointer-width")) + .and_then(|v| v.as_i64()) + .map(|v| v as u64) + .unwrap_or(64); + let panic_strategy = specs + .and_then(|obj| obj.get("panic-strategy")) + .and_then(|v| v.as_str()) + .map(|v| match v { + "unwind" => PanicStrategy::Unwind, + _ => PanicStrategy::Abort, + }) + // The default panic strategy is unwind + .unwrap_or(PanicStrategy::Unwind); + (bitwidth, panic_strategy) + }; + // Here we generate several configurations of this step to evaluate multiple targets. let targets = if run.builder.config.cmd.bless() { // When blessing, we generate a fixed set of 4 targets that cover all the @@ -2202,22 +2227,32 @@ impl CommandLineStep for MirOpt { // We also include the host target, since some tests use very specific `only` clauses // that are not covered by the target set below. - let mut targets = vec![run.target]; + let (bitwidth, strategy) = get_bitwidth_and_panic_strategy(); + let mut targets = vec![(bitwidth, strategy, run.target)]; // 64-bit and 32-bit panic=unwind - for target in ["aarch64-unknown-linux-gnu", "i686-pc-windows-msvc"] { - targets.push(TargetSelection::from_user(target)); + for (bitwidth, target) in + [(64, "aarch64-unknown-linux-gnu"), (32, "i686-pc-windows-msvc")] + { + targets.push((bitwidth, PanicStrategy::Unwind, TargetSelection::from_user(target))); } // 64-bit and 32-bit panic=abort - for target in ["x86_64-apple-darwin", "i686-unknown-linux-musl"] { + for (bitwidth, target) in [(64, "x86_64-apple-darwin"), (32, "i686-unknown-linux-musl")] + { let target = TargetSelection::from_user(target); let panic_abort_target = run .builder .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, target)); - targets.push(panic_abort_target); + targets.push((bitwidth, PanicStrategy::Abort, panic_abort_target)); } - targets + // This is a small optimization for local blessing. + // If we figure out that the host target already has a given bitwidth/panic strategy + // combination, we do not add the fixed targets to the list. + let mut unique = HashSet::new(); + targets.retain(|(bitwidth, strategy, _)| unique.insert((*bitwidth, *strategy))); + + targets.into_iter().map(|(_, _, target)| target).collect() } else { // When not blessing, we could also test all four configurations. But that would make // local tests quite slow. So instead, we check the current target, and then the @@ -2230,33 +2265,15 @@ impl CommandLineStep for MirOpt { // panic=unwind, and force generation of panic=abort. But to ensure that we do this // properly, we actually query the compiler to figure out the panic strategy, and then // generate a synthetic target with the opposite strategy. - if !run.builder.config.dry_run() { - let target_specs = get_target_specs(run.builder, compiler, run.target); - let panic_strategy = target_specs - .as_object() - .and_then(|obj| obj.get("panic-strategy")) - .and_then(|v| v.as_str()) - // The default panic strategy is unwind - .unwrap_or("unwind"); - let synthetic_target = if panic_strategy == "unwind" { - run.builder - .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, run.target)) - } else { - run.builder.ensure(SyntheticTargetWithPanicStrategy::panic_unwind( - compiler, run.target, - )) - }; - vec![run.target, synthetic_target] + let panic_strategy = get_bitwidth_and_panic_strategy().1; + let synthetic_target = if panic_strategy == PanicStrategy::Unwind { + run.builder + .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, run.target)) } else { - // Note: in a dry run, we just hardcode the other target to be panic=abort, - // so that we still see two targets in snapshot tests. - vec![ - run.target, - run.builder.ensure(SyntheticTargetWithPanicStrategy::panic_abort( - compiler, run.target, - )), - ] - } + run.builder + .ensure(SyntheticTargetWithPanicStrategy::panic_unwind(compiler, run.target)) + }; + vec![run.target, synthetic_target] }; for target in targets { diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 895b1849efdf4..021587074d4cb 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2417,27 +2417,22 @@ mod snapshot { insta::assert_snapshot!( prepare_test_config(&ctx) .arg("--bless") + .hosts(&[TEST_TRIPLE_1]) + .arg("--build") + .arg(TEST_TRIPLE_1) .targets(&[TEST_TRIPLE_1]) .path("tests/mir-opt") - .get_steps() - // When blessing, the step executes for a pinned set of targets, so we cannot - // normalize here. - .render_with(RenderConfig { - normalize_host: false - }), @" - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [build] rustc 0 -> Compiletest 1 - [build] rustc 1 -> std 1 + .render_steps(), @" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Compiletest 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 - [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 "); } From 506a0852943c34ffb72a2a38802a302f8b836663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 10 Aug 2026 11:03:52 +0200 Subject: [PATCH 16/23] Do not call `configure_linker` for synthetic targets --- src/bootstrap/src/core/builder/cargo.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index b04f61eafee32..5b63dc4a0f7ae 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -178,7 +178,11 @@ impl Cargo { // No need to configure the target linker for these command types. Kind::Clean | Kind::Check | Kind::Format | Kind::Setup => {} _ => { - cargo.configure_linker(builder); + // Do not configure the linker for synthetic targets, as we won't have cc set up + // for them. + if !target.is_synthetic() { + cargo.configure_linker(builder); + } } } From 99158cd9140d64599aadffd973019cd5866ee639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 20 Aug 2026 08:50:32 +0200 Subject: [PATCH 17/23] Add `needs-deterministic-layouts` flag --- tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs index 5534a45f19d64..8593a322ad363 100644 --- a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs +++ b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs @@ -1,4 +1,6 @@ //@ test-mir-pass: GVN +// layout randomization affects the alloc output +//@ needs-deterministic-layouts //@ compile-flags: -Zinline-mir --crate-type lib // EMIT_MIR_FOR_EACH_BIT_WIDTH // EMIT_MIR_FOR_EACH_PANIC_STRATEGY From 98f5ad849ce3ec28399d61e8ddabf386a55cc661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 1 Sep 2026 14:46:02 +0200 Subject: [PATCH 18/23] Add comments --- src/bootstrap/src/core/build_steps/synthetic_targets.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 04b815743bafd..2c35b39287d70 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -11,6 +11,8 @@ use crate::core::builder::{Builder, Step}; use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; +/// Note that this currently only contains panic strategies that we somehow use in bootstrap, not +/// all possible strategires supported by rustc. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum PanicStrategy { Unwind, @@ -82,6 +84,7 @@ fn create_synthetic_target( } /// Get the JSON target specs from the given compiler. +/// Note that the set of targets will differ between the stage0 and stage1+ (in-tree) compiler! pub fn get_target_specs( builder: &Builder<'_>, compiler: Compiler, From a983fe5443a3c6b1682829cfe86f13f19439d89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 1 Sep 2026 15:53:04 +0200 Subject: [PATCH 19/23] Bless test --- ...er.enumerated_loop.runtime-optimized.after.panic-abort.mir | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-abort.mir index 549af7af4d888..b42087b5c822d 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-abort.mir @@ -21,7 +21,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () { debug x => _34; } scope 18 (inlined > as Iterator>::next) { - let mut _22: std::option::Option; + let mut _22: std::option::Option; let mut _27: std::option::Option<&T>; let mut _30: (usize, bool); let mut _31: (usize, &T); @@ -32,7 +32,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () { } scope 20 { scope 21 { - scope 27 (inlined as FromResidual>>::from_residual) { + scope 27 (inlined as FromResidual>>::from_residual) { let mut _21: isize; let mut _23: bool; } From 3ba9f00a84818ffe43173f70861da0c80b41343b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 3 Sep 2026 10:00:24 +0200 Subject: [PATCH 20/23] Do not normalize host in mir-opt bootstrap test to fix it on i686-pc-windows-msvc --- src/bootstrap/src/core/builder/tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 021587074d4cb..97c8d532edfb8 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2422,7 +2422,10 @@ mod snapshot { .arg(TEST_TRIPLE_1) .targets(&[TEST_TRIPLE_1]) .path("tests/mir-opt") - .render_steps(), @" + .get_steps() + .render_with(RenderConfig { + normalize_host: false + }), @" [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 From c5c326ba70147a51f20e6f8ae530c6784ed7f644 Mon Sep 17 00:00:00 2001 From: Hans Wennborg Date: Thu, 3 Sep 2026 13:08:09 +0200 Subject: [PATCH 21/23] Pass -Z merge-functions=disabled in tests/codegen-llvm/intrinsics/unchecked_math.rs Otherwise e.g. `@unchecked_add_unsigned` and `@unchecked_add_signed` get merged after https://github.com/llvm/llvm-project/pull/220015, breaking the test's expectations. --- tests/codegen-llvm/intrinsics/unchecked_math.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/codegen-llvm/intrinsics/unchecked_math.rs b/tests/codegen-llvm/intrinsics/unchecked_math.rs index 419c120ede9ec..7f63ef99c4e12 100644 --- a/tests/codegen-llvm/intrinsics/unchecked_math.rs +++ b/tests/codegen-llvm/intrinsics/unchecked_math.rs @@ -1,3 +1,4 @@ +//@ compile-flags: -Z merge-functions=disabled #![crate_type = "lib"] #![feature(core_intrinsics)] From 1e10424f0d0623f6c31434dfb04cb3662c1e3466 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 28 Aug 2026 19:05:35 +0000 Subject: [PATCH 22/23] Windows: add fallback if canonicalize fails Get the NT path then search for a drive that links to a prefix of it. --- library/std/src/lib.rs | 1 + library/std/src/sys/fs/windows.rs | 73 ++++++++++++++++++- library/std/src/sys/fs/windows/tests.rs | 21 ++++++ library/std/src/sys/pal/windows/api.rs | 39 ++++++++++ .../std/src/sys/pal/windows/c/bindings.txt | 3 + .../std/src/sys/pal/windows/c/windows_sys.rs | 5 +- 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 library/std/src/sys/fs/windows/tests.rs diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 92eccc27ee05d..5fb512eb3ff9d 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -390,6 +390,7 @@ #![feature(str_internals)] #![feature(sync_unsafe_cell)] #![feature(temporary_niche_types)] +#![feature(trim_prefix_suffix)] #![feature(ub_checks)] #![feature(uint_carryless_mul)] #![feature(used_with_arg)] diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index c99524375113a..288331d4db69a 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -6,6 +6,7 @@ use crate::ffi::{OsStr, OsString, c_void}; use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::mem::{self, MaybeUninit, offset_of}; +use crate::os::windows::ffi::{OsStrExt, OsStringExt}; use crate::os::windows::io::{AsHandle, BorrowedHandle}; use crate::os::windows::prelude::*; use crate::path::{Path, PathBuf}; @@ -18,6 +19,9 @@ use crate::sys::time::SystemTime; use crate::sys::{Align8, AsInner, FromInner, IntoInner, c, cvt}; use crate::{fmt, ptr, slice}; +#[cfg(test)] +mod tests; + mod dir; pub use dir::Dir; mod remove_dir_all; @@ -1591,14 +1595,77 @@ pub fn set_times_nofollow(p: &WCStr, times: FileTimes) -> io::Result<()> { } fn get_path(f: impl AsRawHandle) -> io::Result { + let h = f.as_raw_handle(); + // If getting the canonical path fails with ERROR_INVALID_FUNCTION + // then it's likely it failed to resolve the path's drive. + // In that case, use the fallback method to resolve it. + let invalid_function = Some(c::ERROR_INVALID_FUNCTION as i32); + match get_path_canonical(h) { + Err(e) if e.raw_os_error() == invalid_function => get_path_fallback(h).ok_or(e), + result => result, + } +} + +fn get_path_canonical(handle: c::HANDLE) -> io::Result { fill_utf16_buf( - |buf, sz| unsafe { - c::GetFinalPathNameByHandleW(f.as_raw_handle(), buf, sz, c::VOLUME_NAME_DOS) - }, + |buf, sz| unsafe { c::GetFinalPathNameByHandleW(handle, buf, sz, c::VOLUME_NAME_DOS) }, |buf| PathBuf::from(OsString::from_wide(buf)), ) } +/// Fallback in case `get_path_canonical` fails. +/// +/// `get_path_canonical` can fail if the Win32 drive name cannot be resolved. +/// This can happen with certain third party drivers that don't integrate +/// with the mount manager. +/// +/// Instead we manually do the same job by getting the NT path +/// and then finding the first drive letter that points to a prefix of +/// that path. From there we can construct a Win32 path. +/// +/// It's implemented by first getting the NT path, which should always succeed. +/// Then we use [`GetLogicalDrives`] to get a bit array of win32 drive letters +/// from 'A' to 'Z'. If the corresponding bit is set then it means that drive exists. +/// E.g. bit 2 being set means there's a `C:` drive. +/// +/// Then for each drive we use [`QueryDosDeviceW`] to see the NT path that drive resolves to. +/// If that path is a prefix to the path we got initially then we treat that as the canonical drive letter. +/// So in the unlikely even two drives point to the same device, the lowest one is considered canonical. +/// +/// [`GetLogicalDrives`]: https://learn.microsoft.com/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives +/// [`QueryDosDeviceW`]: https://learn.microsoft.com/windows/win32/api/fileapi/nf-fileapi-querydosdevicew +fn get_path_fallback(handle: c::HANDLE) -> Option { + fill_utf16_buf( + |buf, sz| unsafe { c::GetFinalPathNameByHandleW(handle, buf, sz, c::VOLUME_NAME_NT) }, + |nt_path| { + let mut buf = [0_u16; c::MAX_PATH as usize]; + for letter in api::get_logical_drives() { + let device_name = [letter as u16, b':' as u16, 0]; + // SAFETY: `device_name` is a null terminated u16 string + if let Some(drive_path) = unsafe { api::query_dos_device(&device_name, &mut buf) } { + if let Some(nt_path) = nt_path.strip_prefix(drive_path) { + // Reserve approximately enough space for the drive + path. + let mut path = Vec::with_capacity(r"\\?\C:".len() + nt_path.len()); + // Create a verbatim drive root (e.g. \\?\D:) + let mut verbatim_root = *br#"\\?\C:"#; + verbatim_root[4] = letter; + path.extend_from_slice(&verbatim_root); + path.extend(OsString::from_wide(nt_path).into_encoded_bytes()); + // SAFETY: All characters are either in the ASCII range (the prefix) + // or else came from an OsString. + unsafe { + return Some(OsString::from_encoded_bytes_unchecked(path).into()); + } + } + } + } + None + }, + ) + .ok() + .flatten() +} + pub fn canonicalize(p: &WCStr) -> io::Result { let mut opts = OpenOptions::new(); // No read or write permissions are necessary diff --git a/library/std/src/sys/fs/windows/tests.rs b/library/std/src/sys/fs/windows/tests.rs new file mode 100644 index 0000000000000..b53dfddecf627 --- /dev/null +++ b/library/std/src/sys/fs/windows/tests.rs @@ -0,0 +1,21 @@ +use super::{get_path_canonical, get_path_fallback}; +use crate::env; +use crate::fs::{File, canonicalize}; +use crate::os::windows::io::AsRawHandle; +use crate::test_helpers::tmpdir; + +#[test] +/// Test that `get_path_canonical` and `get_path_fallback` return the exact same path. +fn canonicalize_fallback() { + let t = tmpdir(); + let fname = t.join("hello.txt"); + // This test may break if run in an environment that requires the fallback. + // So skip it if not in CI. + if env::var_os("CI").is_none() && canonicalize(&fname).is_err() { + return; + } + let f = File::create(fname).unwrap(); + let canonical = get_path_canonical(f.as_raw_handle()).unwrap(); + let fallback = get_path_fallback(f.as_raw_handle()).unwrap(); + assert_eq!(canonical, fallback); +} diff --git a/library/std/src/sys/pal/windows/api.rs b/library/std/src/sys/pal/windows/api.rs index 25a6c2d7d8eda..c3494bf9aa4e6 100644 --- a/library/std/src/sys/pal/windows/api.rs +++ b/library/std/src/sys/pal/windows/api.rs @@ -364,3 +364,42 @@ pub macro unicode_str { ) } } + +/// Returns a list of enabled drive letters. +/// +/// This is a wrapper around [`GetLogicalDrives`]. +/// Each letter is returned as an ascii byte. +/// +/// [`GetLogicalDrives`]: (https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives) +pub fn get_logical_drives() -> impl Iterator { + // SAFETY: `GetLogicalDrives` only returns information. + let drives = unsafe { c::GetLogicalDrives() }; + (b'A'..=b'Z').filter(move |letter| drives >> (letter - b'A') & 1 == 1) +} + +/// Get the NT path a device name points to. +/// +/// # Safety +/// +/// `device_name` must be null-terminated. +// FIXME: Use a null-terminated wide string type to assert validity, similar to CStr. +// Then this function can be safe. +pub unsafe fn query_dos_device<'a>( + device_name: &[u16], + buffer: &'a mut [u16], +) -> Option<&'a [u16]> { + let device_ptr = device_name.as_ptr(); + let buffer_ptr = buffer.as_mut_ptr(); + let buffer_len = buffer.len().try_into().ok()?; + // SAFETY: `device_ptr` points to a null-terminated u16 string. + // `buffer_ptr` is writeable up to buffer_len u16s. + let result = unsafe { c::QueryDosDeviceW(device_ptr, buffer_ptr, buffer_len) } as usize; + if result > 0 { + // QueryDosDeviceW returns a list of null-terminated strings where the list itself is also null-terminated + // In the case where you pass a device name (which we always) it only returns one string. + // Therefore to get the string we trim off both the list null termination and the string null termination. + Some(buffer[..result].trim_suffix(&[0, 0])) + } else { + None + } +} diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index a0b2126af9a58..c4c4ac8c7d06e 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -2181,6 +2181,7 @@ GETFINALPATHNAMEBYHANDLE_FLAGS GetFinalPathNameByHandleW GetFullPathNameW GetLastError +GetLogicalDrives GetModuleFileNameW GetModuleHandleA GetModuleHandleExW @@ -2354,6 +2355,7 @@ PROFILE_KERNEL PROFILE_SERVER PROFILE_USER PROGRESS_CONTINUE +QueryDosDeviceW QueryPerformanceCounter QueryPerformanceFrequency READ_CONTROL @@ -2508,6 +2510,7 @@ UpdateProcThreadAttribute VOLUME_NAME_DOS VOLUME_NAME_GUID VOLUME_NAME_NONE +VOLUME_NAME_NT WAIT_ABANDONED WAIT_ABANDONED_0 WAIT_FAILED diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index 9c6f593e1e108..8098e1eb32ddf 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -54,6 +54,7 @@ windows_link::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FI windows_link::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32); windows_link::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32); windows_link::link!("kernel32.dll" "system" fn GetLastError() -> WIN32_ERROR); +windows_link::link!("kernel32.dll" "system" fn GetLogicalDrives() -> u32); windows_link::link!("kernel32.dll" "system" fn GetModuleFileNameW(hmodule : HMODULE, lpfilename : PWSTR, nsize : u32) -> u32); windows_link::link!("kernel32.dll" "system" fn GetModuleHandleA(lpmodulename : PCSTR) -> HMODULE); windows_link::link!("kernel32.dll" "system" fn GetModuleHandleExW(dwflags : u32, lpmodulename : PCWSTR, phmodule : *mut HMODULE) -> BOOL); @@ -83,6 +84,7 @@ windows_link::link!("ntdll.dll" "system" fn NtReadFile(filehandle : HANDLE, even windows_link::link!("ntdll.dll" "system" fn NtSetInformationFile(filehandle : HANDLE, iostatusblock : *mut IO_STATUS_BLOCK, fileinformation : *const core::ffi::c_void, length : u32, fileinformationclass : FILE_INFORMATION_CLASS) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtWriteFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *const core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); windows_link::link!("advapi32.dll" "system" fn OpenProcessToken(processhandle : HANDLE, desiredaccess : TOKEN_ACCESS_MASK, tokenhandle : *mut HANDLE) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn QueryDosDeviceW(lpdevicename : PCWSTR, lptargetpath : PWSTR, ucchmax : u32) -> u32); windows_link::link!("kernel32.dll" "system" fn QueryPerformanceCounter(lpperformancecount : *mut i64) -> BOOL); windows_link::link!("kernel32.dll" "system" fn QueryPerformanceFrequency(lpfrequency : *mut i64) -> BOOL); windows_link::link!("kernel32.dll" "system" fn ReadConsoleW(hconsoleinput : HANDLE, lpbuffer : *mut core::ffi::c_void, nnumberofcharstoread : u32, lpnumberofcharsread : *mut u32, pinputcontrol : *const CONSOLE_READCONSOLE_CONTROL) -> BOOL); @@ -3411,6 +3413,7 @@ impl Default for UNICODE_STRING { pub const VOLUME_NAME_DOS: GETFINALPATHNAMEBYHANDLE_FLAGS = 0u32; pub const VOLUME_NAME_GUID: GETFINALPATHNAMEBYHANDLE_FLAGS = 1u32; pub const VOLUME_NAME_NONE: GETFINALPATHNAMEBYHANDLE_FLAGS = 4u32; +pub const VOLUME_NAME_NT: GETFINALPATHNAMEBYHANDLE_FLAGS = 2u32; pub const WAIT_ABANDONED: WAIT_EVENT = 128u32; pub const WAIT_ABANDONED_0: WAIT_EVENT = 128u32; pub type WAIT_EVENT = u32; @@ -3701,4 +3704,4 @@ pub struct WSADATA { } #[cfg(target_arch = "arm")] pub enum CONTEXT {} -// ignore-tidy-file-filelength +// ignore-tidy-filelength From 29776c0c2639e33411906f75c3b90674165f1060 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 28 Aug 2026 19:41:25 +0000 Subject: [PATCH 23/23] Fix tidy directive in windows bindings --- library/std/src/sys/pal/windows/c/windows_sys.rs | 2 +- src/tools/generate-windows-sys/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index 8098e1eb32ddf..c3c5e193e41f1 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -3704,4 +3704,4 @@ pub struct WSADATA { } #[cfg(target_arch = "arm")] pub enum CONTEXT {} -// ignore-tidy-filelength +// ignore-tidy-file-filelength diff --git a/src/tools/generate-windows-sys/src/main.rs b/src/tools/generate-windows-sys/src/main.rs index 9b1d62f14bb7b..e51340af9a95c 100644 --- a/src/tools/generate-windows-sys/src/main.rs +++ b/src/tools/generate-windows-sys/src/main.rs @@ -33,7 +33,7 @@ fn main() -> Result<(), Box> { let mut f = std::fs::File::options().append(true).open("windows_sys.rs")?; f.write_all(ARM32_SHIM.as_bytes())?; - writeln!(&mut f, "// ignore-tidy-filelength")?; + writeln!(&mut f, "// ignore-tidy-file-filelength")?; Ok(()) }