From 13ed4a381229235791618d091ec4d0c729a5b9fc Mon Sep 17 00:00:00 2001 From: Kivooeo Date: Sat, 6 Sep 2025 15:45:09 +0000 Subject: [PATCH 01/35] uncommented u64 impl --- library/core/src/num/bignum.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/core/src/num/bignum.rs b/library/core/src/num/bignum.rs index e33f58197bba5..1decbc2a04673 100644 --- a/library/core/src/num/bignum.rs +++ b/library/core/src/num/bignum.rs @@ -59,8 +59,7 @@ impl_full_ops! { u8: add(intrinsics::u8_add_with_overflow), mul/div(u16); u16: add(intrinsics::u16_add_with_overflow), mul/div(u32); u32: add(intrinsics::u32_add_with_overflow), mul/div(u64); - // See RFC #521 for enabling this. - // u64: add(intrinsics::u64_add_with_overflow), mul/div(u128); + u64: add(intrinsics::u64_add_with_overflow), mul/div(u128); } /// Table of powers of 5 representable in digits. Specifically, the largest {u8, u16, u32} value From 1e772f9681d4828cb10e0df3a37fc18594bfe992 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 2 Oct 2025 12:09:54 +0200 Subject: [PATCH 02/35] check for empty heads after adding parent --- compiler/rustc_type_ir/src/search_graph/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 7aa58d096d5c0..6d3da27acf9bd 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -969,10 +969,6 @@ impl, X: Cx> SearchGraph { return true; }; - let Some(new_highest_head_index) = heads.opt_highest_cycle_head_index() else { - return false; - }; - // We're rebasing an entry `e` over a head `p`. This head // has a number of own heads `h` it depends on. // @@ -1046,6 +1042,10 @@ impl, X: Cx> SearchGraph { }; } + let Some(new_highest_head_index) = heads.opt_highest_cycle_head_index() else { + return false; + }; + // We now care about the path from the next highest cycle head to the // provisional cache entry. *path_from_head = path_from_head.extend(Self::cycle_path_kind( From 36283bcb7e37b6e1cdf0c971f514a8212164a593 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 2 Oct 2025 12:10:58 +0200 Subject: [PATCH 03/35] fix `RebaseReason::Ambiguity` we should not get the certainty from the current result of the provisional cache entry --- .../src/solve/search_graph.rs | 14 +++--- compiler/rustc_type_ir/src/interner.rs | 3 +- .../rustc_type_ir/src/search_graph/mod.rs | 26 +++++++---- .../forced_ambiguity-use-head-maybe-cause.rs | 44 +++++++++++++++++++ ...rced_ambiguity-use-head-maybe-cause.stderr | 27 ++++++++++++ 5 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.rs create mode 100644 tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.stderr diff --git a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs index 109c8476ccb16..ea45d50969909 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -99,19 +99,23 @@ where response_no_constraints(cx, input, Certainty::overflow(false)) } - fn is_ambiguous_result(result: QueryResult) -> bool { - result.is_ok_and(|response| { - has_no_inference_or_external_constraints(response) + fn is_ambiguous_result(result: QueryResult) -> Option { + result.ok().and_then(|response| { + if has_no_inference_or_external_constraints(response) && matches!(response.value.certainty, Certainty::Maybe { .. }) + { + Some(response.value.certainty) + } else { + None + } }) } fn propagate_ambiguity( cx: I, for_input: CanonicalInput, - from_result: QueryResult, + certainty: Certainty, ) -> QueryResult { - let certainty = from_result.unwrap().value.certainty; response_no_constraints(cx, for_input, certainty) } diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 886d1a78bcbfd..322be31f4cb10 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -11,7 +11,7 @@ use crate::inherent::*; use crate::ir_print::IrPrint; use crate::lang_items::{SolverAdtLangItem, SolverLangItem, SolverTraitLangItem}; use crate::relate::Relate; -use crate::solve::{CanonicalInput, ExternalConstraintsData, QueryResult, inspect}; +use crate::solve::{CanonicalInput, Certainty, ExternalConstraintsData, QueryResult, inspect}; use crate::visit::{Flags, TypeVisitable}; use crate::{self as ty, CanonicalParamEnvCacheEntry, search_graph}; @@ -548,6 +548,7 @@ impl CollectAndApply for Result { impl search_graph::Cx for I { type Input = CanonicalInput; type Result = QueryResult; + type AmbiguityInfo = Certainty; type DepNodeIndex = I::DepNodeIndex; type Tracked = I::Tracked; diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 6d3da27acf9bd..ad8b643d976c7 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -40,6 +40,7 @@ pub use global_cache::GlobalCache; pub trait Cx: Copy { type Input: Debug + Eq + Hash + Copy; type Result: Debug + Eq + Hash + Copy; + type AmbiguityInfo: Debug + Eq + Hash + Copy; type DepNodeIndex; type Tracked: Debug; @@ -96,11 +97,13 @@ pub trait Delegate: Sized { input: ::Input, ) -> ::Result; - fn is_ambiguous_result(result: ::Result) -> bool; + fn is_ambiguous_result( + result: ::Result, + ) -> Option<::AmbiguityInfo>; fn propagate_ambiguity( cx: Self::Cx, for_input: ::Input, - from_result: ::Result, + ambiguity_info: ::AmbiguityInfo, ) -> ::Result; fn compute_goal( @@ -913,9 +916,9 @@ impl, X: Cx> SearchGraph { /// heads from the stack. This may not necessarily mean that we've actually /// reached a fixpoint for that cycle head, which impacts the way we rebase /// provisional cache entries. -enum RebaseReason { +enum RebaseReason { NoCycleUsages, - Ambiguity, + Ambiguity(X::AmbiguityInfo), Overflow, /// We've actually reached a fixpoint. /// @@ -951,7 +954,7 @@ impl, X: Cx> SearchGraph { &mut self, cx: X, stack_entry: &StackEntry, - rebase_reason: RebaseReason, + rebase_reason: RebaseReason, ) { let popped_head_index = self.stack.next_index(); #[allow(rustc::potential_query_instability)] @@ -1029,8 +1032,8 @@ impl, X: Cx> SearchGraph { // is not actually equal to the final provisional result. We // need to discard the provisional cache entry in this case. RebaseReason::NoCycleUsages => return false, - RebaseReason::Ambiguity => { - *result = D::propagate_ambiguity(cx, input, *result); + RebaseReason::Ambiguity(info) => { + *result = D::propagate_ambiguity(cx, input, info); } RebaseReason::Overflow => *result = D::fixpoint_overflow_result(cx, input), RebaseReason::ReachedFixpoint(None) => {} @@ -1268,6 +1271,7 @@ impl, X: Cx> SearchGraph { } /// Whether we've reached a fixpoint when evaluating a cycle head. + #[instrument(level = "trace", skip(self, stack_entry), ret)] fn reached_fixpoint( &mut self, stack_entry: &StackEntry, @@ -1355,8 +1359,12 @@ impl, X: Cx> SearchGraph { // As we only get to this branch if we haven't yet reached a fixpoint, // we also taint all provisional cache entries which depend on the // current goal. - if D::is_ambiguous_result(result) { - self.rebase_provisional_cache_entries(cx, &stack_entry, RebaseReason::Ambiguity); + if let Some(info) = D::is_ambiguous_result(result) { + self.rebase_provisional_cache_entries( + cx, + &stack_entry, + RebaseReason::Ambiguity(info), + ); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); }; diff --git a/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.rs b/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.rs new file mode 100644 index 0000000000000..aa5893d621cdb --- /dev/null +++ b/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.rs @@ -0,0 +1,44 @@ +//@ compile-flags: -Znext-solver +#![feature(rustc_attrs)] +#![rustc_no_implicit_bounds] + +// A regression test making sure that when forcing dependent +// provisional cache entries to ambiguous, we use the `MaybeCause` +// of the cycle head. We ended up trying to use the current result +// of the provisional cache entry, which is incorrect and caused an +// ICE when trying to unwrap it. + +struct Root(T); +struct Head(T); +struct Error(T); +struct NotImplemented(T); + +#[rustc_coinductive] +trait Trait {} +impl Trait for Root +where + Head: Trait, +{} + +impl Trait for Head +where + Root: Trait, + T: Trait, // ambiguous +{} + +impl Trait for Head +where + Error: Trait, + NotImplemented: Trait, +{} + +impl Trait for Error +where + Head: Trait, + NotImplemented: Trait, +{} + +fn impls_trait() {} +fn main() { + impls_trait::>() //~ ERROR type annotations needed +} diff --git a/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.stderr b/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.stderr new file mode 100644 index 0000000000000..dd4049a66d0b4 --- /dev/null +++ b/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.stderr @@ -0,0 +1,27 @@ +error[E0283]: type annotations needed + --> $DIR/forced_ambiguity-use-head-maybe-cause.rs:43:19 + | +LL | impls_trait::>() + | ^^^^^^^ cannot infer type for struct `Head<_>` + | + = note: cannot satisfy `Head<_>: Trait` + = help: the trait `Trait` is implemented for `Head` +note: required for `Root<_>` to implement `Trait` + --> $DIR/forced_ambiguity-use-head-maybe-cause.rs:18:9 + | +LL | impl Trait for Root + | ^^^^^ ^^^^^^^ +LL | where +LL | Head: Trait, + | ----- unsatisfied trait bound introduced here + = note: 8 redundant requirements hidden + = note: required for `Root<_>` to implement `Trait` +note: required by a bound in `impls_trait` + --> $DIR/forced_ambiguity-use-head-maybe-cause.rs:41:19 + | +LL | fn impls_trait() {} + | ^^^^^ required by this bound in `impls_trait` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0283`. From db3c9b640e8adabbee20e212f9ebb473513bad8b Mon Sep 17 00:00:00 2001 From: jackh726 Date: Tue, 30 Sep 2025 04:32:19 +0000 Subject: [PATCH 04/35] When computing opaque types in the next solver, take an initial pass pre-fallback where we equate hidden types but do not report errors --- compiler/rustc_hir_typeck/src/lib.rs | 6 ++ compiler/rustc_hir_typeck/src/opaque_types.rs | 57 ++++++++++++++++--- .../hidden-types-equate-before-fallback.rs | 27 +++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 9f5a85b692642..a615ac9d912dd 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -219,6 +219,12 @@ fn typeck_with_inspect<'tcx>( // the future. fcx.check_repeat_exprs(); + // We need to handle opaque types before emitting ambiguity errors as applying + // defining uses may guide type inference. + if fcx.next_trait_solver() { + fcx.try_handle_opaque_type_uses_next(); + } + fcx.type_inference_fallback(); // Even though coercion casts provide type hints, we check casts after fallback for diff --git a/compiler/rustc_hir_typeck/src/opaque_types.rs b/compiler/rustc_hir_typeck/src/opaque_types.rs index 4c1fe69405e91..9b5f26cefff79 100644 --- a/compiler/rustc_hir_typeck/src/opaque_types.rs +++ b/compiler/rustc_hir_typeck/src/opaque_types.rs @@ -22,6 +22,29 @@ impl<'tcx> FnCtxt<'_, 'tcx> { /// inference variables. /// /// It then uses these defining uses to guide inference for all other uses. + /// + /// Unlike `handle_opaque_type_uses_next`, this does not report errors. + pub(super) fn try_handle_opaque_type_uses_next(&mut self) { + // We clone the opaques instead of stealing them here as they are still used for + // normalization in the next generation trait solver. + let mut opaque_types: Vec<_> = self.infcx.clone_opaque_types(); + for entry in &mut opaque_types { + *entry = self.resolve_vars_if_possible(*entry); + } + debug!(?opaque_types); + + self.compute_definition_site_hidden_types(&opaque_types, true); + self.apply_definition_site_hidden_types(&opaque_types); + } + + /// This takes all the opaque type uses during HIR typeck. It first computes + /// the concrete hidden type by iterating over all defining uses. + /// + /// A use during HIR typeck is defining if all non-lifetime arguments are + /// unique generic parameters and the hidden type does not reference any + /// inference variables. + /// + /// It then uses these defining uses to guide inference for all other uses. #[instrument(level = "debug", skip(self))] pub(super) fn handle_opaque_type_uses_next(&mut self) { // We clone the opaques instead of stealing them here as they are still used for @@ -35,7 +58,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } debug!(?opaque_types); - self.compute_definition_site_hidden_types(&opaque_types); + self.compute_definition_site_hidden_types(&opaque_types, false); self.apply_definition_site_hidden_types(&opaque_types); } } @@ -74,6 +97,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { fn compute_definition_site_hidden_types( &mut self, opaque_types: &[(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)], + first_pass: bool, ) { let tcx = self.tcx; let TypingMode::Analysis { defining_opaque_types_and_generators } = self.typing_mode() @@ -94,12 +118,22 @@ impl<'tcx> FnCtxt<'_, 'tcx> { continue; } - usage_kind.merge(self.consider_opaque_type_use(opaque_type_key, hidden_type)); + usage_kind.merge(self.consider_opaque_type_use( + opaque_type_key, + hidden_type, + first_pass, + )); if let UsageKind::HasDefiningUse = usage_kind { break; } } + // If this the first pass (`try_handle_opaque_type_uses_next`), + // then do not report any errors. + if first_pass { + continue; + } + let guar = match usage_kind { UsageKind::None => { if let Some(guar) = self.tainted_by_errors() { @@ -152,6 +186,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { &mut self, opaque_type_key: OpaqueTypeKey<'tcx>, hidden_type: OpaqueHiddenType<'tcx>, + first_pass: bool, ) -> UsageKind<'tcx> { if let Err(err) = opaque_type_has_defining_use_args( &self, @@ -199,7 +234,13 @@ impl<'tcx> FnCtxt<'_, 'tcx> { .borrow_mut() .hidden_types .insert(opaque_type_key.def_id, hidden_type); - assert!(prev.is_none()); + + // We do want to insert opaque types the first pass, because we want to + // equate them. So, the second pass (where we report errors) will have + // a hidden type inserted. + if first_pass { + assert!(prev.is_none()); + } UsageKind::HasDefiningUse } @@ -209,10 +250,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> { ) { let tcx = self.tcx; for &(key, hidden_type) in opaque_types { - let expected = *self.typeck_results.borrow_mut().hidden_types.get(&key.def_id).unwrap(); - - let expected = EarlyBinder::bind(expected.ty).instantiate(tcx, key.args); - self.demand_eqtype(hidden_type.span, expected, hidden_type.ty); + // On the first pass to this function, some opaque types may not + // have a hidden type assigned. + if let Some(expected) = self.typeck_results.borrow_mut().hidden_types.get(&key.def_id) { + let expected = EarlyBinder::bind(expected.ty).instantiate(tcx, key.args); + self.demand_eqtype(hidden_type.span, expected, hidden_type.ty); + } } } diff --git a/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs b/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs new file mode 100644 index 0000000000000..9fdeecfa1a530 --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs @@ -0,0 +1,27 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +// Regression test for trait-system-refactor-initiative#240. Hidden types should +// equate *before* inference var fallback, otherwise we can get mismatched types. + + +#[derive(Clone, Copy)] +struct FileSystem; +impl FileSystem { + fn build(self, commands: T) -> Option { + match false { + true => Some(commands), + false => { + drop(match self.build::<_>(commands) { + Some(x) => x, + None => return None, + }); + panic!() + }, + } + } +} + +fn main() {} From 9a5cef155f5bc337563bdd09ad2f6ea0c8c2f1c7 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Wed, 1 Oct 2025 22:51:17 +0000 Subject: [PATCH 05/35] Cleanup opaque_type computation a bit --- compiler/rustc_hir_typeck/src/opaque_types.rs | 103 ++++++++++-------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/opaque_types.rs b/compiler/rustc_hir_typeck/src/opaque_types.rs index 9b5f26cefff79..0c8edbac4f2b5 100644 --- a/compiler/rustc_hir_typeck/src/opaque_types.rs +++ b/compiler/rustc_hir_typeck/src/opaque_types.rs @@ -4,6 +4,7 @@ use rustc_middle::ty::{ self, DefiningScopeKind, EarlyBinder, OpaqueHiddenType, OpaqueTypeKey, TypeVisitableExt, TypingMode, }; +use rustc_span::ErrorGuaranteed; use rustc_trait_selection::error_reporting::infer::need_type_info::TypeAnnotationNeeded; use rustc_trait_selection::opaque_types::{ NonDefiningUseReason, opaque_type_has_defining_use_args, report_item_does_not_constrain_error, @@ -24,6 +25,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { /// It then uses these defining uses to guide inference for all other uses. /// /// Unlike `handle_opaque_type_uses_next`, this does not report errors. + #[instrument(level = "debug", skip(self))] pub(super) fn try_handle_opaque_type_uses_next(&mut self) { // We clone the opaques instead of stealing them here as they are still used for // normalization in the next generation trait solver. @@ -34,7 +36,6 @@ impl<'tcx> FnCtxt<'_, 'tcx> { debug!(?opaque_types); self.compute_definition_site_hidden_types(&opaque_types, true); - self.apply_definition_site_hidden_types(&opaque_types); } /// This takes all the opaque type uses during HIR typeck. It first computes @@ -59,22 +60,29 @@ impl<'tcx> FnCtxt<'_, 'tcx> { debug!(?opaque_types); self.compute_definition_site_hidden_types(&opaque_types, false); - self.apply_definition_site_hidden_types(&opaque_types); } } +#[derive(Copy, Clone, Debug)] enum UsageKind<'tcx> { None, NonDefiningUse(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>), UnconstrainedHiddenType(OpaqueHiddenType<'tcx>), - HasDefiningUse, + HasDefiningUse(OpaqueHiddenType<'tcx>), + // `type_of_opaque_hir_typeck` reported an error + HasError(ErrorGuaranteed), } impl<'tcx> UsageKind<'tcx> { fn merge(&mut self, other: UsageKind<'tcx>) { match (&*self, &other) { - (UsageKind::HasDefiningUse, _) | (_, UsageKind::None) => unreachable!(), + (UsageKind::HasDefiningUse(_), _) | (_, UsageKind::None) => unreachable!(), (UsageKind::None, _) => *self = other, + // If `type_of_opaque_hir_typeck` reported an error, then the hidden + // type is an error, but we also want to still report errors for + // remaining hidden types (really just normalization errors). + (UsageKind::HasError(_), _) => {} + (_, UsageKind::HasError(_)) => *self = other, // When mergining non-defining uses, prefer earlier ones. This means // the error happens as early as possible. ( @@ -87,7 +95,7 @@ impl<'tcx> UsageKind<'tcx> { // intended to be defining. ( UsageKind::NonDefiningUse(..) | UsageKind::UnconstrainedHiddenType(..), - UsageKind::UnconstrainedHiddenType(..) | UsageKind::HasDefiningUse, + UsageKind::UnconstrainedHiddenType(..) | UsageKind::HasDefiningUse(_), ) => *self = other, } } @@ -112,22 +120,55 @@ impl<'tcx> FnCtxt<'_, 'tcx> { _ => unreachable!("not opaque or generator: {def_id:?}"), } + // We do actually need to check this the second pass (we can't just + // store this), because we can go from `UnconstrainedHiddenType` to + // `HasDefiningUse` (because of fallback) let mut usage_kind = UsageKind::None; for &(opaque_type_key, hidden_type) in opaque_types { if opaque_type_key.def_id != def_id { continue; } - usage_kind.merge(self.consider_opaque_type_use( - opaque_type_key, - hidden_type, - first_pass, - )); - if let UsageKind::HasDefiningUse = usage_kind { + usage_kind.merge(self.consider_opaque_type_use(opaque_type_key, hidden_type)); + + if let UsageKind::HasDefiningUse(..) = usage_kind { break; } } + if let UsageKind::HasDefiningUse(first_use) = usage_kind { + for &(opaque_type_key, hidden_type) in opaque_types { + if opaque_type_key.def_id != def_id { + continue; + } + + let expected = + EarlyBinder::bind(first_use.ty).instantiate(tcx, opaque_type_key.args); + self.demand_eqtype(hidden_type.span, expected, hidden_type.ty); + } + } + + match usage_kind { + UsageKind::HasDefiningUse(hidden_type) => { + let prev = + self.typeck_results.borrow_mut().hidden_types.insert(def_id, hidden_type); + + // We do want to insert opaque types the first pass, because + // we want to equate them. So, the second pass (where we + // report errors) may have a hidden type inserted. + if first_pass { + assert!(prev.is_none()); + } + } + UsageKind::HasError(guar) => { + self.typeck_results + .borrow_mut() + .hidden_types + .insert(def_id, OpaqueHiddenType::new_error(self.tcx, guar)); + } + _ => {} + } + // If this the first pass (`try_handle_opaque_type_uses_next`), // then do not report any errors. if first_pass { @@ -135,6 +176,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } let guar = match usage_kind { + UsageKind::HasDefiningUse(_) | UsageKind::HasError(_) => continue, UsageKind::None => { if let Some(guar) = self.tainted_by_errors() { guar @@ -171,7 +213,6 @@ impl<'tcx> FnCtxt<'_, 'tcx> { .emit() } } - UsageKind::HasDefiningUse => continue, }; self.typeck_results @@ -182,11 +223,11 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } } + #[tracing::instrument(skip(self), ret)] fn consider_opaque_type_use( &mut self, opaque_type_key: OpaqueTypeKey<'tcx>, hidden_type: OpaqueHiddenType<'tcx>, - first_pass: bool, ) -> UsageKind<'tcx> { if let Err(err) = opaque_type_has_defining_use_args( &self, @@ -196,11 +237,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { ) { match err { NonDefiningUseReason::Tainted(guar) => { - self.typeck_results.borrow_mut().hidden_types.insert( - opaque_type_key.def_id, - OpaqueHiddenType::new_error(self.tcx, guar), - ); - return UsageKind::HasDefiningUse; + return UsageKind::HasError(guar); } _ => return UsageKind::NonDefiningUse(opaque_type_key, hidden_type), }; @@ -228,35 +265,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { self.tcx, DefiningScopeKind::HirTypeck, ); - - let prev = self - .typeck_results - .borrow_mut() - .hidden_types - .insert(opaque_type_key.def_id, hidden_type); - - // We do want to insert opaque types the first pass, because we want to - // equate them. So, the second pass (where we report errors) will have - // a hidden type inserted. - if first_pass { - assert!(prev.is_none()); - } - UsageKind::HasDefiningUse - } - - fn apply_definition_site_hidden_types( - &mut self, - opaque_types: &[(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)], - ) { - let tcx = self.tcx; - for &(key, hidden_type) in opaque_types { - // On the first pass to this function, some opaque types may not - // have a hidden type assigned. - if let Some(expected) = self.typeck_results.borrow_mut().hidden_types.get(&key.def_id) { - let expected = EarlyBinder::bind(expected.ty).instantiate(tcx, key.args); - self.demand_eqtype(hidden_type.span, expected, hidden_type.ty); - } - } + UsageKind::HasDefiningUse(hidden_type) } /// We may in theory add further uses of an opaque after cloning the opaque From c6c58e3fff86747f8d8d67d2f83541099465ca88 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Wed, 1 Oct 2025 23:03:14 +0000 Subject: [PATCH 06/35] Remove UsageKind::HasError --- compiler/rustc_hir_typeck/src/opaque_types.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/opaque_types.rs b/compiler/rustc_hir_typeck/src/opaque_types.rs index 0c8edbac4f2b5..8731a16da5027 100644 --- a/compiler/rustc_hir_typeck/src/opaque_types.rs +++ b/compiler/rustc_hir_typeck/src/opaque_types.rs @@ -4,7 +4,6 @@ use rustc_middle::ty::{ self, DefiningScopeKind, EarlyBinder, OpaqueHiddenType, OpaqueTypeKey, TypeVisitableExt, TypingMode, }; -use rustc_span::ErrorGuaranteed; use rustc_trait_selection::error_reporting::infer::need_type_info::TypeAnnotationNeeded; use rustc_trait_selection::opaque_types::{ NonDefiningUseReason, opaque_type_has_defining_use_args, report_item_does_not_constrain_error, @@ -69,8 +68,6 @@ enum UsageKind<'tcx> { NonDefiningUse(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>), UnconstrainedHiddenType(OpaqueHiddenType<'tcx>), HasDefiningUse(OpaqueHiddenType<'tcx>), - // `type_of_opaque_hir_typeck` reported an error - HasError(ErrorGuaranteed), } impl<'tcx> UsageKind<'tcx> { @@ -78,11 +75,6 @@ impl<'tcx> UsageKind<'tcx> { match (&*self, &other) { (UsageKind::HasDefiningUse(_), _) | (_, UsageKind::None) => unreachable!(), (UsageKind::None, _) => *self = other, - // If `type_of_opaque_hir_typeck` reported an error, then the hidden - // type is an error, but we also want to still report errors for - // remaining hidden types (really just normalization errors). - (UsageKind::HasError(_), _) => {} - (_, UsageKind::HasError(_)) => *self = other, // When mergining non-defining uses, prefer earlier ones. This means // the error happens as early as possible. ( @@ -160,12 +152,6 @@ impl<'tcx> FnCtxt<'_, 'tcx> { assert!(prev.is_none()); } } - UsageKind::HasError(guar) => { - self.typeck_results - .borrow_mut() - .hidden_types - .insert(def_id, OpaqueHiddenType::new_error(self.tcx, guar)); - } _ => {} } @@ -176,7 +162,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } let guar = match usage_kind { - UsageKind::HasDefiningUse(_) | UsageKind::HasError(_) => continue, + UsageKind::HasDefiningUse(_) => continue, UsageKind::None => { if let Some(guar) = self.tainted_by_errors() { guar @@ -237,7 +223,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { ) { match err { NonDefiningUseReason::Tainted(guar) => { - return UsageKind::HasError(guar); + return UsageKind::HasDefiningUse(OpaqueHiddenType::new_error(self.tcx, guar)); } _ => return UsageKind::NonDefiningUse(opaque_type_key, hidden_type), }; From a3fbae599c756c80c9ff42f83afbed65d06fe8c6 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Wed, 1 Oct 2025 23:07:10 +0000 Subject: [PATCH 07/35] Merge match and if --- compiler/rustc_hir_typeck/src/opaque_types.rs | 23 +++++++------------ .../hidden-types-equate-before-fallback.rs | 1 - 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/opaque_types.rs b/compiler/rustc_hir_typeck/src/opaque_types.rs index 8731a16da5027..9d0e6d5abe181 100644 --- a/compiler/rustc_hir_typeck/src/opaque_types.rs +++ b/compiler/rustc_hir_typeck/src/opaque_types.rs @@ -128,31 +128,24 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } } - if let UsageKind::HasDefiningUse(first_use) = usage_kind { + if let UsageKind::HasDefiningUse(ty) = usage_kind { for &(opaque_type_key, hidden_type) in opaque_types { if opaque_type_key.def_id != def_id { continue; } - let expected = - EarlyBinder::bind(first_use.ty).instantiate(tcx, opaque_type_key.args); + let expected = EarlyBinder::bind(ty.ty).instantiate(tcx, opaque_type_key.args); self.demand_eqtype(hidden_type.span, expected, hidden_type.ty); } - } - match usage_kind { - UsageKind::HasDefiningUse(hidden_type) => { - let prev = - self.typeck_results.borrow_mut().hidden_types.insert(def_id, hidden_type); + let prev = self.typeck_results.borrow_mut().hidden_types.insert(def_id, ty); - // We do want to insert opaque types the first pass, because - // we want to equate them. So, the second pass (where we - // report errors) may have a hidden type inserted. - if first_pass { - assert!(prev.is_none()); - } + // We do want to insert opaque types the first pass, because + // we want to equate them. So, the second pass (where we + // report errors) may have a hidden type inserted. + if first_pass { + assert!(prev.is_none()); } - _ => {} } // If this the first pass (`try_handle_opaque_type_uses_next`), diff --git a/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs b/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs index 9fdeecfa1a530..dd5ff26da4f7f 100644 --- a/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs +++ b/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs @@ -6,7 +6,6 @@ // Regression test for trait-system-refactor-initiative#240. Hidden types should // equate *before* inference var fallback, otherwise we can get mismatched types. - #[derive(Clone, Copy)] struct FileSystem; impl FileSystem { From 283ad6699cb806f6b5d38efc13a40dbd9a3321ca Mon Sep 17 00:00:00 2001 From: Jack Huey <31162821+jackh726@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:56:33 -0400 Subject: [PATCH 08/35] Fix comment, change first_pass to error_on_missing_defining_use, remove the assert in favor over a comment, and change to & for &mut --- compiler/rustc_hir_typeck/src/opaque_types.rs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/opaque_types.rs b/compiler/rustc_hir_typeck/src/opaque_types.rs index 9d0e6d5abe181..a46e1a5f75fa7 100644 --- a/compiler/rustc_hir_typeck/src/opaque_types.rs +++ b/compiler/rustc_hir_typeck/src/opaque_types.rs @@ -26,15 +26,15 @@ impl<'tcx> FnCtxt<'_, 'tcx> { /// Unlike `handle_opaque_type_uses_next`, this does not report errors. #[instrument(level = "debug", skip(self))] pub(super) fn try_handle_opaque_type_uses_next(&mut self) { - // We clone the opaques instead of stealing them here as they are still used for - // normalization in the next generation trait solver. + // We clone the opaques instead of stealing them here as we still need + // to use them after fallback. let mut opaque_types: Vec<_> = self.infcx.clone_opaque_types(); for entry in &mut opaque_types { *entry = self.resolve_vars_if_possible(*entry); } debug!(?opaque_types); - self.compute_definition_site_hidden_types(&opaque_types, true); + self.compute_definition_site_hidden_types(&opaque_types, false); } /// This takes all the opaque type uses during HIR typeck. It first computes @@ -58,7 +58,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } debug!(?opaque_types); - self.compute_definition_site_hidden_types(&opaque_types, false); + self.compute_definition_site_hidden_types(&opaque_types, true); } } @@ -97,7 +97,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { fn compute_definition_site_hidden_types( &mut self, opaque_types: &[(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)], - first_pass: bool, + error_on_missing_defining_use: bool, ) { let tcx = self.tcx; let TypingMode::Analysis { defining_opaque_types_and_generators } = self.typing_mode() @@ -138,19 +138,16 @@ impl<'tcx> FnCtxt<'_, 'tcx> { self.demand_eqtype(hidden_type.span, expected, hidden_type.ty); } - let prev = self.typeck_results.borrow_mut().hidden_types.insert(def_id, ty); - - // We do want to insert opaque types the first pass, because - // we want to equate them. So, the second pass (where we - // report errors) may have a hidden type inserted. - if first_pass { - assert!(prev.is_none()); - } + // Being explicit here: it may be possible that we in a + // previous call to this function we did an insert, but this + // should be just fine, since they all get equated anyways and + // we shouldn't ever go from `HasDefiningUse` to anyway else. + let _ = self.typeck_results.borrow_mut().hidden_types.insert(def_id, ty); } // If this the first pass (`try_handle_opaque_type_uses_next`), // then do not report any errors. - if first_pass { + if !error_on_missing_defining_use { continue; } @@ -204,7 +201,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { #[tracing::instrument(skip(self), ret)] fn consider_opaque_type_use( - &mut self, + &self, opaque_type_key: OpaqueTypeKey<'tcx>, hidden_type: OpaqueHiddenType<'tcx>, ) -> UsageKind<'tcx> { From 8b178272c7f7c9df3e2b49d740614c3497de2fb8 Mon Sep 17 00:00:00 2001 From: Jack Huey <31162821+jackh726@users.noreply.github.com> Date: Sun, 5 Oct 2025 00:50:19 -0400 Subject: [PATCH 09/35] Remove unneeded get_hidden_type --- .../src/region_infer/opaque_types/mod.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs index 8d89f3e0d8700..fe9f9a37b862c 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -155,13 +155,6 @@ fn add_hidden_type<'tcx>( } } -fn get_hidden_type<'tcx>( - hidden_types: &DefinitionSiteHiddenTypes<'tcx>, - def_id: LocalDefId, -) -> Option>> { - hidden_types.0.get(&def_id).map(|ty| EarlyBinder::bind(*ty)) -} - #[derive(Debug)] struct DefiningUse<'tcx> { /// The opaque type using non NLL vars. This uses the actual @@ -501,7 +494,8 @@ pub(crate) fn apply_definition_site_hidden_types<'tcx>( let tcx = infcx.tcx; let mut errors = Vec::new(); for &(key, hidden_type) in opaque_types { - let Some(expected) = get_hidden_type(hidden_types, key.def_id) else { + let Some(expected) = hidden_types.0.get(&key.def_id).map(|ty| EarlyBinder::bind(*ty)) + else { if !tcx.use_typing_mode_borrowck() { if let ty::Alias(ty::Opaque, alias_ty) = hidden_type.ty.kind() && alias_ty.def_id == key.def_id.to_def_id() From 04c2724866e1c7ea1c4195475ba3539e01a131da Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Sat, 13 Sep 2025 08:51:04 -0400 Subject: [PATCH 10/35] constify basic Clone impls --- library/core/src/clone.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index 7f2a40f753fa6..06d2c93cc698f 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -575,7 +575,8 @@ mod impls { ($($t:ty)*) => { $( #[stable(feature = "rust1", since = "1.0.0")] - impl Clone for $t { + #[rustc_const_unstable(feature = "const_clone", issue = "142757")] + impl const Clone for $t { #[inline(always)] fn clone(&self) -> Self { *self @@ -593,7 +594,8 @@ mod impls { } #[unstable(feature = "never_type", issue = "35121")] - impl Clone for ! { + #[rustc_const_unstable(feature = "const_clone", issue = "142757")] + impl const Clone for ! { #[inline] fn clone(&self) -> Self { *self @@ -601,7 +603,8 @@ mod impls { } #[stable(feature = "rust1", since = "1.0.0")] - impl Clone for *const T { + #[rustc_const_unstable(feature = "const_clone", issue = "142757")] + impl const Clone for *const T { #[inline(always)] fn clone(&self) -> Self { *self @@ -609,7 +612,8 @@ mod impls { } #[stable(feature = "rust1", since = "1.0.0")] - impl Clone for *mut T { + #[rustc_const_unstable(feature = "const_clone", issue = "142757")] + impl const Clone for *mut T { #[inline(always)] fn clone(&self) -> Self { *self @@ -618,7 +622,8 @@ mod impls { /// Shared references can be cloned, but mutable references *cannot*! #[stable(feature = "rust1", since = "1.0.0")] - impl Clone for &T { + #[rustc_const_unstable(feature = "const_clone", issue = "142757")] + impl const Clone for &T { #[inline(always)] #[rustc_diagnostic_item = "noop_method_clone"] fn clone(&self) -> Self { From 1e6b444df7f1242ad9b0cc58620f101f34514734 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 7 Oct 2025 13:07:23 -0700 Subject: [PATCH 11/35] library: fs: Factor out a `file_time_to_timespec` function in preparation for reusing it --- library/std/src/sys/fs/unix.rs | 49 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 33a1e7ff5e40e..bcd5ea6494447 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1604,24 +1604,6 @@ impl File { } pub fn set_times(&self, times: FileTimes) -> io::Result<()> { - #[cfg(not(any( - target_os = "redox", - target_os = "espidf", - target_os = "horizon", - target_os = "nuttx", - )))] - let to_timespec = |time: Option| match time { - Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts), - Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_error!( - io::ErrorKind::InvalidInput, - "timestamp is too large to set as a file time", - )), - Some(_) => Err(io::const_error!( - io::ErrorKind::InvalidInput, - "timestamp is too small to set as a file time", - )), - None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }), - }; cfg_select! { any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { // Redox doesn't appear to support `UTIME_OMIT`. @@ -1639,17 +1621,17 @@ impl File { let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; if times.created.is_some() { - buf[num_times].write(to_timespec(times.created)?); + buf[num_times].write(file_time_to_timespec(times.created)?); num_times += 1; attrlist.commonattr |= libc::ATTR_CMN_CRTIME; } if times.modified.is_some() { - buf[num_times].write(to_timespec(times.modified)?); + buf[num_times].write(file_time_to_timespec(times.modified)?); num_times += 1; attrlist.commonattr |= libc::ATTR_CMN_MODTIME; } if times.accessed.is_some() { - buf[num_times].write(to_timespec(times.accessed)?); + buf[num_times].write(file_time_to_timespec(times.accessed)?); num_times += 1; attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; } @@ -1663,7 +1645,7 @@ impl File { Ok(()) } target_os = "android" => { - let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; // futimens requires Android API level 19 cvt(unsafe { weak!( @@ -1697,7 +1679,7 @@ impl File { return Ok(()); } } - let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; cvt(unsafe { libc::futimens(self.as_raw_fd(), times.as_ptr()) })?; Ok(()) } @@ -1705,6 +1687,27 @@ impl File { } } +#[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "nuttx", +)))] +fn file_time_to_timespec(time: Option) -> io::Result { + match time { + Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts), + Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_error!( + io::ErrorKind::InvalidInput, + "timestamp is too large to set as a file time", + )), + Some(_) => Err(io::const_error!( + io::ErrorKind::InvalidInput, + "timestamp is too small to set as a file time", + )), + None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }), + } +}; + impl DirBuilder { pub fn new() -> DirBuilder { DirBuilder { mode: 0o777 } From 1bf555c947296016d78de926e989d306b4cfde45 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 7 Oct 2025 13:18:38 -0700 Subject: [PATCH 12/35] library: fs: Factor out the Apple file time to attrlist code for reuse --- library/std/src/sys/fs/unix.rs | 74 +++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index bcd5ea6494447..bed9ea9139834 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1616,30 +1616,12 @@ impl File { )) } target_vendor = "apple" => { - let mut buf = [mem::MaybeUninit::::uninit(); 3]; - let mut num_times = 0; - let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; - attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; - if times.created.is_some() { - buf[num_times].write(file_time_to_timespec(times.created)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_CRTIME; - } - if times.modified.is_some() { - buf[num_times].write(file_time_to_timespec(times.modified)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_MODTIME; - } - if times.accessed.is_some() { - buf[num_times].write(file_time_to_timespec(times.accessed)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; - } + let ta = TimesAttrlist::from_times(×)?; cvt(unsafe { libc::fsetattrlist( self.as_raw_fd(), - (&raw const attrlist).cast::().cast_mut(), - buf.as_ptr().cast::().cast_mut(), - num_times * size_of::(), + ta.attrlist(), + ta.times_buf(), + ta.times_buf_size(), 0 ) })?; Ok(()) @@ -1706,7 +1688,53 @@ fn file_time_to_timespec(time: Option) -> io::Result )), None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }), } -}; +} + +#[cfg(target_vendor = "apple")] +struct TimesAttrlist { + buf: [mem::MaybeUninit; 3], + attrlist: libc::attrlist, + num_times: usize, +} + +#[cfg(target_vendor = "apple")] +impl TimesAttrlist { + fn from_times(times: &FileTimes) -> io::Result { + let mut this = Self { + buf: [mem::MaybeUninit::::uninit(); 3], + attrlist: unsafe { mem::zeroed() }, + num_times: 0, + }; + this.attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; + if times.created.is_some() { + this.buf[this.num_times].write(file_time_to_timespec(times.created)?); + this.num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_CRTIME; + } + if times.modified.is_some() { + this.buf[this.num_times].write(file_time_to_timespec(times.modified)?); + this.num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_MODTIME; + } + if times.accessed.is_some() { + this.buf[this.num_times].write(file_time_to_timespec(times.accessed)?); + this.num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; + } + } + + fn attrlist(&self) -> *mut libc::c_void { + (&raw const self.attrlist).cast::().cast_mut() + } + + fn times_buf(&self) -> *mut libc::c_void { + self.buf.as_ptr().cast::().cast_mut() + } + + fn times_buf_size(&self) -> usize { + self.num_times * size_of::() + } +} impl DirBuilder { pub fn new() -> DirBuilder { From 0355358e6cd106f771392eadae3c0e3f49b17e98 Mon Sep 17 00:00:00 2001 From: cyrgani Date: Wed, 8 Oct 2025 13:49:32 +0200 Subject: [PATCH 13/35] run zero-size assertion in `const {}` --- library/proc_macro/src/bridge/selfless_reify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/proc_macro/src/bridge/selfless_reify.rs b/library/proc_macro/src/bridge/selfless_reify.rs index b06434a5ffee2..a53550e0b9e0c 100644 --- a/library/proc_macro/src/bridge/selfless_reify.rs +++ b/library/proc_macro/src/bridge/selfless_reify.rs @@ -50,7 +50,7 @@ macro_rules! define_reify_functions { >(f: F) -> $(extern $abi)? fn($($arg_ty),*) -> $ret_ty { // FIXME(eddyb) describe the `F` type (e.g. via `type_name::`) once panic // formatting becomes possible in `const fn`. - assert!(size_of::() == 0, "selfless_reify: closure must be zero-sized"); + const { assert!(size_of::() == 0, "selfless_reify: closure must be zero-sized"); } $(extern $abi)? fn wrapper< $($($param,)*)? From e3d2016a2264b704a7f6f1c18e45ce21732b627b Mon Sep 17 00:00:00 2001 From: cyrgani Date: Wed, 8 Oct 2025 14:33:31 +0200 Subject: [PATCH 14/35] inline constants in generated `enum` `Encode` impls --- library/proc_macro/src/bridge/rpc.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/library/proc_macro/src/bridge/rpc.rs b/library/proc_macro/src/bridge/rpc.rs index 7f4f5fc3a97d5..6e5afe282d683 100644 --- a/library/proc_macro/src/bridge/rpc.rs +++ b/library/proc_macro/src/bridge/rpc.rs @@ -58,16 +58,11 @@ macro_rules! rpc_encode_decode { fn encode(self, w: &mut Writer, s: &mut S) { // HACK(eddyb): `Tag` enum duplicated between the // two impls as there's no other place to stash it. - #[allow(non_upper_case_globals)] - mod tag { - #[repr(u8)] enum Tag { $($variant),* } - - $(pub(crate) const $variant: u8 = Tag::$variant as u8;)* - } + #[repr(u8)] enum Tag { $($variant),* } match self { $($name::$variant $(($field))* => { - tag::$variant.encode(w, s); + (Tag::$variant as u8).encode(w, s); $($field.encode(w, s);)* })* } From 722545427f46d670228c345a03ab9c0c9cf74241 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 8 Oct 2025 16:39:53 +0800 Subject: [PATCH 15/35] Implement fs api set_times and set_times_nofollow --- library/std/src/fs.rs | 74 +++++++++ library/std/src/fs/tests.rs | 219 ++++++++++++++++++++++++++ library/std/src/sys/fs/hermit.rs | 8 + library/std/src/sys/fs/mod.rs | 8 + library/std/src/sys/fs/solid.rs | 11 ++ library/std/src/sys/fs/uefi.rs | 8 + library/std/src/sys/fs/unix.rs | 127 +++++++++++++++ library/std/src/sys/fs/unsupported.rs | 8 + library/std/src/sys/fs/vexos.rs | 8 + library/std/src/sys/fs/wasi.rs | 12 ++ library/std/src/sys/fs/windows.rs | 17 ++ 11 files changed, 500 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 28b2c7173d321..e97190e69d68f 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -387,6 +387,80 @@ pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result inner(path.as_ref(), contents.as_ref()) } +/// Changes the timestamps of the file or directory at the specified path. +/// +/// This function will attempt to set the access and modification times +/// to the times specified. If the path refers to a symbolic link, this function +/// will follow the link and change the timestamps of the target file. +/// +/// # Platform-specific behavior +/// +/// This function currently corresponds to the `utimensat` function on Unix platforms +/// and the `SetFileTime` function on Windows. +/// +/// # Errors +/// +/// This function will return an error if the user lacks permission to change timestamps on the +/// target file or symlink. It may also return an error if the OS does not support it. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(fs_set_times)] +/// use std::fs::{self, FileTimes}; +/// use std::time::SystemTime; +/// +/// fn main() -> std::io::Result<()> { +/// let now = SystemTime::now(); +/// let times = FileTimes::new() +/// .set_accessed(now) +/// .set_modified(now); +/// fs::set_times("foo.txt", times)?; +/// Ok(()) +/// } +/// ``` +#[unstable(feature = "fs_set_times", issue = "147455")] +pub fn set_times>(path: P, times: FileTimes) -> io::Result<()> { + fs_imp::set_times(path.as_ref(), times.0) +} + +/// Changes the timestamps of the file or symlink at the specified path. +/// +/// This function will attempt to set the access and modification times +/// to the times specified. Differ from `set_times`, if the path refers to a symbolic link, +/// this function will change the timestamps of the symlink itself, not the target file. +/// +/// # Platform-specific behavior +/// +/// This function currently corresponds to the `utimensat` function with `AT_SYMLINK_NOFOLLOW` +/// on Unix platforms and the `SetFileTime` function on Windows after opening the symlink. +/// +/// # Errors +/// +/// This function will return an error if the user lacks permission to change timestamps on the +/// target file or symlink. It may also return an error if the OS does not support it. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(fs_set_times)] +/// use std::fs::{self, FileTimes}; +/// use std::time::SystemTime; +/// +/// fn main() -> std::io::Result<()> { +/// let now = SystemTime::now(); +/// let times = FileTimes::new() +/// .set_accessed(now) +/// .set_modified(now); +/// fs::set_times_nofollow("symlink.txt", times)?; +/// Ok(()) +/// } +/// ``` +#[unstable(feature = "fs_set_times", issue = "147455")] +pub fn set_times_nofollow>(path: P, times: FileTimes) -> io::Result<()> { + fs_imp::set_times_nofollow(path.as_ref(), times.0) +} + #[stable(feature = "file_lock", since = "1.89.0")] impl error::Error for TryLockError {} diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index f8dfb0d633400..4d67ba9248998 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2226,3 +2226,222 @@ fn test_open_options_invalid_combinations() { assert_eq!(err.kind(), ErrorKind::InvalidInput); assert_eq!(err.to_string(), "must specify at least one of read, write, or append access"); } + +#[test] +fn test_fs_set_times() { + #[cfg(target_vendor = "apple")] + use crate::os::darwin::fs::FileTimesExt; + #[cfg(windows)] + use crate::os::windows::fs::FileTimesExt; + + let tmp = tmpdir(); + let path = tmp.join("foo"); + File::create(&path).unwrap(); + + let mut times = FileTimes::new(); + let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345); + let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321); + times = times.set_accessed(accessed).set_modified(modified); + + #[cfg(any(windows, target_vendor = "apple"))] + let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123); + #[cfg(any(windows, target_vendor = "apple"))] + { + times = times.set_created(created); + } + + match fs::set_times(&path, times) { + // Allow unsupported errors on platforms which don't support setting times. + #[cfg(not(any( + windows, + all( + unix, + not(any( + target_os = "android", + target_os = "redox", + target_os = "espidf", + target_os = "horizon" + )) + ) + )))] + Err(e) if e.kind() == ErrorKind::Unsupported => return, + Err(e) => panic!("error setting file times: {e:?}"), + Ok(_) => {} + } + + let metadata = fs::metadata(&path).unwrap(); + assert_eq!(metadata.accessed().unwrap(), accessed); + assert_eq!(metadata.modified().unwrap(), modified); + #[cfg(any(windows, target_vendor = "apple"))] + { + assert_eq!(metadata.created().unwrap(), created); + } +} + +#[test] +fn test_fs_set_times_follows_symlink() { + #[cfg(target_vendor = "apple")] + use crate::os::darwin::fs::FileTimesExt; + #[cfg(windows)] + use crate::os::windows::fs::FileTimesExt; + + let tmp = tmpdir(); + + // Create a target file + let target = tmp.join("target"); + File::create(&target).unwrap(); + + // Create a symlink to the target + #[cfg(unix)] + let link = tmp.join("link"); + #[cfg(unix)] + crate::os::unix::fs::symlink(&target, &link).unwrap(); + + #[cfg(windows)] + let link = tmp.join("link.txt"); + #[cfg(windows)] + crate::os::windows::fs::symlink_file(&target, &link).unwrap(); + + // Get the symlink's own modified time BEFORE calling set_times (to compare later) + // We don't check accessed time because reading metadata may update atime on some platforms. + let link_metadata_before = fs::symlink_metadata(&link).unwrap(); + let link_modified_before = link_metadata_before.modified().unwrap(); + + let mut times = FileTimes::new(); + let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345); + let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321); + times = times.set_accessed(accessed).set_modified(modified); + + #[cfg(any(windows, target_vendor = "apple"))] + let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123); + #[cfg(any(windows, target_vendor = "apple"))] + { + times = times.set_created(created); + } + + // Call fs::set_times on the symlink - it should follow the link and modify the target + match fs::set_times(&link, times) { + // Allow unsupported errors on platforms which don't support setting times. + #[cfg(not(any( + windows, + all( + unix, + not(any( + target_os = "android", + target_os = "redox", + target_os = "espidf", + target_os = "horizon" + )) + ) + )))] + Err(e) if e.kind() == ErrorKind::Unsupported => return, + Err(e) => panic!("error setting file times through symlink: {e:?}"), + Ok(_) => {} + } + + // Verify that the TARGET file's times were changed (following the symlink) + let target_metadata = fs::metadata(&target).unwrap(); + assert_eq!( + target_metadata.accessed().unwrap(), + accessed, + "target file accessed time should match" + ); + assert_eq!( + target_metadata.modified().unwrap(), + modified, + "target file modified time should match" + ); + #[cfg(any(windows, target_vendor = "apple"))] + { + assert_eq!( + target_metadata.created().unwrap(), + created, + "target file created time should match" + ); + } + + // Also verify through the symlink (fs::metadata follows symlinks) + let link_followed_metadata = fs::metadata(&link).unwrap(); + assert_eq!(link_followed_metadata.accessed().unwrap(), accessed); + assert_eq!(link_followed_metadata.modified().unwrap(), modified); + + // Verify that the SYMLINK ITSELF was NOT modified + // Note: We only check modified time, not accessed time, because reading the symlink + // metadata may update its atime on some platforms (e.g., Linux). + let link_metadata_after = fs::symlink_metadata(&link).unwrap(); + assert_eq!( + link_metadata_after.modified().unwrap(), + link_modified_before, + "symlink's own modified time should not change" + ); +} + +#[test] +fn test_fs_set_times_nofollow() { + #[cfg(target_vendor = "apple")] + use crate::os::darwin::fs::FileTimesExt; + #[cfg(windows)] + use crate::os::windows::fs::FileTimesExt; + + let tmp = tmpdir(); + + // Create a target file and a symlink to it + let target = tmp.join("target"); + File::create(&target).unwrap(); + + #[cfg(unix)] + let link = tmp.join("link"); + #[cfg(unix)] + crate::os::unix::fs::symlink(&target, &link).unwrap(); + + #[cfg(windows)] + let link = tmp.join("link.txt"); + #[cfg(windows)] + crate::os::windows::fs::symlink_file(&target, &link).unwrap(); + + let mut times = FileTimes::new(); + let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(11111); + let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(22222); + times = times.set_accessed(accessed).set_modified(modified); + + #[cfg(any(windows, target_vendor = "apple"))] + let created = SystemTime::UNIX_EPOCH + Duration::from_secs(33333); + #[cfg(any(windows, target_vendor = "apple"))] + { + times = times.set_created(created); + } + + // Set times on the symlink itself (not following it) + match fs::set_times_nofollow(&link, times) { + // Allow unsupported errors on platforms which don't support setting times. + #[cfg(not(any( + windows, + all( + unix, + not(any( + target_os = "android", + target_os = "redox", + target_os = "espidf", + target_os = "horizon" + )) + ) + )))] + Err(e) if e.kind() == ErrorKind::Unsupported => return, + Err(e) => panic!("error setting symlink times: {e:?}"), + Ok(_) => {} + } + + // Read symlink metadata (without following) + let metadata = fs::symlink_metadata(&link).unwrap(); + assert_eq!(metadata.accessed().unwrap(), accessed); + assert_eq!(metadata.modified().unwrap(), modified); + #[cfg(any(windows, target_vendor = "apple"))] + { + assert_eq!(metadata.created().unwrap(), created); + } + + // Verify that the target file's times were NOT changed + let target_metadata = fs::metadata(&target).unwrap(); + assert_ne!(target_metadata.accessed().unwrap(), accessed); + assert_ne!(target_metadata.modified().unwrap(), modified); +} diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 175d919c289dd..21235bcfbd8c5 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -566,6 +566,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { Err(Error::from_raw_os_error(22)) } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + Err(Error::from_raw_os_error(22)) +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + Err(Error::from_raw_os_error(22)) +} + pub fn rmdir(path: &Path) -> io::Result<()> { run_path_with_cstr(path, &|path| cvt(unsafe { hermit_abi::rmdir(path.as_ptr()) }).map(|_| ())) } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 64f5a6b36d3db..b498f9cb7ea72 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -161,3 +161,11 @@ pub fn exists(path: &Path) -> io::Result { #[cfg(windows)] with_native_path(path, &imp::exists) } + +pub fn set_times(path: &Path, times: FileTimes) -> io::Result<()> { + with_native_path(path, &|path| imp::set_times(path, times.clone())) +} + +pub fn set_times_nofollow(path: &Path, times: FileTimes) -> io::Result<()> { + with_native_path(path, &|path| imp::set_times_nofollow(path, times.clone())) +} diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index 808a95829114e..39bd9b3cdd70b 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -538,6 +538,17 @@ pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { Ok(()) } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + Err(io::const_error!(io::ErrorKind::Unsupported, "setting file times not supported",)) +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + Err(io::const_error!( + io::ErrorKind::Unsupported, + "setting file times on symlinks not supported", + )) +} + pub fn rmdir(p: &Path) -> io::Result<()> { if stat(p)?.file_type().is_dir() { error::SolidError::err_if_negative(unsafe { abi::SOLID_FS_Unlink(cstr(p)?.as_ptr()) }) diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 5763d7862f5ae..e4e7274ae8cb3 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -333,6 +333,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() +} + pub fn rmdir(_p: &Path) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index bed9ea9139834..578b5f4a1d986 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1195,6 +1195,55 @@ impl fmt::Debug for OpenOptions { } } +#[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "nuttx", +)))] +fn to_timespec(time: Option) -> io::Result { + match time { + Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts), + Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_error!( + io::ErrorKind::InvalidInput, + "timestamp is too large to set as a file time", + )), + Some(_) => Err(io::const_error!( + io::ErrorKind::InvalidInput, + "timestamp is too small to set as a file time", + )), + None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }), + } +} + +#[cfg(target_vendor = "apple")] +fn set_attrlist_with_times( + times: &FileTimes, +) -> io::Result<(libc::attrlist, [mem::MaybeUninit; 3], usize)> { + let mut buf = [mem::MaybeUninit::::uninit(); 3]; + let mut num_times = 0; + let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; + attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; + + if times.created.is_some() { + buf[num_times].write(to_timespec(times.created)?); + num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_CRTIME; + } + if times.modified.is_some() { + buf[num_times].write(to_timespec(times.modified)?); + num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_MODTIME; + } + if times.accessed.is_some() { + buf[num_times].write(to_timespec(times.accessed)?); + num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; + } + + Ok((attrlist, buf, num_times)) +} + impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { run_path_with_cstr(path, &|path| File::open_c(path, opts)) @@ -2112,6 +2161,84 @@ fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> Ok((reader, metadata)) } +fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { + cfg_select! { + any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { + let _ = (p, times, flags); + Err(io::const_error!( + io::ErrorKind::Unsupported, + "setting file times not supported", + )) + } + target_vendor = "apple" => { + // Apple platforms use setattrlist which supports setting times on symlinks + let (attrlist, buf, num_times) = set_attrlist_with_times(×)?; + let options = if flags == libc::AT_SYMLINK_NOFOLLOW { + libc::FSOPT_NOFOLLOW + } else { + 0 + }; + + cvt(unsafe { libc::setattrlist( + p.as_ptr(), + (&raw const attrlist).cast::().cast_mut(), + buf.as_ptr().cast::().cast_mut(), + num_times * size_of::(), + options as u32 + ) })?; + Ok(()) + } + target_os = "android" => { + let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + // utimensat requires Android API level 19 + cvt(unsafe { + weak!( + fn utimensat(dirfd: c_int, path: *const c_char, times: *const libc::timespec, flags: c_int) -> c_int; + ); + match utimensat.get() { + Some(utimensat) => utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags), + None => return Err(io::const_error!( + io::ErrorKind::Unsupported, + "setting file times requires Android API level >= 19", + )), + } + })?; + Ok(()) + } + _ => { + #[cfg(all(target_os = "linux", target_env = "gnu", target_pointer_width = "32", not(target_arch = "riscv32")))] + { + use crate::sys::{time::__timespec64, weak::weak}; + + // Added in glibc 2.34 + weak!( + fn __utimensat64(dirfd: c_int, path: *const c_char, times: *const __timespec64, flags: c_int) -> c_int; + ); + + if let Some(utimensat64) = __utimensat64.get() { + let to_timespec = |time: Option| time.map(|time| time.t.to_timespec64()) + .unwrap_or(__timespec64::new(0, libc::UTIME_OMIT as _)); + let times = [to_timespec(times.accessed), to_timespec(times.modified)]; + cvt(unsafe { utimensat64(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?; + return Ok(()); + } + } + let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + cvt(unsafe { libc::utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?; + Ok(()) + } + } +} + +pub fn set_times(p: &CStr, times: FileTimes) -> io::Result<()> { + // flags = 0 means follow symlinks + set_times_impl(p, times, 0) +} + +pub fn set_times_nofollow(p: &CStr, times: FileTimes) -> io::Result<()> { + set_times_impl(p, times, libc::AT_SYMLINK_NOFOLLOW) +} + #[cfg(target_os = "espidf")] fn open_to_and_set_permissions( to: &Path, diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index efaddb51b3751..659ea2a8fc276 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -312,6 +312,14 @@ pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { match perm.0 {} } +pub fn set_times(_p: &Path, times: FileTimes) -> io::Result<()> { + match times {} +} + +pub fn set_times_nofollow(_p: &Path, times: FileTimes) -> io::Result<()> { + match times {} +} + pub fn rmdir(_p: &Path) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index f642e7cb074ec..99b156d535768 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -492,6 +492,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() +} + pub fn exists(path: &Path) -> io::Result { run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0)) } diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index 0b65b9cb389df..1e6c0fad5b830 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -643,6 +643,18 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + // File times haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + // File times haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() +} + pub fn rmdir(p: &Path) -> io::Result<()> { let (dir, file) = open_parent(p)?; dir.remove_directory(osstr2str(file.as_ref())?) diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index ccfe410627f70..f2d325da35c7d 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1514,6 +1514,23 @@ pub fn set_perm(p: &WCStr, perm: FilePermissions) -> io::Result<()> { } } +pub fn set_times(p: &WCStr, times: FileTimes) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.write(true); + opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS); + let file = File::open_native(p, &opts)?; + file.set_times(times) +} + +pub fn set_times_nofollow(p: &WCStr, times: FileTimes) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.write(true); + // `FILE_FLAG_OPEN_REPARSE_POINT` for no_follow behavior + opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT); + let file = File::open_native(p, &opts)?; + file.set_times(times) +} + fn get_path(f: &File) -> io::Result { fill_utf16_buf( |buf, sz| unsafe { From 3d40fa69587c60d8ba7bcf3d2a39e503a5ded07c Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 9 Oct 2025 10:20:48 +0800 Subject: [PATCH 16/35] Update library/std/src/fs.rs Co-authored-by: Josh Triplett --- 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 e97190e69d68f..f374f4155cfd2 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -432,8 +432,9 @@ pub fn set_times>(path: P, times: FileTimes) -> io::Result<()> { /// /// # Platform-specific behavior /// -/// This function currently corresponds to the `utimensat` function with `AT_SYMLINK_NOFOLLOW` -/// on Unix platforms and the `SetFileTime` function on Windows after opening the symlink. +/// This function currently corresponds to the `utimensat` function with `AT_SYMLINK_NOFOLLOW` on +/// Unix platforms, the `setattrlist` function with `FSOPT_NOFOLLOW` on Apple platforms, and the +/// `SetFileTime` function on Windows. /// /// # Errors /// From 6308e76cd7d9d6b4b3912c875eabd3eb695afc5d Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 9 Oct 2025 10:21:06 +0800 Subject: [PATCH 17/35] Update library/std/src/fs.rs Co-authored-by: Josh Triplett --- library/std/src/fs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index f374f4155cfd2..60eefc36ae2be 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -395,8 +395,8 @@ pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result /// /// # Platform-specific behavior /// -/// This function currently corresponds to the `utimensat` function on Unix platforms -/// and the `SetFileTime` function on Windows. +/// This function currently corresponds to the `utimensat` function on Unix platforms, the +/// `setattrlist` function on Apple platforms, and the `SetFileTime` function on Windows. /// /// # Errors /// From 1c5c8caad2401c923c1c8c515f004c52c8e74ed8 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 9 Oct 2025 10:23:34 +0800 Subject: [PATCH 18/35] use proper unsupported --- library/std/src/sys/fs/solid.rs | 7 ++----- library/std/src/sys/fs/unix.rs | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index 39bd9b3cdd70b..f6d5d3b784d3b 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -539,14 +539,11 @@ pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { } pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { - Err(io::const_error!(io::ErrorKind::Unsupported, "setting file times not supported",)) + unsupported() } pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { - Err(io::const_error!( - io::ErrorKind::Unsupported, - "setting file times on symlinks not supported", - )) + unsupported() } pub fn rmdir(p: &Path) -> io::Result<()> { diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 578b5f4a1d986..014b10f06176c 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1201,6 +1201,7 @@ impl fmt::Debug for OpenOptions { target_os = "horizon", target_os = "nuttx", )))] +#[inline(always)] fn to_timespec(time: Option) -> io::Result { match time { Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts), @@ -1217,6 +1218,7 @@ fn to_timespec(time: Option) -> io::Result { } #[cfg(target_vendor = "apple")] +#[inline(always)] fn set_attrlist_with_times( times: &FileTimes, ) -> io::Result<(libc::attrlist, [mem::MaybeUninit; 3], usize)> { From 46c6f0aadee016c28a0eb3d3fa977f33111e38ab Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 9 Oct 2025 10:49:13 +0800 Subject: [PATCH 19/35] rebase #147504 --- library/std/src/sys/fs/unix.rs | 73 ++++++---------------------------- 1 file changed, 13 insertions(+), 60 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 014b10f06176c..63a6db77324a9 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1195,57 +1195,6 @@ impl fmt::Debug for OpenOptions { } } -#[cfg(not(any( - target_os = "redox", - target_os = "espidf", - target_os = "horizon", - target_os = "nuttx", -)))] -#[inline(always)] -fn to_timespec(time: Option) -> io::Result { - match time { - Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts), - Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_error!( - io::ErrorKind::InvalidInput, - "timestamp is too large to set as a file time", - )), - Some(_) => Err(io::const_error!( - io::ErrorKind::InvalidInput, - "timestamp is too small to set as a file time", - )), - None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }), - } -} - -#[cfg(target_vendor = "apple")] -#[inline(always)] -fn set_attrlist_with_times( - times: &FileTimes, -) -> io::Result<(libc::attrlist, [mem::MaybeUninit; 3], usize)> { - let mut buf = [mem::MaybeUninit::::uninit(); 3]; - let mut num_times = 0; - let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; - attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; - - if times.created.is_some() { - buf[num_times].write(to_timespec(times.created)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_CRTIME; - } - if times.modified.is_some() { - buf[num_times].write(to_timespec(times.modified)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_MODTIME; - } - if times.accessed.is_some() { - buf[num_times].write(to_timespec(times.accessed)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; - } - - Ok((attrlist, buf, num_times)) -} - impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { run_path_with_cstr(path, &|path| File::open_c(path, opts)) @@ -1760,18 +1709,19 @@ impl TimesAttrlist { if times.created.is_some() { this.buf[this.num_times].write(file_time_to_timespec(times.created)?); this.num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_CRTIME; + this.attrlist.commonattr |= libc::ATTR_CMN_CRTIME; } if times.modified.is_some() { this.buf[this.num_times].write(file_time_to_timespec(times.modified)?); this.num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_MODTIME; + this.attrlist.commonattr |= libc::ATTR_CMN_MODTIME; } if times.accessed.is_some() { this.buf[this.num_times].write(file_time_to_timespec(times.accessed)?); this.num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; + this.attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; } + Ok(this) } fn attrlist(&self) -> *mut libc::c_void { @@ -2174,7 +2124,8 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { } target_vendor = "apple" => { // Apple platforms use setattrlist which supports setting times on symlinks - let (attrlist, buf, num_times) = set_attrlist_with_times(×)?; + //let (attrlist, buf, num_times) = set_attrlist_with_times(×)?; + let ta = TimesAttrlist::from_times(×)?; let options = if flags == libc::AT_SYMLINK_NOFOLLOW { libc::FSOPT_NOFOLLOW } else { @@ -2183,15 +2134,15 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { cvt(unsafe { libc::setattrlist( p.as_ptr(), - (&raw const attrlist).cast::().cast_mut(), - buf.as_ptr().cast::().cast_mut(), - num_times * size_of::(), + ta.attrlist(), + ta.times_buf(), + ta.times_buf_size(), options as u32 ) })?; Ok(()) } target_os = "android" => { - let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; // utimensat requires Android API level 19 cvt(unsafe { weak!( @@ -2225,18 +2176,20 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { return Ok(()); } } - let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; cvt(unsafe { libc::utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?; Ok(()) } } } +#[inline(always)] pub fn set_times(p: &CStr, times: FileTimes) -> io::Result<()> { // flags = 0 means follow symlinks set_times_impl(p, times, 0) } +#[inline(always)] pub fn set_times_nofollow(p: &CStr, times: FileTimes) -> io::Result<()> { set_times_impl(p, times, libc::AT_SYMLINK_NOFOLLOW) } From 2438df75fe8a954f54479ce1a79cb9863f05d926 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 9 Oct 2025 11:08:30 +0800 Subject: [PATCH 20/35] support fs::set_times for wasi --- library/std/src/sys/fs/wasi.rs | 55 +++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index 1e6c0fad5b830..92eb35317415f 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -536,17 +536,9 @@ impl File { } pub fn set_times(&self, times: FileTimes) -> io::Result<()> { - let to_timestamp = |time: Option| match time { - Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts), - Some(_) => Err(io::const_error!( - io::ErrorKind::InvalidInput, - "timestamp is too large to set as a file time", - )), - None => Ok(0), - }; self.fd.filestat_set_times( - to_timestamp(times.accessed)?, - to_timestamp(times.modified)?, + to_wasi_timestamp_or_now(times.accessed)?, + to_wasi_timestamp_or_now(times.modified)?, times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), ) @@ -643,16 +635,43 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } -pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { - // File times haven't been fully figured out in wasi yet, so this is - // likely temporary - unsupported() +#[inline(always)] +pub fn set_times(p: &Path, times: FileTimes) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + set_times_impl(&dir, &file, times, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW) } -pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { - // File times haven't been fully figured out in wasi yet, so this is - // likely temporary - unsupported() +#[inline(always)] +pub fn set_times_nofollow(p: &Path, times: FileTimes) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + set_times_impl(&dir, &file, times, 0) +} + +fn to_wasi_timestamp_or_now(time: Option) -> io::Result { + match time { + Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts), + Some(_) => Err(io::const_error!( + io::ErrorKind::InvalidInput, + "timestamp is too large to set as a file time", + )), + None => Ok(0), + } +} + +fn set_times_impl( + fd: &WasiFd, + path: &Path, + times: FileTimes, + flags: wasi::Lookupflags, +) -> io::Result<()> { + fd.path_filestat_set_times( + flags, + osstr2str(path.as_ref())?, + to_wasi_timestamp_or_now(times.accessed)?, + to_wasi_timestamp_or_now(times.modified)?, + times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) + | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), + ) } pub fn rmdir(p: &Path) -> io::Result<()> { From 1dd5641d74ca6d0fd873e7de9f5a63d12046d870 Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 9 Oct 2025 11:37:48 +0800 Subject: [PATCH 21/35] add doc alias for set_times_nofollow Co-authored-by: Josh Triplett --- library/std/src/fs.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 60eefc36ae2be..9841a246d6a5d 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -458,6 +458,9 @@ pub fn set_times>(path: P, times: FileTimes) -> io::Result<()> { /// } /// ``` #[unstable(feature = "fs_set_times", issue = "147455")] +#[doc(alias = "utimensat")] +#[doc(alias = "lutimens")] +#[doc(alias = "lutimes")] pub fn set_times_nofollow>(path: P, times: FileTimes) -> io::Result<()> { fs_imp::set_times_nofollow(path.as_ref(), times.0) } From 6fd1c2bd9456cfa69812c7b35132b6f4ce7569f0 Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 9 Oct 2025 11:38:19 +0800 Subject: [PATCH 22/35] add doc alias for set_times Co-authored-by: Josh Triplett --- library/std/src/fs.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 9841a246d6a5d..b548eb4939d42 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -420,6 +420,9 @@ pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result /// } /// ``` #[unstable(feature = "fs_set_times", issue = "147455")] +#[doc(alias = "utimens")] +#[doc(alias = "utimes")] +#[doc(alias = "utime")] pub fn set_times>(path: P, times: FileTimes) -> io::Result<()> { fs_imp::set_times(path.as_ref(), times.0) } From 901366af1e673a260d1a20551b0199540ae4b455 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 9 Oct 2025 16:42:54 +0800 Subject: [PATCH 23/35] fix c_char error in Android --- library/std/src/sys/fs/unix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 63a6db77324a9..51849a31f61b5 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -2146,7 +2146,7 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { // utimensat requires Android API level 19 cvt(unsafe { weak!( - fn utimensat(dirfd: c_int, path: *const c_char, times: *const libc::timespec, flags: c_int) -> c_int; + fn utimensat(dirfd: c_int, path: *const libc::c_char, times: *const libc::timespec, flags: c_int) -> c_int; ); match utimensat.get() { Some(utimensat) => utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags), From f8118d88d7bdb5f697cf4712e01f6b46fe2bc742 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 9 Oct 2025 06:36:51 -0700 Subject: [PATCH 24/35] unsupported: Use `unsupported()` for `set_times` --- library/std/src/sys/fs/unsupported.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 659ea2a8fc276..7901bf5624d74 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -312,8 +312,8 @@ pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { match perm.0 {} } -pub fn set_times(_p: &Path, times: FileTimes) -> io::Result<()> { - match times {} +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() } pub fn set_times_nofollow(_p: &Path, times: FileTimes) -> io::Result<()> { From d2f590a6d598dc75b09fd9d97c5bb32d23a6971a Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 9 Oct 2025 06:37:09 -0700 Subject: [PATCH 25/35] unsupported: Use `unsupported()` for `set_times_nofollow` --- library/std/src/sys/fs/unsupported.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 7901bf5624d74..f222151d18e25 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -316,8 +316,8 @@ pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { unsupported() } -pub fn set_times_nofollow(_p: &Path, times: FileTimes) -> io::Result<()> { - match times {} +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() } pub fn rmdir(_p: &Path) -> io::Result<()> { From 6a12470210aefa46d6078e27bcd20f6ef896c93a Mon Sep 17 00:00:00 2001 From: cyrgani Date: Fri, 10 Oct 2025 22:19:04 +0200 Subject: [PATCH 26/35] rename `DecodeMut` to `Decode` --- library/proc_macro/src/bridge/client.rs | 6 ++--- library/proc_macro/src/bridge/mod.rs | 4 +-- library/proc_macro/src/bridge/rpc.rs | 36 ++++++++++++------------- library/proc_macro/src/bridge/server.rs | 10 +++---- library/proc_macro/src/bridge/symbol.rs | 4 +-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/library/proc_macro/src/bridge/client.rs b/library/proc_macro/src/bridge/client.rs index 92558f2b7d9cc..bdaa865a998d6 100644 --- a/library/proc_macro/src/bridge/client.rs +++ b/library/proc_macro/src/bridge/client.rs @@ -58,7 +58,7 @@ macro_rules! define_client_handles { } } - impl DecodeMut<'_, '_, S> for $oty { + impl Decode<'_, '_, S> for $oty { fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { $oty { handle: handle::Handle::decode(r, s), @@ -82,7 +82,7 @@ macro_rules! define_client_handles { } } - impl DecodeMut<'_, '_, S> for $ity { + impl Decode<'_, '_, S> for $ity { fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { $ity { handle: handle::Handle::decode(r, s), @@ -276,7 +276,7 @@ fn maybe_install_panic_hook(force_show_panics: bool) { /// Client-side helper for handling client panics, entering the bridge, /// deserializing input and serializing output. // FIXME(eddyb) maybe replace `Bridge::enter` with this? -fn run_client DecodeMut<'a, 's, ()>, R: Encode<()>>( +fn run_client Decode<'a, 's, ()>, R: Encode<()>>( config: BridgeConfig<'_>, f: impl FnOnce(A) -> R, ) -> Buffer { diff --git a/library/proc_macro/src/bridge/mod.rs b/library/proc_macro/src/bridge/mod.rs index 582c43c78fcbb..b0ee9c0cc3027 100644 --- a/library/proc_macro/src/bridge/mod.rs +++ b/library/proc_macro/src/bridge/mod.rs @@ -143,7 +143,7 @@ mod symbol; use buffer::Buffer; pub use rpc::PanicMessage; -use rpc::{DecodeMut, Encode, Reader, Writer}; +use rpc::{Decode, Encode, Reader, Writer}; /// Configuration for establishing an active connection between a server and a /// client. The server creates the bridge config (`run_server` in `server.rs`), @@ -168,7 +168,7 @@ impl !Sync for BridgeConfig<'_> {} #[forbid(unsafe_code)] #[allow(non_camel_case_types)] mod api_tags { - use super::rpc::{DecodeMut, Encode, Reader, Writer}; + use super::rpc::{Decode, Encode, Reader, Writer}; macro_rules! declare_tags { ($($name:ident { diff --git a/library/proc_macro/src/bridge/rpc.rs b/library/proc_macro/src/bridge/rpc.rs index 6e5afe282d683..ed67674a74ab7 100644 --- a/library/proc_macro/src/bridge/rpc.rs +++ b/library/proc_macro/src/bridge/rpc.rs @@ -12,7 +12,7 @@ pub(super) trait Encode: Sized { pub(super) type Reader<'a> = &'a [u8]; -pub(super) trait DecodeMut<'a, 's, S>: Sized { +pub(super) trait Decode<'a, 's, S>: Sized { fn decode(r: &mut Reader<'a>, s: &'s mut S) -> Self; } @@ -24,7 +24,7 @@ macro_rules! rpc_encode_decode { } } - impl DecodeMut<'_, '_, S> for $ty { + impl Decode<'_, '_, S> for $ty { fn decode(r: &mut Reader<'_>, _: &mut S) -> Self { const N: usize = size_of::<$ty>(); @@ -43,12 +43,12 @@ macro_rules! rpc_encode_decode { } } - impl<'a, S, $($($T: for<'s> DecodeMut<'a, 's, S>),+)?> DecodeMut<'a, '_, S> + impl<'a, S, $($($T: for<'s> Decode<'a, 's, S>),+)?> Decode<'a, '_, S> for $name $(<$($T),+>)? { fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { $name { - $($field: DecodeMut::decode(r, s)),* + $($field: Decode::decode(r, s)),* } } } @@ -69,7 +69,7 @@ macro_rules! rpc_encode_decode { } } - impl<'a, S, $($($T: for<'s> DecodeMut<'a, 's, S>),+)?> DecodeMut<'a, '_, S> + impl<'a, S, $($($T: for<'s> Decode<'a, 's, S>),+)?> Decode<'a, '_, S> for $name $(<$($T),+>)? { fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { @@ -84,7 +84,7 @@ macro_rules! rpc_encode_decode { match u8::decode(r, s) { $(tag::$variant => { - $(let $field = DecodeMut::decode(r, s);)* + $(let $field = Decode::decode(r, s);)* $name::$variant $(($field))* })* _ => unreachable!(), @@ -98,7 +98,7 @@ impl Encode for () { fn encode(self, _: &mut Writer, _: &mut S) {} } -impl DecodeMut<'_, '_, S> for () { +impl Decode<'_, '_, S> for () { fn decode(_: &mut Reader<'_>, _: &mut S) -> Self {} } @@ -108,7 +108,7 @@ impl Encode for u8 { } } -impl DecodeMut<'_, '_, S> for u8 { +impl Decode<'_, '_, S> for u8 { fn decode(r: &mut Reader<'_>, _: &mut S) -> Self { let x = r[0]; *r = &r[1..]; @@ -125,7 +125,7 @@ impl Encode for bool { } } -impl DecodeMut<'_, '_, S> for bool { +impl Decode<'_, '_, S> for bool { fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { match u8::decode(r, s) { 0 => false, @@ -141,7 +141,7 @@ impl Encode for char { } } -impl DecodeMut<'_, '_, S> for char { +impl Decode<'_, '_, S> for char { fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { char::from_u32(u32::decode(r, s)).unwrap() } @@ -153,7 +153,7 @@ impl Encode for NonZero { } } -impl DecodeMut<'_, '_, S> for NonZero { +impl Decode<'_, '_, S> for NonZero { fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { Self::new(u32::decode(r, s)).unwrap() } @@ -166,11 +166,11 @@ impl, B: Encode> Encode for (A, B) { } } -impl<'a, S, A: for<'s> DecodeMut<'a, 's, S>, B: for<'s> DecodeMut<'a, 's, S>> DecodeMut<'a, '_, S> +impl<'a, S, A: for<'s> Decode<'a, 's, S>, B: for<'s> Decode<'a, 's, S>> Decode<'a, '_, S> for (A, B) { fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { - (DecodeMut::decode(r, s), DecodeMut::decode(r, s)) + (Decode::decode(r, s), Decode::decode(r, s)) } } @@ -181,7 +181,7 @@ impl Encode for &[u8] { } } -impl<'a, S> DecodeMut<'a, '_, S> for &'a [u8] { +impl<'a, S> Decode<'a, '_, S> for &'a [u8] { fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { let len = usize::decode(r, s); let xs = &r[..len]; @@ -196,7 +196,7 @@ impl Encode for &str { } } -impl<'a, S> DecodeMut<'a, '_, S> for &'a str { +impl<'a, S> Decode<'a, '_, S> for &'a str { fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { str::from_utf8(<&[u8]>::decode(r, s)).unwrap() } @@ -208,7 +208,7 @@ impl Encode for String { } } -impl DecodeMut<'_, '_, S> for String { +impl Decode<'_, '_, S> for String { fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { <&str>::decode(r, s).to_string() } @@ -223,7 +223,7 @@ impl> Encode for Vec { } } -impl<'a, S, T: for<'s> DecodeMut<'a, 's, S>> DecodeMut<'a, '_, S> for Vec { +impl<'a, S, T: for<'s> Decode<'a, 's, S>> Decode<'a, '_, S> for Vec { fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { let len = usize::decode(r, s); let mut vec = Vec::with_capacity(len); @@ -283,7 +283,7 @@ impl Encode for PanicMessage { } } -impl DecodeMut<'_, '_, S> for PanicMessage { +impl Decode<'_, '_, S> for PanicMessage { fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { match Option::::decode(r, s) { Some(s) => PanicMessage::String(s), diff --git a/library/proc_macro/src/bridge/server.rs b/library/proc_macro/src/bridge/server.rs index 2850e1099b700..e9ef26c07f24f 100644 --- a/library/proc_macro/src/bridge/server.rs +++ b/library/proc_macro/src/bridge/server.rs @@ -32,7 +32,7 @@ macro_rules! define_server_handles { } } - impl DecodeMut<'_, '_, HandleStore>> + impl Decode<'_, '_, HandleStore>> for Marked { fn decode(r: &mut Reader<'_>, s: &mut HandleStore>) -> Self { @@ -40,7 +40,7 @@ macro_rules! define_server_handles { } } - impl<'s, S: Types> DecodeMut<'_, 's, HandleStore>> + impl<'s, S: Types> Decode<'_, 's, HandleStore>> for &'s Marked { fn decode(r: &mut Reader<'_>, s: &'s mut HandleStore>) -> Self { @@ -48,7 +48,7 @@ macro_rules! define_server_handles { } } - impl<'s, S: Types> DecodeMut<'_, 's, HandleStore>> + impl<'s, S: Types> Decode<'_, 's, HandleStore>> for &'s mut Marked { fn decode( @@ -67,7 +67,7 @@ macro_rules! define_server_handles { } } - impl DecodeMut<'_, '_, HandleStore>> + impl Decode<'_, '_, HandleStore>> for Marked { fn decode(r: &mut Reader<'_>, s: &mut HandleStore>) -> Self { @@ -355,7 +355,7 @@ pub trait MessagePipe: Sized { fn run_server< S: Server, I: Encode>>, - O: for<'a, 's> DecodeMut<'a, 's, HandleStore>>, + O: for<'a, 's> Decode<'a, 's, HandleStore>>, >( strategy: &impl ExecutionStrategy, handle_counters: &'static client::HandleCounters, diff --git a/library/proc_macro/src/bridge/symbol.rs b/library/proc_macro/src/bridge/symbol.rs index eb7d30f9a6cc9..0d6a725fddd98 100644 --- a/library/proc_macro/src/bridge/symbol.rs +++ b/library/proc_macro/src/bridge/symbol.rs @@ -102,7 +102,7 @@ impl Encode for Symbol { } } -impl DecodeMut<'_, '_, server::HandleStore>> +impl Decode<'_, '_, server::HandleStore>> for Marked { fn decode(r: &mut Reader<'_>, s: &mut server::HandleStore>) -> Self { @@ -118,7 +118,7 @@ impl Encode>> } } -impl DecodeMut<'_, '_, S> for Symbol { +impl Decode<'_, '_, S> for Symbol { fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { Symbol::new(<&str>::decode(r, s)) } From 04da682613b1849893cff7cb31eda4429ff5633f Mon Sep 17 00:00:00 2001 From: Tropical <42101043+tropicaaal@users.noreply.github.com> Date: Sat, 11 Oct 2025 14:42:26 -0500 Subject: [PATCH 27/35] vexos: implement `pal::os::exit` --- library/std/src/sys/pal/vexos/mod.rs | 1 - library/std/src/sys/pal/vexos/os.rs | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 library/std/src/sys/pal/vexos/os.rs diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs index 61a34b0f68a30..3a37c8f2d4885 100644 --- a/library/std/src/sys/pal/vexos/mod.rs +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -1,4 +1,3 @@ -#[path = "../unsupported/os.rs"] pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/vexos/os.rs b/library/std/src/sys/pal/vexos/os.rs new file mode 100644 index 0000000000000..405f7c918f4a5 --- /dev/null +++ b/library/std/src/sys/pal/vexos/os.rs @@ -0,0 +1,19 @@ +#[expect(dead_code)] +#[path = "../unsupported/os.rs"] +mod unsupported_os; +pub use unsupported_os::{ + JoinPathsError, SplitPaths, chdir, current_exe, errno, error_string, getcwd, getpid, home_dir, + join_paths, split_paths, temp_dir, +}; + +pub use super::unsupported; + +pub fn exit(_code: i32) -> ! { + unsafe { + vex_sdk::vexSystemExitRequest(); + + loop { + vex_sdk::vexTasksRun(); + } + } +} From cc96d3f2f88b439e43b1271c1e2fe9b94ae3deb1 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 12 Oct 2025 00:00:40 +0100 Subject: [PATCH 28/35] Adjust the Arm targets in CI to reflect latest changes * Adds armv7a-none-eabihf (https://github.com/rust-lang/rust/pull/146522) * Adds armv8r-none-eabihf (https://github.com/rust-lang/rust/pull/146520) * Drops armeb*-none-* (https://github.com/rust-lang/rust/pull/146523) --- src/ci/docker/host-x86_64/dist-various-1/Dockerfile | 4 ++-- src/tools/build-manifest/src/main.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile index e59012ff6afa9..ccab021dd4588 100644 --- a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile @@ -111,12 +111,12 @@ ENV TARGETS=$TARGETS,riscv32imac-unknown-none-elf ENV TARGETS=$TARGETS,riscv32imafc-unknown-none-elf ENV TARGETS=$TARGETS,riscv64imac-unknown-none-elf ENV TARGETS=$TARGETS,riscv64gc-unknown-none-elf -ENV TARGETS=$TARGETS,armebv7r-none-eabi -ENV TARGETS=$TARGETS,armebv7r-none-eabihf ENV TARGETS=$TARGETS,armv7r-none-eabi ENV TARGETS=$TARGETS,armv7r-none-eabihf +ENV TARGETS=$TARGETS,armv8r-none-eabihf ENV TARGETS=$TARGETS,thumbv7neon-unknown-linux-gnueabihf ENV TARGETS=$TARGETS,armv7a-none-eabi +ENV TARGETS=$TARGETS,armv7a-none-eabihf ENV CFLAGS_armv5te_unknown_linux_musleabi="-march=armv5te -marm -mfloat-abi=soft" \ CFLAGS_arm_unknown_linux_musleabi="-march=armv6 -marm" \ diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 2890d26d3f447..9bae8b241a941 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -89,6 +89,7 @@ static TARGETS: &[&str] = &[ "armv7-unknown-linux-gnueabi", "armv7-unknown-linux-gnueabihf", "armv7a-none-eabi", + "armv7a-none-eabihf", "thumbv7neon-unknown-linux-gnueabihf", "armv7-unknown-linux-musleabi", "armv7-unknown-linux-musleabihf", From 64c023bad80b8de5f2f711c30424cfbab4dda171 Mon Sep 17 00:00:00 2001 From: dianqk Date: Sun, 12 Oct 2025 19:32:57 +0800 Subject: [PATCH 29/35] Add miscompiled test cases --- .../mir-opt/gvn_loop.loop_deref_mut.GVN.diff | 115 ++++++++++++++++++ tests/mir-opt/gvn_loop.rs | 31 +++++ tests/ui/mir/gvn-loop-miscompile.rs | 34 ++++++ 3 files changed, 180 insertions(+) create mode 100644 tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff create mode 100644 tests/mir-opt/gvn_loop.rs create mode 100644 tests/ui/mir/gvn-loop-miscompile.rs diff --git a/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff b/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff new file mode 100644 index 0000000000000..1a53667624aac --- /dev/null +++ b/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff @@ -0,0 +1,115 @@ +- // MIR for `loop_deref_mut` before GVN ++ // MIR for `loop_deref_mut` after GVN + + fn loop_deref_mut(_1: &mut Value) -> Value { + debug val => _1; + let mut _0: Value; + let _2: &Value; + let _3: &Value; + let mut _4: &Value; + let mut _6: !; + let mut _8: isize; + let mut _9: !; + let mut _10: (); + let mut _12: i32; + let _13: (); + let mut _14: bool; + let mut _15: !; + let mut _16: Value; + scope 1 { + debug val_alias => _2; + let mut _5: bool; + scope 2 { + debug stop => _5; + let _7: i32; + scope 3 { + debug v => _7; + let _11: Value; + scope 4 { + debug v => _11; + } + } + } + } + + bb0: { + StorageLive(_2); +- StorageLive(_3); ++ nop; + StorageLive(_4); + _4 = &(*_1); + _3 = get::(move _4) -> [return: bb1, unwind unreachable]; + } + + bb1: { + _2 = &(*_3); + StorageDead(_4); +- StorageDead(_3); ++ nop; + StorageLive(_5); + _5 = const false; +- _8 = discriminant((*_2)); ++ _8 = discriminant((*_3)); + switchInt(move _8) -> [0: bb3, otherwise: bb2]; + } + + bb2: { + unreachable; + } + + bb3: { +- StorageLive(_7); +- _7 = copy (((*_2) as V0).0: i32); ++ nop; ++ _7 = copy (((*_3) as V0).0: i32); + StorageLive(_9); + goto -> bb4; + } + + bb4: { + StorageLive(_11); + StorageLive(_12); + _12 = copy _7; +- _11 = Value::V0(move _12); ++ _11 = copy (*_3); + StorageDead(_12); + StorageLive(_13); + StorageLive(_14); + _14 = copy _5; + switchInt(move _14) -> [0: bb6, otherwise: bb5]; + } + + bb5: { + _0 = move _11; + StorageDead(_14); + StorageDead(_13); + StorageDead(_11); + StorageDead(_9); +- StorageDead(_7); ++ nop; + StorageDead(_5); + StorageDead(_2); + return; + } + + bb6: { + _13 = const (); + StorageDead(_14); + StorageDead(_13); + _5 = const true; + StorageLive(_16); +- _16 = Value::V1; +- (*_1) = move _16; ++ _16 = const Value::V1; ++ (*_1) = const Value::V1; + StorageDead(_16); + _10 = const (); + StorageDead(_11); + goto -> bb4; + } ++ } ++ ++ ALLOC0 (size: 8, align: 4) { ++ 01 00 00 00 __ __ __ __ │ ....░░░░ + } + diff --git a/tests/mir-opt/gvn_loop.rs b/tests/mir-opt/gvn_loop.rs new file mode 100644 index 0000000000000..a993f9b53f839 --- /dev/null +++ b/tests/mir-opt/gvn_loop.rs @@ -0,0 +1,31 @@ +// skip-filecheck +//@ test-mir-pass: GVN + +#![crate_type = "lib"] +#![feature(core_intrinsics, rustc_attrs)] + +pub enum Value { + V0(i32), + V1, +} + +// EMIT_MIR gvn_loop.loop_deref_mut.GVN.diff +fn loop_deref_mut(val: &mut Value) -> Value { + let val_alias: &Value = get(val); + let mut stop = false; + let Value::V0(v) = *val_alias else { unsafe { core::intrinsics::unreachable() } }; + loop { + let v = Value::V0(v); + if stop { + return v; + } + stop = true; + *val = Value::V1; + } +} + +#[inline(never)] +#[rustc_nounwind] +fn get(v: &T) -> &T { + v +} diff --git a/tests/ui/mir/gvn-loop-miscompile.rs b/tests/ui/mir/gvn-loop-miscompile.rs new file mode 100644 index 0000000000000..f24858918b16c --- /dev/null +++ b/tests/ui/mir/gvn-loop-miscompile.rs @@ -0,0 +1,34 @@ +//@ compile-flags: -O +//@ run-fail + +pub enum Value { + V0(i32), + V1, +} + +fn set_discriminant(val: &mut Value) -> Value { + let val_alias: &Value = get(val); + let mut stop = false; + let Value::V0(v) = *val_alias else { + unreachable!(); + }; + loop { + let v = Value::V0(v); + if stop { + return v; + } + stop = true; + *val = Value::V1; + } +} + +fn main() { + let mut v = Value::V0(1); + let v = set_discriminant(&mut v); + assert!(matches!(v, Value::V0(1))); +} + +#[inline(never)] +fn get(v: &T) -> &T { + v +} From 9b91c3e7ef481b9f35fe4afc3c660efe0a1ff66e Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 12 Oct 2025 17:47:20 +0100 Subject: [PATCH 30/35] Add CFLAGS for armv8r-none-eabihf --- src/ci/docker/host-x86_64/dist-various-1/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile index ccab021dd4588..6fcd9242699ef 100644 --- a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile @@ -131,6 +131,8 @@ ENV CFLAGS_armv5te_unknown_linux_musleabi="-march=armv5te -marm -mfloat-abi=soft CC_armv7a_none_eabihf=arm-none-eabi-gcc \ CFLAGS_armv7a_none_eabi=-march=armv7-a \ CFLAGS_armv7a_none_eabihf=-march=armv7-a+vfpv3 \ + CC_armv8r_none_eabihf=arm-none-eabi-gcc \ + CFLAGS_armv8r_none_eabihf="-march=armv8-r+fp.sp -mfpu=fp-armv8" \ CC_aarch64_unknown_none_softfloat=aarch64-none-elf-gcc \ CFLAGS_aarch64_unknown_none_softfloat=-mstrict-align -march=armv8-a+nofp+nosimd \ CC_aarch64_unknown_none=aarch64-none-elf-gcc \ From fef16d2cbaa9288631ce6047eaf006c58b419a56 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 12 Oct 2025 17:52:57 +0100 Subject: [PATCH 31/35] Change armv7a-none-eabihf CFLAGS to assume only single-precision FPU Not all ARMv7-A CPUs have a double-precision FPU. So adjust the CFLAGS from `+vfpv3` (which assumes 32 double-precision registers) to `+fp` (which only assumes 16 double-precision registers). --- src/ci/docker/host-x86_64/dist-various-1/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile index 6fcd9242699ef..ab29d0e80ac2a 100644 --- a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile @@ -130,7 +130,7 @@ ENV CFLAGS_armv5te_unknown_linux_musleabi="-march=armv5te -marm -mfloat-abi=soft CC_armv7a_none_eabi=arm-none-eabi-gcc \ CC_armv7a_none_eabihf=arm-none-eabi-gcc \ CFLAGS_armv7a_none_eabi=-march=armv7-a \ - CFLAGS_armv7a_none_eabihf=-march=armv7-a+vfpv3 \ + CFLAGS_armv7a_none_eabihf=-march=armv7-a+fp \ CC_armv8r_none_eabihf=arm-none-eabi-gcc \ CFLAGS_armv8r_none_eabihf="-march=armv8-r+fp.sp -mfpu=fp-armv8" \ CC_aarch64_unknown_none_softfloat=aarch64-none-elf-gcc \ From 4f1b945a4921fc1313bf0e410cf60119927cbbe6 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sun, 12 Oct 2025 19:12:15 -0400 Subject: [PATCH 32/35] Avoid redundant UB check in RangeFrom slice indexing --- library/core/src/slice/index.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs index de220e7e38a4b..a9806060d3d81 100644 --- a/library/core/src/slice/index.rs +++ b/library/core/src/slice/index.rs @@ -564,7 +564,10 @@ unsafe impl const SliceIndex<[T]> for ops::RangeFrom { slice_index_fail(self.start, slice.len(), slice.len()) } // SAFETY: `self` is checked to be valid and in bounds above. - unsafe { &*self.get_unchecked(slice) } + unsafe { + let new_len = crate::intrinsics::unchecked_sub(slice.len(), self.start); + &*get_offset_len_noubcheck(slice, self.start, new_len) + } } #[inline] @@ -573,7 +576,10 @@ unsafe impl const SliceIndex<[T]> for ops::RangeFrom { slice_index_fail(self.start, slice.len(), slice.len()) } // SAFETY: `self` is checked to be valid and in bounds above. - unsafe { &mut *self.get_unchecked_mut(slice) } + unsafe { + let new_len = crate::intrinsics::unchecked_sub(slice.len(), self.start); + &mut *get_offset_len_mut_noubcheck(slice, self.start, new_len) + } } } From 11977b2e65422362b4c0aed49ed8a573652e78cc Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:00:45 +0200 Subject: [PATCH 33/35] Hide vendoring and copyright in GHA group These two steps are currently the most verbose steps in a dist-linux build, which makes it harder to find more interesting parts. Hide them in a group like most things. --- src/bootstrap/src/core/build_steps/run.rs | 2 ++ src/bootstrap/src/core/build_steps/vendor.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index 9f7248b80f763..e87abf067a036 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -263,6 +263,8 @@ impl Step for GenerateCopyright { cache_dir }; + let _guard = builder.group("generate-copyright"); + let mut cmd = builder.tool_cmd(Tool::GenerateCopyright); cmd.env("CARGO_MANIFESTS", &cargo_manifests); cmd.env("LICENSE_METADATA", &license_metadata); diff --git a/src/bootstrap/src/core/build_steps/vendor.rs b/src/bootstrap/src/core/build_steps/vendor.rs index 0e9d4e7e32ba1..72250a26f376e 100644 --- a/src/bootstrap/src/core/build_steps/vendor.rs +++ b/src/bootstrap/src/core/build_steps/vendor.rs @@ -74,7 +74,7 @@ impl Step for Vendor { /// This function runs `cargo vendor` and ensures all required submodules /// are initialized before vendoring begins. fn run(self, builder: &Builder<'_>) -> Self::Output { - builder.info(&format!("Vendoring sources to {:?}", self.root_dir)); + let _guard = builder.group(&format!("Vendoring sources to {:?}", self.root_dir)); let mut cmd = command(&builder.initial_cargo); cmd.arg("vendor"); From d51f09e5ba425183bef648abecce13c5ac9cdb8b Mon Sep 17 00:00:00 2001 From: Jack Huey <31162821+jackh726@users.noreply.github.com> Date: Sun, 12 Oct 2025 13:17:02 -0400 Subject: [PATCH 34/35] Review comments. Move resolve_vars_if_possible into fn and add test minimizations. Update comment and test. --- compiler/rustc_hir_typeck/src/opaque_types.rs | 31 +++++++++---------- .../hidden-types-equate-before-fallback.rs | 18 +++++++++++ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/opaque_types.rs b/compiler/rustc_hir_typeck/src/opaque_types.rs index a46e1a5f75fa7..05b5b1a8af803 100644 --- a/compiler/rustc_hir_typeck/src/opaque_types.rs +++ b/compiler/rustc_hir_typeck/src/opaque_types.rs @@ -28,13 +28,9 @@ impl<'tcx> FnCtxt<'_, 'tcx> { pub(super) fn try_handle_opaque_type_uses_next(&mut self) { // We clone the opaques instead of stealing them here as we still need // to use them after fallback. - let mut opaque_types: Vec<_> = self.infcx.clone_opaque_types(); - for entry in &mut opaque_types { - *entry = self.resolve_vars_if_possible(*entry); - } - debug!(?opaque_types); + let opaque_types: Vec<_> = self.infcx.clone_opaque_types(); - self.compute_definition_site_hidden_types(&opaque_types, false); + self.compute_definition_site_hidden_types(opaque_types, false); } /// This takes all the opaque type uses during HIR typeck. It first computes @@ -49,16 +45,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> { pub(super) fn handle_opaque_type_uses_next(&mut self) { // We clone the opaques instead of stealing them here as they are still used for // normalization in the next generation trait solver. - let mut opaque_types: Vec<_> = self.infcx.clone_opaque_types(); + let opaque_types: Vec<_> = self.infcx.clone_opaque_types(); let num_entries = self.inner.borrow_mut().opaque_types().num_entries(); let prev = self.checked_opaque_types_storage_entries.replace(Some(num_entries)); debug_assert_eq!(prev, None); - for entry in &mut opaque_types { - *entry = self.resolve_vars_if_possible(*entry); - } - debug!(?opaque_types); - self.compute_definition_site_hidden_types(&opaque_types, true); + self.compute_definition_site_hidden_types(opaque_types, true); } } @@ -96,9 +88,14 @@ impl<'tcx> UsageKind<'tcx> { impl<'tcx> FnCtxt<'_, 'tcx> { fn compute_definition_site_hidden_types( &mut self, - opaque_types: &[(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)], + mut opaque_types: Vec<(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)>, error_on_missing_defining_use: bool, ) { + for entry in opaque_types.iter_mut() { + *entry = self.resolve_vars_if_possible(*entry); + } + debug!(?opaque_types); + let tcx = self.tcx; let TypingMode::Analysis { defining_opaque_types_and_generators } = self.typing_mode() else { @@ -116,7 +113,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { // store this), because we can go from `UnconstrainedHiddenType` to // `HasDefiningUse` (because of fallback) let mut usage_kind = UsageKind::None; - for &(opaque_type_key, hidden_type) in opaque_types { + for &(opaque_type_key, hidden_type) in &opaque_types { if opaque_type_key.def_id != def_id { continue; } @@ -129,7 +126,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } if let UsageKind::HasDefiningUse(ty) = usage_kind { - for &(opaque_type_key, hidden_type) in opaque_types { + for &(opaque_type_key, hidden_type) in &opaque_types { if opaque_type_key.def_id != def_id { continue; } @@ -145,8 +142,8 @@ impl<'tcx> FnCtxt<'_, 'tcx> { let _ = self.typeck_results.borrow_mut().hidden_types.insert(def_id, ty); } - // If this the first pass (`try_handle_opaque_type_uses_next`), - // then do not report any errors. + // If we're in `fn try_handle_opaque_type_uses_next` then do not + // report any errors. if !error_on_missing_defining_use { continue; } diff --git a/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs b/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs index dd5ff26da4f7f..0bd01e66b02b8 100644 --- a/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs +++ b/tests/ui/traits/next-solver/opaques/hidden-types-equate-before-fallback.rs @@ -23,4 +23,22 @@ impl FileSystem { } } +fn build2() -> impl Sized { + if false { + build2::() + } else { + loop {} + }; + 1u32 +} + +fn build3<'a>() -> impl Sized + use<'a> { + if false { + build3() + } else { + loop {} + }; + 1u32 +} + fn main() {} From 2048b9c027c8c55d1a83be5d769bb65a855397d3 Mon Sep 17 00:00:00 2001 From: dianqk Date: Sun, 12 Oct 2025 19:53:12 +0800 Subject: [PATCH 35/35] GVN: Invalidate derefs at loop headers --- compiler/rustc_middle/src/mir/loops.rs | 29 +++++++++++++++++++ compiler/rustc_middle/src/mir/mod.rs | 1 + compiler/rustc_mir_transform/src/gvn.rs | 6 ++++ .../rustc_mir_transform/src/jump_threading.rs | 28 +----------------- .../mir-opt/gvn_loop.loop_deref_mut.GVN.diff | 2 +- tests/mir-opt/gvn_loop.rs | 10 ++++++- tests/ui/mir/gvn-loop-miscompile.rs | 2 +- 7 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 compiler/rustc_middle/src/mir/loops.rs diff --git a/compiler/rustc_middle/src/mir/loops.rs b/compiler/rustc_middle/src/mir/loops.rs new file mode 100644 index 0000000000000..ae332913c6421 --- /dev/null +++ b/compiler/rustc_middle/src/mir/loops.rs @@ -0,0 +1,29 @@ +use rustc_index::bit_set::DenseBitSet; + +use super::*; + +/// Compute the set of loop headers in the given body. A loop header is usually defined as a block +/// which dominates one of its predecessors. This definition is only correct for reducible CFGs. +/// However, computing dominators is expensive, so we approximate according to the post-order +/// traversal order. A loop header for us is a block which is visited after its predecessor in +/// post-order. This is ok as we mostly need a heuristic. +pub fn maybe_loop_headers(body: &Body<'_>) -> DenseBitSet { + let mut maybe_loop_headers = DenseBitSet::new_empty(body.basic_blocks.len()); + let mut visited = DenseBitSet::new_empty(body.basic_blocks.len()); + for (bb, bbdata) in traversal::postorder(body) { + // Post-order means we visit successors before the block for acyclic CFGs. + // If the successor is not visited yet, consider it a loop header. + for succ in bbdata.terminator().successors() { + if !visited.contains(succ) { + maybe_loop_headers.insert(succ); + } + } + + // Only mark `bb` as visited after we checked the successors, in case we have a self-loop. + // bb1: goto -> bb1; + let _new = visited.insert(bb); + debug_assert!(_new); + } + + maybe_loop_headers +} diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 8eae8da4f44f8..36dfabea45677 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -51,6 +51,7 @@ mod statement; mod syntax; mod terminator; +pub mod loops; pub mod traversal; pub mod visit; diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 2f857ea45ac72..0d97571fa818a 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -129,6 +129,7 @@ impl<'tcx> crate::MirPass<'tcx> for GVN { let ssa = SsaLocals::new(tcx, body, typing_env); // Clone dominators because we need them while mutating the body. let dominators = body.basic_blocks.dominators().clone(); + let maybe_loop_headers = loops::maybe_loop_headers(body); let arena = DroplessArena::default(); let mut state = @@ -141,6 +142,11 @@ impl<'tcx> crate::MirPass<'tcx> for GVN { let reverse_postorder = body.basic_blocks.reverse_postorder().to_vec(); for bb in reverse_postorder { + // N.B. With loops, reverse postorder cannot produce a valid topological order. + // A statement or terminator from inside the loop, that is not processed yet, may have performed an indirect write. + if maybe_loop_headers.contains(bb) { + state.invalidate_derefs(); + } let data = &mut body.basic_blocks.as_mut_preserves_cfg()[bb]; state.visit_basic_block_data(bb, data); } diff --git a/compiler/rustc_mir_transform/src/jump_threading.rs b/compiler/rustc_mir_transform/src/jump_threading.rs index 59aa811aa73e8..492f5ca82a07e 100644 --- a/compiler/rustc_mir_transform/src/jump_threading.rs +++ b/compiler/rustc_mir_transform/src/jump_threading.rs @@ -84,7 +84,7 @@ impl<'tcx> crate::MirPass<'tcx> for JumpThreading { body, arena, map: Map::new(tcx, body, Some(MAX_PLACES)), - maybe_loop_headers: maybe_loop_headers(body), + maybe_loop_headers: loops::maybe_loop_headers(body), opportunities: Vec::new(), }; @@ -830,29 +830,3 @@ enum Update { Incr, Decr, } - -/// Compute the set of loop headers in the given body. A loop header is usually defined as a block -/// which dominates one of its predecessors. This definition is only correct for reducible CFGs. -/// However, computing dominators is expensive, so we approximate according to the post-order -/// traversal order. A loop header for us is a block which is visited after its predecessor in -/// post-order. This is ok as we mostly need a heuristic. -fn maybe_loop_headers(body: &Body<'_>) -> DenseBitSet { - let mut maybe_loop_headers = DenseBitSet::new_empty(body.basic_blocks.len()); - let mut visited = DenseBitSet::new_empty(body.basic_blocks.len()); - for (bb, bbdata) in traversal::postorder(body) { - // Post-order means we visit successors before the block for acyclic CFGs. - // If the successor is not visited yet, consider it a loop header. - for succ in bbdata.terminator().successors() { - if !visited.contains(succ) { - maybe_loop_headers.insert(succ); - } - } - - // Only mark `bb` as visited after we checked the successors, in case we have a self-loop. - // bb1: goto -> bb1; - let _new = visited.insert(bb); - debug_assert!(_new); - } - - maybe_loop_headers -} diff --git a/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff b/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff index 1a53667624aac..92e5ccabedf9e 100644 --- a/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff +++ b/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff @@ -71,7 +71,7 @@ StorageLive(_12); _12 = copy _7; - _11 = Value::V0(move _12); -+ _11 = copy (*_3); ++ _11 = Value::V0(copy _7); StorageDead(_12); StorageLive(_13); StorageLive(_14); diff --git a/tests/mir-opt/gvn_loop.rs b/tests/mir-opt/gvn_loop.rs index a993f9b53f839..6e9df55a968dc 100644 --- a/tests/mir-opt/gvn_loop.rs +++ b/tests/mir-opt/gvn_loop.rs @@ -1,4 +1,3 @@ -// skip-filecheck //@ test-mir-pass: GVN #![crate_type = "lib"] @@ -9,8 +8,17 @@ pub enum Value { V1, } +// Check that we do not use the dereferenced value of `val_alias` when returning. + // EMIT_MIR gvn_loop.loop_deref_mut.GVN.diff fn loop_deref_mut(val: &mut Value) -> Value { + // CHECK-LABEL: fn loop_deref_mut( + // CHECK: [[VAL_REF:_.*]] = get::( + // CHECK: [[V:_.*]] = copy (((*[[VAL_REF]]) as V0).0: i32); + // CEHCK-NOT: copy (*[[VAL_REF]]); + // CHECK: [[RET:_*]] = Value::V0(copy [[V]]); + // CEHCK-NOT: copy (*[[VAL_REF]]); + // CHECK: _0 = move [[RET]] let val_alias: &Value = get(val); let mut stop = false; let Value::V0(v) = *val_alias else { unsafe { core::intrinsics::unreachable() } }; diff --git a/tests/ui/mir/gvn-loop-miscompile.rs b/tests/ui/mir/gvn-loop-miscompile.rs index f24858918b16c..8e987c5c94662 100644 --- a/tests/ui/mir/gvn-loop-miscompile.rs +++ b/tests/ui/mir/gvn-loop-miscompile.rs @@ -1,5 +1,5 @@ //@ compile-flags: -O -//@ run-fail +//@ run-pass pub enum Value { V0(i32),