From b429456924bb43549c98af77e80807f3e3fb98cf Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 23 Jun 2026 16:37:49 +0200 Subject: [PATCH 01/14] Move some methods that only need the region context onto that --- .../src/diagnostics/explain_borrow.rs | 2 +- .../src/diagnostics/region_errors.rs | 40 ++++++++++++------- .../src/diagnostics/region_name.rs | 6 +-- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs index 5d154cc7b9050..f6abafb874165 100644 --- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs +++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs @@ -706,7 +706,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { Some(Cause::LiveVar(..) | Cause::DropVar(..)) | None => { // Here, under NLL: no cause was found. Under polonius: no cause was found, or a // boring local was found, which we ignore like NLLs do to match its diagnostics. - if let Some(region) = self.to_error_region_vid(borrow_region_vid) { + if let Some(region) = self.regioncx.to_error_region_vid(borrow_region_vid) { let (category, from_closure, span, region_name, path) = self.free_region_constraint_info(borrow_region_vid, region); if let Some(region_name) = region_name { diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index e436d95cdb087..66d35f8b8bf2d 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -28,6 +28,7 @@ use rustc_trait_selection::traits::{Obligation, ObligationCtxt}; use tracing::{debug, instrument, trace}; use super::{LIMITATION_NOTE, OutlivesSuggestionBuilder, RegionName, RegionNameSource}; +use crate::consumers::RegionInferenceContext; use crate::nll::ConstraintDescription; use crate::region_infer::{BlameConstraint, TypeTest}; use crate::session_diagnostics::{ @@ -134,7 +135,7 @@ pub(crate) struct ErrorConstraintInfo<'tcx> { pub(super) span: Span, } -impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { +impl<'tcx> RegionInferenceContext<'tcx> { /// Converts a region inference variable into a `ty::Region` that /// we can use for error reporting. If `r` is universally bound, /// then we use the name that we have on record for it. If `r` is @@ -142,20 +143,20 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// to find a good name from that. Returns `None` if we can't find /// one (e.g., this is just some random part of the CFG). pub(super) fn to_error_region(&self, r: RegionVid) -> Option> { - self.to_error_region_vid(r).and_then(|r| self.regioncx.region_definition(r).external_name) + self.to_error_region_vid(r).and_then(|r| self.region_definition(r).external_name) } /// Returns the `RegionVid` corresponding to the region returned by /// `to_error_region`. pub(super) fn to_error_region_vid(&self, r: RegionVid) -> Option { - if self.regioncx.universal_regions().is_universal_region(r) { + if self.universal_regions().is_universal_region(r) { Some(r) } else { // We just want something nameable, even if it's not // actually an upper bound. - let upper_bound = self.regioncx.approx_universal_upper_bound(r); + let upper_bound = self.approx_universal_upper_bound(r); - if self.regioncx.upper_bound_in_region_scc(r, upper_bound) { + if self.upper_bound_in_region_scc(r, upper_bound) { self.to_error_region_vid(upper_bound) } else { None @@ -179,14 +180,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { if let Some(r) = self.to_error_region(fr) && let ty::ReLateParam(late_param) = r.kind() && let ty::LateParamRegionKind::ClosureEnv = late_param.kind - && let DefiningTy::Closure(_, args) = self.regioncx.universal_regions().defining_ty + && let DefiningTy::Closure(_, args) = self.universal_regions().defining_ty { return args.as_closure().kind() == ty::ClosureKind::FnMut; } false } +} +impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // For generic associated types (GATs) which implied 'static requirement // from higher-ranked trait bounds (HRTB). Try to locate span of the trait // and the span which bounded to the trait for adding 'static lifetime suggestion @@ -309,12 +312,12 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { RegionErrorKind::TypeTestError { type_test } => { // Try to convert the lower-bound region into something named we can print for // the user. - let lower_bound_region = self.to_error_region(type_test.lower_bound); + let lower_bound_region = self.regioncx.to_error_region(type_test.lower_bound); let type_test_span = type_test.span; if let Some(lower_bound_region) = lower_bound_region { - let generic_ty = self.name_regions( + let generic_ty = self.regioncx.name_regions( self.infcx.tcx, type_test.generic_kind.to_ty(self.infcx.tcx), ); @@ -324,7 +327,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { self.body.source.def_id().expect_local(), type_test_span, Some(origin), - self.name_regions(self.infcx.tcx, type_test.generic_kind), + self.regioncx.name_regions(self.infcx.tcx, type_test.generic_kind), lower_bound_region, )); } else { @@ -450,7 +453,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info); // Check if we can use one of the "nice region errors". - if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) { + if let (Some(f), Some(o)) = + (self.regioncx.to_error_region(fr), self.regioncx.to_error_region(outlived_fr)) + { let infer_err = self.infcx.err_ctxt(); let nice = NiceRegionError::new_from_span(&infer_err, self.mir_def_id(), cause.span, o, f); @@ -481,7 +486,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { d.note("meoow :c"); d } - (ConstraintCategory::Return(kind), true, false) if self.is_closure_fn_mut(fr) => { + (ConstraintCategory::Return(kind), true, false) + if self.regioncx.is_closure_fn_mut(fr) => + { self.report_fnmut_error(&errci, kind) } (ConstraintCategory::Assignment, true, false) @@ -736,7 +743,10 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // Only show an extra note if we can find an 'error region' for both of the region // variables. This avoids showing a noisy note that just mentions 'synthetic' regions // that don't help the user understand the error. - match (self.to_error_region(errci.fr), self.to_error_region(errci.outlived_fr)) { + match ( + self.regioncx.to_error_region(errci.fr), + self.regioncx.to_error_region(errci.outlived_fr), + ) { (Some(f), Some(o)) => { self.maybe_suggest_constrain_dyn_trait_impl(&mut diag, f, o, category); @@ -842,7 +852,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { outlived_fr: RegionVid, ) { if let (Some(f), Some(outlived_f)) = - (self.to_error_region(fr), self.to_error_region(outlived_fr)) + (self.regioncx.to_error_region(fr), self.regioncx.to_error_region(outlived_fr)) { if outlived_f.kind() != ty::ReStatic { return; @@ -1013,7 +1023,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } fn suggest_adding_lifetime_params(&self, diag: &mut Diag<'_>, sub: RegionVid, sup: RegionVid) { - let (Some(sub), Some(sup)) = (self.to_error_region(sub), self.to_error_region(sup)) else { + let (Some(sub), Some(sup)) = + (self.regioncx.to_error_region(sub), self.regioncx.to_error_region(sup)) + else { return; }; diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs index a00d39eea12d3..b8037fc83aad0 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs @@ -283,7 +283,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { /// named variants. #[instrument(level = "trace", skip(self))] fn give_name_from_error_region(&self, fr: RegionVid) -> Option { - let error_region = self.to_error_region(fr)?; + let error_region = self.regioncx.to_error_region(fr)?; let tcx = self.infcx.tcx; @@ -1015,7 +1015,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { &self, fr: RegionVid, ) -> Option { - let ty::ReEarlyParam(region) = self.to_error_region(fr)?.kind() else { + let ty::ReEarlyParam(region) = self.regioncx.to_error_region(fr)?.kind() else { return None; }; if region.is_named() { @@ -1050,7 +1050,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { &self, fr: RegionVid, ) -> Option { - let ty::ReEarlyParam(region) = self.to_error_region(fr)?.kind() else { + let ty::ReEarlyParam(region) = self.regioncx.to_error_region(fr)?.kind() else { return None; }; if region.is_named() { From 7ebb8596d118af0144d60d60c29135c1095a2bd5 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 09:10:13 +0200 Subject: [PATCH 02/14] Consistently taint root cx Instead of having to taint both infcx and root_cx, we always just taint infcx (which usually gets automatically tainted from emitting diagnostics), and at the end (before dropping the infcx) we move the taint over to the root_cx --- .../rustc_borrowck/src/diagnostics/mod.rs | 61 ++++++++++--------- .../src/diagnostics/opaque_types.rs | 8 +-- compiler/rustc_borrowck/src/lib.rs | 8 +-- compiler/rustc_borrowck/src/type_check/mod.rs | 1 - 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 56e0c74c85e97..a5b7a26a10ab4 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -116,11 +116,42 @@ impl<'infcx, 'tcx> BorrowckDiagnosticsBuffer<'infcx, 'tcx> { pub(crate) fn buffer_non_error(&mut self, diag: Diag<'infcx, ()>) { self.buffered_diags.push(BufferedDiag::NonError(diag)); } + pub(crate) fn buffer_error(&mut self, diag: Diag<'infcx>) { + self.buffered_diags.push(BufferedDiag::Error(diag)); + } + + pub(crate) fn emit_errors(&mut self) -> Option { + let mut res = None; + + // Buffer any move errors that we collected and de-duplicated. + for (_, (_, diag)) in std::mem::take(&mut self.buffered_move_errors) { + // We have already set tainted for this error, so just buffer it. + self.buffer_error(diag); + } + for (_, (mut diag, count)) in std::mem::take(&mut self.buffered_mut_errors) { + if count > 10 { + diag.note(format!("...and {} other attempted mutable borrows", count - 10)); + } + self.buffer_error(diag); + } + + if !self.buffered_diags.is_empty() { + self.buffered_diags.sort_by_key(|buffered_diag| buffered_diag.sort_span()); + for buffered_diag in self.buffered_diags.drain(..) { + match buffered_diag { + BufferedDiag::Error(diag) => res = Some(diag.emit()), + BufferedDiag::NonError(diag) => diag.emit(), + } + } + } + + res + } } impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { pub(crate) fn buffer_error(&mut self, diag: Diag<'infcx>) { - self.diags_buffer.buffered_diags.push(BufferedDiag::Error(diag)); + self.diags_buffer.buffer_error(diag); } pub(crate) fn buffer_non_error(&mut self, diag: Diag<'infcx, ()>) { @@ -152,34 +183,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { self.diags_buffer.buffered_mut_errors.insert(span, (diag, count)); } - pub(crate) fn emit_errors(&mut self) -> Option { - let mut res = self.infcx.tainted_by_errors(); - - // Buffer any move errors that we collected and de-duplicated. - for (_, (_, diag)) in std::mem::take(&mut self.diags_buffer.buffered_move_errors) { - // We have already set tainted for this error, so just buffer it. - self.buffer_error(diag); - } - for (_, (mut diag, count)) in std::mem::take(&mut self.diags_buffer.buffered_mut_errors) { - if count > 10 { - diag.note(format!("...and {} other attempted mutable borrows", count - 10)); - } - self.buffer_error(diag); - } - - if !self.diags_buffer.buffered_diags.is_empty() { - self.diags_buffer.buffered_diags.sort_by_key(|buffered_diag| buffered_diag.sort_span()); - for buffered_diag in self.diags_buffer.buffered_diags.drain(..) { - match buffered_diag { - BufferedDiag::Error(diag) => res = Some(diag.emit()), - BufferedDiag::NonError(diag) => diag.emit(), - } - } - } - - res - } - pub(crate) fn has_buffered_diags(&self) -> bool { self.diags_buffer.buffered_diags.is_empty() } diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs index 590c2c9c3965b..1df16595e2d86 100644 --- a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs +++ b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs @@ -28,11 +28,10 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } let infcx = self.infcx; - let mut guar = None; let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> = None; for error in errors { - guar = Some(match error { + match error { DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err) => err.report(infcx), DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam(err) => { infcx.dcx().emit_err(err) @@ -81,11 +80,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ) ), ), - }); + }; } - let guar = guar.unwrap(); - self.root_cx.set_tainted_by_errors(guar); - self.infcx.set_tainted_by_errors(guar); } /// Try to note when an opaque is involved in a borrowck error and that diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 94ad74b67b629..b9ff75421a52f 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -323,7 +323,6 @@ fn borrowck_collect_region_constraints<'tcx>( let input_promoted: &IndexSlice<_, _> = &promoted.borrow(); if let Some(e) = input_body.tainted_by_errors { infcx.set_tainted_by_errors(e); - root_cx.set_tainted_by_errors(e); } // Replace all regions with fresh inference variables. This @@ -571,15 +570,16 @@ fn borrowck_check_region_constraints<'tcx>( debug!("mbcx.used_mut: {:?}", mbcx.used_mut); mbcx.lint_unused_mut(); - if let Some(guar) = mbcx.emit_errors() { - mbcx.root_cx.set_tainted_by_errors(guar); - } let result = PropagatedBorrowCheckResults { closure_requirements: opt_closure_req, used_mut_upvars: mbcx.used_mut_upvars, }; + if let Some(guar) = mbcx.diags_buffer.emit_errors().or(infcx.tainted_by_errors()) { + root_cx.set_tainted_by_errors(guar); + } + if let Some(consumer) = &mut root_cx.consumer { consumer.insert_body( def, diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 450ac46aab521..3dc104e251cbe 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -198,7 +198,6 @@ pub(crate) fn type_check<'tcx>( debug!("encountered an error region; removing constraints!"); constraints.outlives_constraints = Default::default(); constraints.type_tests = Default::default(); - root_cx.set_tainted_by_errors(guar); infcx.set_tainted_by_errors(guar); } From f44093d3136d935c649f3560081941a1a4d5e462 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 09:10:13 +0200 Subject: [PATCH 03/14] Remove some unnecessary mutabilities --- compiler/rustc_borrowck/src/lib.rs | 2 +- compiler/rustc_borrowck/src/nll.rs | 2 +- compiler/rustc_borrowck/src/root_cx.rs | 2 +- compiler/rustc_borrowck/src/type_check/mod.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index b9ff75421a52f..e6d0163044947 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -718,7 +718,7 @@ impl<'tcx> Deref for BorrowckInferCtxt<'tcx> { } pub(crate) struct MirBorrowckCtxt<'a, 'infcx, 'tcx> { - root_cx: &'a mut BorrowCheckRootCtxt<'tcx>, + root_cx: &'a BorrowCheckRootCtxt<'tcx>, infcx: &'infcx BorrowckInferCtxt<'tcx>, body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>, diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index dd6eb17947577..b11f1868b64c9 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -111,7 +111,7 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( /// /// This may result in errors being reported. pub(crate) fn compute_regions<'tcx>( - root_cx: &mut BorrowCheckRootCtxt<'tcx>, + root_cx: &BorrowCheckRootCtxt<'tcx>, infcx: &BorrowckInferCtxt<'tcx>, body: &Body<'tcx>, location_table: &PoloniusLocationTable, diff --git a/compiler/rustc_borrowck/src/root_cx.rs b/compiler/rustc_borrowck/src/root_cx.rs index a082aba35b8a7..e406f158db216 100644 --- a/compiler/rustc_borrowck/src/root_cx.rs +++ b/compiler/rustc_borrowck/src/root_cx.rs @@ -72,7 +72,7 @@ impl<'tcx> BorrowCheckRootCtxt<'tcx> { } pub(super) fn used_mut_upvars( - &mut self, + &self, nested_body_def_id: LocalDefId, ) -> &SmallVec<[FieldIdx; 8]> { &self.propagated_borrowck_results[&nested_body_def_id].used_mut_upvars diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 3dc104e251cbe..b7bf4efcb6d38 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -95,7 +95,7 @@ mod relate_tys; /// - `move_data` -- move-data constructed when performing the maybe-init dataflow analysis /// - `location_map` -- map between MIR `Location` and `PointIndex` pub(crate) fn type_check<'tcx>( - root_cx: &mut BorrowCheckRootCtxt<'tcx>, + root_cx: &BorrowCheckRootCtxt<'tcx>, infcx: &BorrowckInferCtxt<'tcx>, body: &Body<'tcx>, promoted: &IndexSlice>, @@ -228,7 +228,7 @@ enum FieldAccessError { /// way, it accrues region constraints -- these can later be used by /// NLL region checking. struct TypeChecker<'a, 'tcx> { - root_cx: &'a mut BorrowCheckRootCtxt<'tcx>, + root_cx: &'a BorrowCheckRootCtxt<'tcx>, infcx: &'a BorrowckInferCtxt<'tcx>, last_span: Span, body: &'a Body<'tcx>, From 7102a6ca9fec52d986f03b6a5bd5d8bb315633ee Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 12:17:24 +0200 Subject: [PATCH 04/14] Typeck children always have the same param env as their parent --- compiler/rustc_ty_utils/src/ty.rs | 3 +++ .../ui/lifetimes/issue-76168-hr-outlives-3.rs | 5 ++--- .../issue-76168-hr-outlives-3.stderr | 20 ++++--------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs index 367e2e1b97059..c7a3d7252e1ce 100644 --- a/compiler/rustc_ty_utils/src/ty.rs +++ b/compiler/rustc_ty_utils/src/ty.rs @@ -149,6 +149,9 @@ fn adt_sizedness_constraint<'tcx>( /// See `ParamEnv` struct definition for details. fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> { + if tcx.is_typeck_child(def_id) { + return tcx.param_env(tcx.typeck_root_def_id(def_id)); + } // Compute the bounds on Self and the type parameters. let ty::InstantiatedPredicates { predicates, .. } = tcx.predicates_of(def_id).instantiate_identity(tcx); diff --git a/tests/ui/lifetimes/issue-76168-hr-outlives-3.rs b/tests/ui/lifetimes/issue-76168-hr-outlives-3.rs index 85eeb5d4c901e..77a1f0b4f25d6 100644 --- a/tests/ui/lifetimes/issue-76168-hr-outlives-3.rs +++ b/tests/ui/lifetimes/issue-76168-hr-outlives-3.rs @@ -8,10 +8,9 @@ async fn wrapper(f: F) //~| ERROR: expected a `FnOnce(&'a mut i32)` closure, found `i32` //~| ERROR: expected a `FnOnce(&'a mut i32)` closure, found `i32` where -F:, -for<'a> >::Output: Future + 'a, + F:, + for<'a> >::Output: Future + 'a, { - //~^ ERROR: expected a `FnOnce(&'a mut i32)` closure, found `i32` let mut i = 41; &mut i; } diff --git a/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr b/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr index 6da9f7380d597..bf70826165341 100644 --- a/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr +++ b/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr @@ -3,9 +3,9 @@ error[E0277]: expected a `FnOnce(&'a mut i32)` closure, found `i32` | LL | / async fn wrapper(f: F) ... | -LL | | F:, -LL | | for<'a> >::Output: Future + 'a, - | |__________________________________________________________________________^ expected an `FnOnce(&'a mut i32)` closure, found `i32` +LL | | F:, +LL | | for<'a> >::Output: Future + 'a, + | |______________________________________________________________________________^ expected an `FnOnce(&'a mut i32)` closure, found `i32` | = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` @@ -26,18 +26,6 @@ LL | async fn wrapper(f: F) = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0277]: expected a `FnOnce(&'a mut i32)` closure, found `i32` - --> $DIR/issue-76168-hr-outlives-3.rs:13:1 - | -LL | / { -LL | | -LL | | let mut i = 41; -LL | | &mut i; -LL | | } - | |_^ expected an `FnOnce(&'a mut i32)` closure, found `i32` - | - = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` - -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0277`. From 7cb94f0f36df282b6f2b6896b975f946069db233 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 12:38:01 +0200 Subject: [PATCH 05/14] Don't compute opaque types for typeck children. They are always the same as their parent --- compiler/rustc_ty_utils/src/opaque_types.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_ty_utils/src/opaque_types.rs b/compiler/rustc_ty_utils/src/opaque_types.rs index d95624b9a24d2..939b51fa7dbc0 100644 --- a/compiler/rustc_ty_utils/src/opaque_types.rs +++ b/compiler/rustc_ty_utils/src/opaque_types.rs @@ -316,6 +316,9 @@ fn opaque_types_defined_by<'tcx>( tcx: TyCtxt<'tcx>, item: LocalDefId, ) -> &'tcx ty::List { + if tcx.is_typeck_child(item.to_def_id()) { + return tcx.opaque_types_defined_by(tcx.local_parent(item)); + } let kind = tcx.def_kind(item); trace!(?kind); let mut collector = OpaqueTypeCollector::new(tcx, item); @@ -331,13 +334,6 @@ fn opaque_types_defined_by<'tcx>( | DefKind::AnonConst => { collector.collect_taits_declared_in_body(); } - // Closures and coroutines are type checked with their parent - // Note that we also support `SyntheticCoroutineBody` since we create - // a MIR body for the def kind, and some MIR passes (like promotion) - // may require doing analysis using its typing env. - DefKind::Closure | DefKind::InlineConst | DefKind::SyntheticCoroutineBody => { - collector.opaques.extend(tcx.opaque_types_defined_by(tcx.local_parent(item))); - } DefKind::AssocTy | DefKind::TyAlias | DefKind::GlobalAsm => {} DefKind::OpaqueTy | DefKind::Mod @@ -348,6 +344,15 @@ fn opaque_types_defined_by<'tcx>( | DefKind::Trait | DefKind::ForeignTy | DefKind::TraitAlias + + // Closures and coroutines are type checked with their parent + // Note that we also support `SyntheticCoroutineBody` since we create + // a MIR body for the def kind, and some MIR passes (like promotion) + // may require doing analysis using its typing env. + | DefKind::Closure + | DefKind::InlineConst + | DefKind::SyntheticCoroutineBody + | DefKind::TyParam | DefKind::ConstParam | DefKind::Ctor(_, _) From 7ccc34feb25be450833e6ad132a3672dbbcee706 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 16:37:31 +0200 Subject: [PATCH 06/14] Add a convenience helper for doing type ops --- compiler/rustc_borrowck/src/lib.rs | 9 +++++ .../src/type_check/constraint_conversion.rs | 8 ++--- .../src/type_check/free_region_relations.rs | 34 +++++++------------ .../src/type_check/liveness/trace.rs | 10 +++--- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index e6d0163044947..cc01207141a28 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -48,6 +48,7 @@ use rustc_mir_dataflow::points::DenseLocationMap; use rustc_mir_dataflow::{Analysis, EntryStates, Results, ResultsVisitor, visit_results}; use rustc_session::lint::builtin::{TAIL_EXPR_DROP_ORDER, UNUSED_MUT}; use rustc_span::{ErrorGuaranteed, Span, Symbol}; +use rustc_trait_selection::traits::query::type_op::{QueryTypeOp, TypeOp, TypeOpOutput}; use smallvec::SmallVec; use tracing::{debug, instrument}; @@ -707,6 +708,14 @@ impl<'tcx> BorrowckInferCtxt<'tcx> { next_region } + + fn fully_perform + TypeVisitable>>( + &self, + q: Q, + span: Span, + ) -> Result>, ErrorGuaranteed> { + self.param_env.and(q).fully_perform(&self.infcx, self.root_def_id, span) + } } impl<'tcx> Deref for BorrowckInferCtxt<'tcx> { diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index 4d93fa08fe0bd..82e623bb42535 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -11,7 +11,7 @@ use rustc_middle::ty::{ self, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, elaborate, fold_regions, }; use rustc_span::Span; -use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; +use rustc_trait_selection::traits::query::type_op::TypeOpOutput; use tracing::{debug, instrument}; use crate::constraints::OutlivesConstraint; @@ -287,11 +287,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { ConstraintCategory<'tcx>, )>, ) -> Ty<'tcx> { - match self.infcx.param_env.and(DeeplyNormalize { value: ty }).fully_perform( - self.infcx, - self.infcx.root_def_id, - self.span, - ) { + match self.infcx.fully_perform(DeeplyNormalize { value: ty }, self.span) { Ok(TypeOpOutput { output: ty, constraints, .. }) => { // FIXME(higher_ranked_auto): What should we do with the assumptions here? if let Some(QueryRegionConstraints { constraints, assumptions: _ }) = constraints { diff --git a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs index 6c7beb85f1e8c..62becc8e298f4 100644 --- a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs +++ b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs @@ -10,7 +10,7 @@ use rustc_middle::mir::ConstraintCategory; use rustc_middle::traits::query::OutlivesBound; use rustc_middle::ty::{self, RegionVid, Ty, TypeVisitableExt}; use rustc_span::{ErrorGuaranteed, Span}; -use rustc_trait_selection::traits::query::type_op::{self, TypeOp}; +use rustc_trait_selection::traits::query::type_op; use tracing::{debug, instrument}; use type_op::TypeOpOutput; @@ -242,15 +242,14 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { if let Some(c) = constraints_unnorm { constraints.push(c) } - let TypeOpOutput { output: norm_ty, constraints: constraints_normalize, .. } = - param_env - .and(DeeplyNormalize { value: ty }) - .fully_perform(self.infcx, self.infcx.root_def_id, span) - .unwrap_or_else(|guar| TypeOpOutput { - output: Ty::new_error(self.infcx.tcx, guar), - constraints: None, - error_info: None, - }); + let TypeOpOutput { output: norm_ty, constraints: constraints_normalize, .. } = self + .infcx + .fully_perform(DeeplyNormalize { value: ty }, span) + .unwrap_or_else(|guar| TypeOpOutput { + output: Ty::new_error(self.infcx.tcx, guar), + constraints: None, + error_info: None, + }); if let Some(c) = constraints_normalize { constraints.push(c) } @@ -299,9 +298,8 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { if matches!(tcx.def_kind(defining_ty_def_id), DefKind::AssocFn | DefKind::AssocConst { .. }) { for &(ty, _) in tcx.assumed_wf_types(tcx.local_parent(defining_ty_def_id)) { - let result: Result<_, ErrorGuaranteed> = param_env - .and(DeeplyNormalize { value: ty }) - .fully_perform(self.infcx, self.infcx.root_def_id, span); + let result: Result<_, ErrorGuaranteed> = + self.infcx.fully_perform(DeeplyNormalize { value: ty }, span); let Ok(TypeOpOutput { output: norm_ty, constraints: c, .. }) = result else { continue; }; @@ -354,11 +352,7 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { output: normalized_outlives, constraints: constraints_normalize, error_info: _, - }) = self.infcx.param_env.and(DeeplyNormalize { value: outlives }).fully_perform( - self.infcx, - self.infcx.root_def_id, - span, - ) + }) = self.infcx.fully_perform(DeeplyNormalize { value: outlives }, span) else { self.infcx.dcx().delayed_bug(format!("could not normalize {outlives:?}")); return; @@ -381,9 +375,7 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { ) -> Option<&'tcx QueryRegionConstraints<'tcx>> { let TypeOpOutput { output: bounds, constraints, .. } = self .infcx - .param_env - .and(type_op::ImpliedOutlivesBounds { ty }) - .fully_perform(self.infcx, self.infcx.root_def_id, span) + .fully_perform(type_op::ImpliedOutlivesBounds { ty }, span) .map_err(|_: ErrorGuaranteed| debug!("failed to compute implied bounds {:?}", ty)) .ok()?; debug!(?bounds, ?constraints); diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 840210496eb44..5a06f54cd2e41 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -15,7 +15,7 @@ use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::traits::ObligationCtxt; use rustc_trait_selection::traits::query::dropck_outlives; -use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOp, TypeOpOutput}; +use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOpOutput}; use tracing::debug; use crate::polonius; @@ -638,9 +638,9 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { ) -> DropData<'tcx> { debug!("compute_drop_data(dropped_ty={:?})", dropped_ty); - let op = typeck.infcx.param_env.and(DropckOutlives { dropped_ty }); + let goal = DropckOutlives { dropped_ty }; - match op.fully_perform(typeck.infcx, typeck.root_cx.root_def_id(), DUMMY_SP) { + match typeck.infcx.fully_perform(goal, DUMMY_SP) { Ok(TypeOpOutput { output, constraints, .. }) => { DropData { dropck_result: output, region_constraint_data: constraints } } @@ -655,7 +655,9 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { typeck.infcx.probe(|_| { let ocx = ObligationCtxt::new_with_diagnostics(&typeck.infcx); let errors = match dropck_outlives::compute_dropck_outlives_with_errors( - &ocx, op, span, + &ocx, + typeck.infcx.param_env.and(goal), + span, ) { Ok(_) => ocx.evaluate_obligations_error_on_ambiguity(), Err(e) => e, From b2a46c9d9a3773d48f1e20585e9c26bf60d3b59c Mon Sep 17 00:00:00 2001 From: Obei Sideg Date: Wed, 24 Jun 2026 18:12:42 +0300 Subject: [PATCH 07/14] Move `check_ffi_pure` into the attribute parser convert `FfiPureParser` to a full `AttributeParser` so its `finalize` can check for a sibling `#[ffi_const]` and reject `#[ffi_pure]` during attribute parsing, replacing `check_ffi_pure` in `rustc_passes`. --- .../src/attributes/link_attrs.rs | 41 ++++++++++++++----- compiler/rustc_attr_parsing/src/context.rs | 2 +- .../src/session_diagnostics.rs | 7 ++++ compiler/rustc_passes/src/check_attr.rs | 9 +--- compiler/rustc_passes/src/diagnostics.rs | 7 ---- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index f260863840080..0ff97b47fc696 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -14,11 +14,11 @@ use super::util::parse_single_integer; use crate::attributes::AttributeSafety; use crate::attributes::cfg::parse_cfg_entry; use crate::session_diagnostics::{ - AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic, - ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, - InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange, - LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, RawDylibOnlyWindows, - WholeArchiveNeedsStatic, + AsNeededCompatibility, BothFfiConstAndPure, BundleNeedsStatic, EmptyLinkName, + ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, + InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, + LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, + RawDylibOnlyWindows, UnusedMultiple, WholeArchiveNeedsStatic, }; pub(crate) struct LinkNameParser; @@ -563,16 +563,37 @@ impl NoArgsAttributeParser for FfiConstParser { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::FfiConst; } -pub(crate) struct FfiPureParser; -impl NoArgsAttributeParser for FfiPureParser { - const PATH: &[Symbol] = &[sym::ffi_pure]; +#[derive(Default)] +pub(crate) struct FfiPureParser { + first_span: Option, +} + +impl AttributeParser for FfiPureParser { + const ATTRIBUTES: AcceptMapping = + &[(&[sym::ffi_pure], template!(Word), unstable!(ffi_pure), |this, cx, args| { + let _ = cx.expect_no_args(args); + let span = cx.attr_span; + // `#[ffi_pure]` may only appear once; mirror the previous `OnDuplicate::Error`. + if let Some(first) = this.first_span { + cx.emit_err(UnusedMultiple { this: span, other: first, name: sym::ffi_pure }); + } else { + this.first_span = Some(span); + } + })]; const SAFETY: AttributeSafety = AttributeSafety::Unsafe { note: "`#[ffi_pure]` functions shall have no effects except for its return value, which shall not change across two consecutive function calls with the same parameters.", unsafe_since: None, }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); - const STABILITY: AttributeStability = unstable!(ffi_pure); - const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; + + fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { + let span = self.first_span?; + // `#[ffi_const]` functions cannot be `#[ffi_pure]`. + if cx.all_attrs.iter().any(|a| a.word_is(sym::ffi_const)) { + cx.emit_err(BothFfiConstAndPure { attr_span: span }); + } + Some(AttributeKind::FfiPure(span)) + } } pub(crate) struct RustcStdInternalSymbolParser; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 14e443c922711..16458f65b1116 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -143,6 +143,7 @@ attribute_parsers!( ConfusablesParser, ConstStabilityParser, DocParser, + FfiPureParser, MacroUseParser, NakedParser, OnConstParser, @@ -246,7 +247,6 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 494824a8393b3..74b028ecc35be 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -13,6 +13,13 @@ use rustc_target::spec::TargetTuple; use crate::AttributeTemplate; use crate::context::Suggestion; +#[derive(Diagnostic)] +#[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)] +pub(crate) struct BothFfiConstAndPure { + #[primary_span] + pub attr_span: Span, +} + #[derive(Diagnostic)] #[diag("{$attr_str} attribute cannot have empty value")] pub(crate) struct DocAliasEmpty<'a> { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 079fdd923ac0d..c91c1776c81f4 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -219,7 +219,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::NonExhaustive(attr_span) => { self.check_non_exhaustive(*attr_span, span, target, item) } - &AttributeKind::FfiPure(attr_span) => self.check_ffi_pure(attr_span, attrs), AttributeKind::MayDangle(attr_span) => self.check_may_dangle(hir_id, *attr_span), AttributeKind::Link(_, attr_span) => self.check_link(hir_id, *attr_span, target), AttributeKind::MacroExport { span, .. } => { @@ -271,6 +270,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::ExportStable => (), AttributeKind::Feature(..) => (), AttributeKind::FfiConst => (), + AttributeKind::FfiPure(..) => (), AttributeKind::Fundamental => (), AttributeKind::Ignore { .. } => (), AttributeKind::InstructionSet(..) => (), @@ -1112,13 +1112,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_ffi_pure(&self, attr_span: Span, attrs: &[Attribute]) { - if find_attr!(attrs, FfiConst) { - // `#[ffi_const]` functions cannot be `#[ffi_pure]` - self.dcx().emit_err(diagnostics::BothFfiConstAndPure { attr_span }); - } - } - /// Checks if `#[may_dangle]` is applied to a lifetime or type generic parameter in `Drop` impl. fn check_may_dangle(&self, hir_id: HirId, attr_span: Span) { if let hir::Node::GenericParam(param) = self.tcx.hir_node(hir_id) diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index f6c4b0cf77d12..2576b84716664 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -153,13 +153,6 @@ pub(crate) struct DocMaskedNotExternCrateSelf { pub item_span: Span, } -#[derive(Diagnostic)] -#[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)] -pub(crate) struct BothFfiConstAndPure { - #[primary_span] - pub attr_span: Span, -} - #[derive(Diagnostic)] #[diag("`#[optimize(none)]` cannot be used with `#[inline]` attributes")] pub(crate) struct BothOptimizeNoneAndInline { From c19c46298276396bb7b27498fd5f9877ec8fd844 Mon Sep 17 00:00:00 2001 From: Obei Sideg Date: Thu, 25 Jun 2026 00:15:36 +0300 Subject: [PATCH 08/14] Use `NoArgsAttributeParser` for parsing `#[ffi_pure]` --- .../src/attributes/link_attrs.rs | 30 +++++-------------- .../rustc_attr_parsing/src/attributes/mod.rs | 26 ++++++++++++++-- compiler/rustc_attr_parsing/src/context.rs | 2 +- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 0ff97b47fc696..713d8db1524e0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -18,7 +18,7 @@ use crate::session_diagnostics::{ ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, - RawDylibOnlyWindows, UnusedMultiple, WholeArchiveNeedsStatic, + RawDylibOnlyWindows, WholeArchiveNeedsStatic, }; pub(crate) struct LinkNameParser; @@ -563,36 +563,22 @@ impl NoArgsAttributeParser for FfiConstParser { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::FfiConst; } -#[derive(Default)] -pub(crate) struct FfiPureParser { - first_span: Option, -} - -impl AttributeParser for FfiPureParser { - const ATTRIBUTES: AcceptMapping = - &[(&[sym::ffi_pure], template!(Word), unstable!(ffi_pure), |this, cx, args| { - let _ = cx.expect_no_args(args); - let span = cx.attr_span; - // `#[ffi_pure]` may only appear once; mirror the previous `OnDuplicate::Error`. - if let Some(first) = this.first_span { - cx.emit_err(UnusedMultiple { this: span, other: first, name: sym::ffi_pure }); - } else { - this.first_span = Some(span); - } - })]; +pub(crate) struct FfiPureParser; +impl NoArgsAttributeParser for FfiPureParser { + const PATH: &[Symbol] = &[sym::ffi_pure]; const SAFETY: AttributeSafety = AttributeSafety::Unsafe { note: "`#[ffi_pure]` functions shall have no effects except for its return value, which shall not change across two consecutive function calls with the same parameters.", unsafe_since: None, }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); + const STABILITY: AttributeStability = unstable!(ffi_pure); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; - fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { - let span = self.first_span?; + fn finalize_check(attr_span: Span, cx: &FinalizeContext<'_, '_>) { // `#[ffi_const]` functions cannot be `#[ffi_pure]`. if cx.all_attrs.iter().any(|a| a.word_is(sym::ffi_const)) { - cx.emit_err(BothFfiConstAndPure { attr_span: span }); + cx.emit_err(BothFfiConstAndPure { attr_span }); } - Some(AttributeKind::FfiPure(span)) } } diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 5c64e9f2eaed6..e04341508bb7e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -147,6 +147,14 @@ pub(crate) trait SingleAttributeParser: 'static { /// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`] fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option; + + /// Optional cross-attribute validation, run once during finalization after all + /// attributes on the item have been parsed. Unlike [`convert`](Self::convert), this + /// has access to the sibling attributes via [`FinalizeContext::all_attrs`], so it can + /// reject incompatible combinations. `attr_span` is the span of this attribute. + /// + /// Defaults to a no-op. + fn finalize_check(_attr_span: Span, _cx: &FinalizeContext<'_, '_>) {} } /// Use in combination with [`SingleAttributeParser`]. @@ -177,8 +185,10 @@ impl AttributeParser for Single { const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; const SAFETY: AttributeSafety = T::SAFETY; - fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option { - Some(self.1?.0) + fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { + let (kind, span) = self.1?; + T::finalize_check(span, cx); + Some(kind) } } @@ -258,6 +268,14 @@ pub(crate) trait NoArgsAttributeParser: 'static { /// Create the [`AttributeKind`] given attribute's [`Span`]. const CREATE: fn(Span) -> AttributeKind; + + /// Optional cross-attribute validation, run once during finalization after all + /// attributes on the item have been parsed. Has access to the sibling attributes via + /// [`FinalizeContext::all_attrs`], so it can reject incompatible combinations. + /// `attr_span` is the span of this attribute. + /// + /// Defaults to a no-op. + fn finalize_check(_attr_span: Span, _cx: &FinalizeContext<'_, '_>) {} } pub(crate) struct WithoutArgs(PhantomData); @@ -280,6 +298,10 @@ impl SingleAttributeParser for WithoutArgs { let _ = cx.expect_no_args(args); Some(T::CREATE(cx.attr_span)) } + + fn finalize_check(attr_span: Span, cx: &FinalizeContext<'_, '_>) { + T::finalize_check(attr_span, cx) + } } type ConvertFn = fn(ThinVec, Span) -> AttributeKind; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 16458f65b1116..14e443c922711 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -143,7 +143,6 @@ attribute_parsers!( ConfusablesParser, ConstStabilityParser, DocParser, - FfiPureParser, MacroUseParser, NakedParser, OnConstParser, @@ -247,6 +246,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, From ba2c9baccbb22e97aefe46724a36f2f95908250e Mon Sep 17 00:00:00 2001 From: joboet Date: Thu, 25 Jun 2026 15:51:06 +0200 Subject: [PATCH 09/14] std: truncate thread names on NetBSD --- library/std/src/sys/thread/unix.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index eb3fcff05ea9e..dd7b7940de510 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -392,6 +392,7 @@ pub fn current_os_id() -> Option { target_os = "vxworks", target_os = "cygwin", target_vendor = "apple", + target_os = "netbsd", ))] fn truncate_cstr(cstr: &CStr) -> [libc::c_char; MAX_WITH_NUL] { let mut result = [0; MAX_WITH_NUL]; @@ -463,7 +464,12 @@ pub fn set_name(name: &CStr) { #[cfg(target_os = "netbsd")] pub fn set_name(name: &CStr) { + // See https://github.com/NetBSD/src/blob/8d40872b4c550a802379f3b9c22a40212d5e149d/lib/libpthread/pthread.h#L281 + // FIXME: move to libc. + const PTHREAD_MAX_NAMELEN_NP: usize = 32; + unsafe { + let name = truncate_cstr::<{ PTHREAD_MAX_NAMELEN_NP }>(name); let res = libc::pthread_setname_np( libc::pthread_self(), c"%s".as_ptr(), From 6b9de05805cc1301e26ca9214fc2e653873f382c Mon Sep 17 00:00:00 2001 From: Emmanuel Ugwu Date: Fri, 26 Jun 2026 03:23:03 +0000 Subject: [PATCH 10/14] Attribute docs `deprecated` , `warn`, `allow`, `cfg`, `deny`, and `forbid` * Added documentation for allow, cfg, deny, forbid, deprecated and warn attribute Signed-off-by: Emmanuel Ugwu * address review feedback Signed-off-by: Emmanuel Ugwu * address review feedback Signed-off-by: Emmanuel Ugwu * address review feedback Signed-off-by: Emmanuel Ugwu * address feedback Signed-off-by: Emmanuel Ugwu * add the right deprecated link Signed-off-by: Emmanuel Ugwu * add the right deprecated link Signed-off-by: Emmanuel Ugwu * fix typo Signed-off-by: Emmanuel Ugwu * fix phrasing Signed-off-by: Emmanuel Ugwu * fix phrasing Signed-off-by: Emmanuel Ugwu * address feedback Signed-off-by: Emmanuel Ugwu * address feedback and add link to rustc book Signed-off-by: Emmanuel Ugwu * address feedback Signed-off-by: Emmanuel Ugwu --- library/std/src/attribute_docs.rs | 250 ++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) diff --git a/library/std/src/attribute_docs.rs b/library/std/src/attribute_docs.rs index 1d8dddb7ca54a..e8d36483f3139 100644 --- a/library/std/src/attribute_docs.rs +++ b/library/std/src/attribute_docs.rs @@ -85,3 +85,253 @@ /// [`unused_must_use`]: ../rustc/lints/listing/warn-by-default.html#unused-must-use /// [the `must_use` attribute]: ../reference/attributes/diagnostics.html#the-must_use-attribute mod must_use_attribute {} + +#[doc(attribute = "allow")] +// +/// The `allow` attribute suppresses lint diagnostics that would otherwise produce +/// warnings or errors. It can be used on any lint or lint group (except those +/// set to `forbid`). +/// +/// ```rust +/// #[allow(dead_code)] +/// fn unused_function() { +/// // ... +/// } +/// +/// fn main() { +/// // `unused_function` does not generate a compiler warning. +/// } +/// ``` +/// +/// Without `#[allow(dead_code)]`, the example above would emit: +/// +/// ```text +/// warning: function `unused_function` is never used +/// --> main.rs:1:4 +/// | +/// 1 | fn unused_function() { +/// | ^^^^^^^^^^^^^^^ +/// | +/// = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default +/// +/// warning: 1 warning emitted +/// ``` +/// +/// Multiple lints can be set to `allow` at once with commas: +/// +/// ```rust +/// #[allow(unused_variables, unused_mut)] +/// fn main() { +/// let mut x: u32 = 42; +/// } +/// ``` +/// +/// This is mostly used to prevent lint warnings or errors while still under development. +/// +/// It cannot override a lint that has been set to `forbid`. +/// +/// It's also important to consider that overusing `allow` could make code harder to maintain +/// and possibly hide issues. To mitigate this issue, using the `expect` attribute is preferred. +/// +/// `allow` can be overridden by `warn`, `deny`, and `forbid`. +/// +/// The lint checks supported by rustc can be found via `rustc -W help`, +/// along with their default settings and are documented in [the `rustc` book]. +/// +/// [the `rustc` book]: ../rustc/lints/listing/index.html +/// +/// For more information, see the Reference on [the `allow` attribute]. +/// +/// [the `allow` attribute]: ../reference/attributes/diagnostics.html#lint-check-attributes +mod allow_attribute {} + +#[doc(attribute = "cfg")] +// +/// Used for conditional compilation. +/// +/// The `cfg` attribute allows compiling an item under specific conditions, otherwise it +/// will be ignored. +/// +/// ```rust +/// // Only compiles this function for Linux. +/// #[cfg(target_os = "linux")] +/// fn platform_specific() { +/// println!("Running on Linux"); +/// } +/// +/// // Only compiles this function if not for Linux. +/// #[cfg(not(target_os = "linux"))] +/// fn platform_specific() { +/// println!("Running on something else"); +/// } +/// ``` +/// +/// Depending on the platform you're targeting, only one of these two functions will be considered +/// during the compilation. +/// +/// Conditions can also be combined with `all(...)`, `any(...)`, and `not(...)`. +/// +/// * `all`: True if all given predicates are true. +/// * `any`: True if at least one of the given predicates is true. +/// * `not`: True if the predicate is false and false if the predicate is true. +/// +/// ```rust +/// #[cfg(all(unix, target_pointer_width = "64"))] +/// fn unix_64bit() { +/// } +/// ``` +/// +/// If you want to use this mechanism in an `if` condition in your code, you +/// can use the [`cfg!`] macro. To conditionally apply an attribute, +/// see [`cfg_attr`]. +/// +/// For more information, see the Reference on [the `cfg` attribute]. +/// +/// [`cfg_attr`]: ../reference/conditional-compilation.html#the-cfg_attr-attribute +/// [the `cfg` attribute]: ../reference/conditional-compilation.html#the-cfg-attribute +mod cfg_attribute {} + +#[doc(attribute = "deny")] +// +/// Emits an error, preventing the compilation from finishing, when a lint check has failed. +/// This is useful for enforcing rules or preventing certain patterns: +/// +/// ```rust,compile_fail +/// #[deny(unused)] +/// fn foo() { +/// let x = 42; // Emits an error because x is unused. +/// } +/// ``` +/// +/// `deny` can be overridden by `allow`, `warn`, and `forbid`: +/// +/// ```rust +/// #![deny(unused)] +/// +/// #[allow(unused)] // We override the `deny` for this function. +/// fn foo() { +/// let x = 42; // No lint emitted even though `x` is unused. +/// } +/// ``` +/// +/// Multiple lints can also be set to `deny` at once: +/// +/// ```rust,compile_fail +/// #![deny(unused_imports, unused_variables)] +/// use std::collections::*; +/// +/// fn main() { +/// let mut x = 10; +/// } +/// ``` +/// +/// The lint checks supported by rustc can be found via `rustc -W help`, +/// along with their default settings and are documented in [the `rustc` book]. +/// +/// [the `rustc` book]: ../rustc/lints/listing/index.html +/// +/// For more information, see the Reference on [the `deny` attribute]. +/// +/// [the `deny` attribute]: ../reference/attributes/diagnostics.html#lint-check-attributes +mod deny_attribute {} + +#[doc(attribute = "forbid")] +// +/// Emits an error, preventing the compilation from finishing, when a lint check has failed. +/// +/// A lint set to `forbid` cannot be overridden by `allow` or `warn`. +/// Attempting either will result in a compilation error. Writing `#[deny(...)]` on the same lint inside a +/// `forbid` scope is permitted, but has no effect; the lint remains at the `forbid` level. +/// +/// This is useful for enforcing strict policies that should not be relaxed +/// anywhere in the codebase. Example: +/// +/// ```rust +/// #![forbid(unsafe_code)] +/// +/// // This would cause a compilation error if uncommented: +/// // #[allow(unsafe_code)] // error: cannot override `forbid` +/// ``` +/// +/// Multiple lints can be set to `forbid` at once: +/// +/// ```rust +/// #![forbid(unsafe_code, unused)] +/// ``` +/// +/// The lint checks supported by rustc can be found via `rustc -W help`, +/// along with their default settings and are documented in [the `rustc` book]. +/// +/// [the `rustc` book]: ../rustc/lints/listing/index.html +/// +/// For more information, see the Reference on [the `forbid` attribute]. +/// +/// [the `forbid` attribute]: ../reference/attributes/diagnostics.html#lint-check-attributes +mod forbid_attribute {} + +#[doc(attribute = "deprecated")] +// +/// Emits a warning during compilation when an item with this attribute is used. +/// `since` and `note` are optional fields giving more detail about why the item is deprecated. +/// +/// * `since`: the version since when the item is deprecated. +/// * `note`: the reason why an item is deprecated. +/// +/// Example: +/// +/// ```rust +/// #[deprecated(since = "1.0.0", note = "Use bar instead")] +/// struct Foo; +/// struct Bar; +/// ``` +/// +/// `deprecated` attribute helps developers transition away from old code by providing warnings when +/// deprecated items are used. Note that during `Cargo` builds, warnings on dependencies get silenced +/// by default, so you may not see a deprecation warning unless you build that dependency directly. +/// +/// For more information, see the Reference on [the `deprecated` attribute]. +/// +/// [the `deprecated` attribute]: ../reference/attributes/diagnostics.html#the-deprecated-attribute +mod deprecated_attribute {} + +#[doc(attribute = "warn")] +// +/// Emits a warning during compilation when a lint check failed. +/// +/// Unlike `deny` or `forbid`, `warn` does not produce a hard error: the compilation continues, but +/// the compiler emits a warning message. `warn` can be overridden by `allow`, `deny`, and `forbid`. +/// +/// Example: +/// +/// ```rust,compile_fail +/// #![allow(unused)] +/// +/// #[warn(unused)] // We override the allowed `unused` lint. +/// fn foo() { +/// // This lint warns by default even without #[warn(unused)] being explicitly set +/// let x = 42; // warning: unused variable `x` +/// } +/// ``` +/// +/// +/// Many lints, including `unused`, are already set to `warn` by default so this attribute is +/// mainly useful for lints that are normally `allow` by default. +/// +/// Multiple lints can be set to `warn` at once: +/// +/// ```rust,compile_fail +/// #[warn(unused_mut, unused_variables)] +/// fn main() { +/// let mut x = 42; +/// } +/// ``` +/// +/// The lint checks supported by rustc can be found via `rustc -W help`, +/// along with their default settings and are documented in [the `rustc` book]. +/// +/// [the `rustc` book]: ../rustc/lints/listing/index.html +/// +/// For more information, see the Reference on [the `warn` attribute]. +/// +/// [the `warn` attribute]: ../reference/attributes/diagnostics.html#lint-check-attributes +mod warn_attribute {} From 9b188dd6c50ed504ed0eaf80dfe8f4ef1b56cf5c Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sat, 23 May 2026 15:27:28 -0700 Subject: [PATCH 11/14] cg_LLVM: Stop needing an alloca for volatile loads And while I'm here, improve the tests to check that the unaligned ones are actually unaligned, since `unaligned_volatile_load::` doesn't actually test anything. --- compiler/rustc_codegen_gcc/src/builder.rs | 3 +- .../rustc_codegen_gcc/src/intrinsic/mod.rs | 7 +- compiler/rustc_codegen_llvm/src/builder.rs | 4 +- compiler/rustc_codegen_llvm/src/context.rs | 2 +- .../rustc_codegen_llvm/src/debuginfo/gdb.rs | 6 +- compiler/rustc_codegen_llvm/src/intrinsic.rs | 46 ++++--- .../rustc_codegen_ssa/src/traits/builder.rs | 2 +- tests/codegen-llvm/i128-x86-align.rs | 6 +- tests/codegen-llvm/intrinsics/volatile.rs | 121 +++++++++++++++++- 9 files changed, 161 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index 8ae4dedff8f28..6cbc0054cc015 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -993,7 +993,8 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { loaded_value.to_rvalue() } - fn volatile_load(&mut self, ty: Type<'gcc>, ptr: RValue<'gcc>) -> RValue<'gcc> { + fn volatile_load(&mut self, ty: Type<'gcc>, ptr: RValue<'gcc>, _: Align) -> RValue<'gcc> { + // FIXME(antoyo): set alignment. let ptr = self.context.new_cast(self.location, ptr, ty.make_volatile().make_pointer()); // (FractalFir): We insert a local here, to ensure this volatile load can't move across // blocks. diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index a12116d5b9d39..78a4c7e88c895 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -5,7 +5,7 @@ mod simd; use std::iter; use gccjit::{ComparisonOp, Function, FunctionType, RValue, ToRValue, Type, UnaryOp}; -use rustc_abi::{BackendRepr, HasDataLayout, WrappingRange}; +use rustc_abi::{Align, BackendRepr, HasDataLayout, WrappingRange}; use rustc_codegen_ssa::base::wants_msvc_seh; use rustc_codegen_ssa::common::IntPredicate; use rustc_codegen_ssa::errors::InvalidMonomorphization; @@ -368,8 +368,9 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc sym::volatile_load | sym::unaligned_volatile_load => { let ptr = args[0].immediate(); - let load = self.volatile_load(result.layout.gcc_type(self), ptr); - // FIXME(antoyo): set alignment. + let abi_align = result_layout.align.abi; + let ptr_align = if name == sym::volatile_load { abi_align } else { Align::ONE }; + let load = self.volatile_load(result.layout.gcc_type(self), ptr, ptr_align); if let BackendRepr::Scalar(scalar) = result.layout.backend_repr { self.to_immediate_scalar(load, scalar) } else { diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index afb6985d21a95..7535c41cd1ed3 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -682,9 +682,9 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { } } - fn volatile_load(&mut self, ty: &'ll Type, ptr: &'ll Value) -> &'ll Value { + fn volatile_load(&mut self, ty: &'ll Type, ptr: &'ll Value, align: Align) -> &'ll Value { unsafe { - let load = llvm::LLVMBuildLoad2(self.llbuilder, ty, ptr, UNNAMED); + let load = self.load(ty, ptr, align); llvm::LLVMSetVolatile(load, llvm::TRUE); load } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 84661f5160b14..6198a98e5f7ae 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -978,7 +978,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { } fn intrinsic_call_expects_place_always(&self, name: Symbol) -> bool { - matches!(name, sym::volatile_load | sym::unaligned_volatile_load | sym::black_box) + matches!(name, sym::black_box) } } diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs index f88f0c2fb0994..f5b843b4e3e74 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs @@ -1,5 +1,6 @@ // .debug_gdb_scripts binary section. +use rustc_abi::Align; use rustc_codegen_ssa::base::collect_debugger_visualizers_transitive; use rustc_codegen_ssa::traits::*; use rustc_hir::attrs::DebuggerVisualizerType; @@ -18,10 +19,7 @@ pub(crate) fn insert_reference_to_gdb_debug_scripts_section_global(bx: &mut Buil let gdb_debug_scripts_section = get_or_insert_gdb_debug_scripts_section_global(bx); // Load just the first byte as that's all that's necessary to force // LLVM to keep around the reference to the global. - let volatile_load_instruction = bx.volatile_load(bx.type_i8(), gdb_debug_scripts_section); - unsafe { - llvm::LLVMSetAlignment(volatile_load_instruction, 1); - } + bx.volatile_load(bx.type_i8(), gdb_debug_scripts_section, Align::ONE); } } diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 1caa95f369360..98e2cbac03dfe 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -354,25 +354,39 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { } sym::volatile_load | sym::unaligned_volatile_load => { - let result = PlaceRef { - val: result_place.unwrap(), - layout: result_layout, - }; - + // Note that we cannot just load the `llvm_type` because we should never load non-scalars. + // Trying to do so blows up horribly in some cases -- for example loading a + // `MaybeUninint<&dyn Trait>` would load as `{ [i64x2] }` which gives assertions later + // (if we're lucky) from things not being pointers that ought to be. let ptr = args[0].immediate(); - let load = self.volatile_load(result_layout.llvm_type(self), ptr); - let align = if name == sym::unaligned_volatile_load { - 1 + let abi_align = result_layout.align.abi; + let ptr_align = if name == sym::volatile_load { abi_align } else { Align::ONE }; + if result_layout.is_zst() { + return IntrinsicResult::Operand(OperandValue::ZeroSized); + } else if let BackendRepr::Scalar(scalar) = result_layout.backend_repr { + let load = self.volatile_load(self.type_from_scalar(scalar), ptr, ptr_align); + self.to_immediate_scalar(load, scalar) } else { - result_layout.align.bytes() as u32 - }; - unsafe { - llvm::LLVMSetAlignment(load, align); - } - if !result_layout.is_zst() { - self.store_to_place(load, result.val); + // One day Rust will probably want to define how we split up a volatile load + // of something that's *not* just an ordinary scalar, but for now we can just + // use an LLVM integer type of the correct width and let it split it however. + let llty = self.type_ix(result_layout.size.bits()); + let temp = if let Some(result_place) = result_place { + PlaceRef { + val: result_place, + layout: result_layout, + } + } else { + PlaceRef::alloca(self, result_layout) + }; + let llval = self.volatile_load(llty, ptr, ptr_align); + self.store(llval, temp.val.llval, abi_align); + return if result_place.is_none() { + IntrinsicResult::Operand(self.load_operand(temp).val) + } else { + IntrinsicResult::WroteIntoPlace + }; } - return IntrinsicResult::WroteIntoPlace; } sym::volatile_store => { let dst = args[0].deref(self.cx()); diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index d68549c6871f4..39c2529152566 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -242,7 +242,7 @@ pub trait BuilderMethods<'a, 'tcx>: fn alloca_with_ty(&mut self, layout: TyAndLayout<'tcx>) -> Self::Value; fn load(&mut self, ty: Self::Type, ptr: Self::Value, align: Align) -> Self::Value; - fn volatile_load(&mut self, ty: Self::Type, ptr: Self::Value) -> Self::Value; + fn volatile_load(&mut self, ty: Self::Type, ptr: Self::Value, align: Align) -> Self::Value; fn atomic_load( &mut self, ty: Self::Type, diff --git a/tests/codegen-llvm/i128-x86-align.rs b/tests/codegen-llvm/i128-x86-align.rs index 75802b0c5056a..998821d17e80c 100644 --- a/tests/codegen-llvm/i128-x86-align.rs +++ b/tests/codegen-llvm/i128-x86-align.rs @@ -5,8 +5,6 @@ // while rustc wants it to have 16 byte alignment. This test checks that we handle this // correctly. -// CHECK: %ScalarPair = type { i32, [3 x i32], i128 } - #![feature(core_intrinsics)] #[repr(C)] @@ -62,8 +60,8 @@ pub fn load_volatile(x: &ScalarPair) -> ScalarPair { // CHECK-SAME: dereferenceable(32) %_0, // CHECK-SAME: align 16 // CHECK-SAME: dereferenceable(32) %x - // CHECK: [[LOAD:%.*]] = load volatile %ScalarPair, ptr %x, align 16 - // CHECK-NEXT: store %ScalarPair [[LOAD]], ptr %_0, align 16 + // CHECK: [[LOAD:%.*]] = load volatile i256, ptr %x, align 16 + // CHECK-NEXT: store i256 [[LOAD]], ptr %_0, align 16 // CHECK-NEXT: ret void unsafe { std::intrinsics::volatile_load(x) } } diff --git a/tests/codegen-llvm/intrinsics/volatile.rs b/tests/codegen-llvm/intrinsics/volatile.rs index 2dea5ecb2ca92..4ba4c5edd2df6 100644 --- a/tests/codegen-llvm/intrinsics/volatile.rs +++ b/tests/codegen-llvm/intrinsics/volatile.rs @@ -5,6 +5,11 @@ use std::intrinsics; +#[repr(align(32))] +pub struct CustomZst; + +type UninitFatPointer = std::mem::MaybeUninit<&'static dyn std::fmt::Debug>; + // CHECK-LABEL: @volatile_copy_memory #[no_mangle] pub unsafe fn volatile_copy_memory(a: *mut u8, b: *const u8) { @@ -28,8 +33,62 @@ pub unsafe fn volatile_set_memory(a: *mut u8, b: u8) { // CHECK-LABEL: @volatile_load #[no_mangle] -pub unsafe fn volatile_load(a: *const u8) -> u8 { - // CHECK: load volatile +pub unsafe fn volatile_load(a: *const u16) -> u16 { + // CHECK: [[TEMP:%.+]] = load volatile i16, ptr %a + // CHECK-SAME: align 2{{,|$}} + // CHECK-NEXT: ret i16 [[TEMP]] + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_load_bool +#[no_mangle] +pub unsafe fn volatile_load_bool(a: *const bool) -> bool { + // CHECK: [[TEMP:%.+]] = load volatile i8, ptr %a + // CHECK-SAME: align 1{{,|$}} + // CHECK: [[TRUNC:%.+]] = trunc nuw i8 [[TEMP]] to i1 + // CHECK: ret i1 [[TRUNC]] + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_load_zst +#[no_mangle] +pub unsafe fn volatile_load_zst(a: *const CustomZst) -> CustomZst { + // CHECK: start: + // CHECK-NEXT: ret void + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_load_array +// CHECK-SAME: ptr{{.+}}sret([16 x i8]){{.+}}%_0 +#[no_mangle] +pub unsafe fn volatile_load_array(a: *const [u16; 8]) -> [u16; 8] { + // CHECK-NOT: alloca + // CHECK: [[TEMP:%.+]] = load volatile i128, ptr %a, + // CHECK-SAME: align 2{{,|$}} + // CHECK: store i128 [[TEMP]], ptr %_0, + // CHECK-SAME: align 2{{,|$}} + // CHECK-NEXT: ret void + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_load_fat +#[no_mangle] +pub unsafe fn volatile_load_fat(a: *const UninitFatPointer) -> UninitFatPointer { + // CHECK: [[ALLOCA:%.+]] = alloca + // CHECK-SAME: [[SIZE:4|8|16]] x i8 + // CHECK-SAME: align [[ALIGN:2|4|8]] + + // CHECK: [[TEMP:%.+]] = load volatile [[INT:i32|i64|i128]], ptr %a, + // CHECK-SAME: align [[ALIGN]]{{,|$}} + // CHECK: store [[INT]] [[TEMP]], ptr [[ALLOCA]], + // CHECK-SAME: align [[ALIGN]]{{,|$}} + + // CHECK: [[T0:%.+]] = load ptr, ptr [[ALLOCA]], align [[ALIGN]] + // CHECK: [[T1:%.+]] = getelementptr inbounds i8, ptr [[ALLOCA]] + // CHECK: [[T2:%.+]] = load ptr, ptr [[T1]], align [[ALIGN]] + // CHECK: [[P1:%.+]] = insertvalue { ptr, ptr } poison, ptr [[T0]], 0 + // CHECK: [[P2:%.+]] = insertvalue { ptr, ptr } [[P1]], ptr [[T2]], 1 + // CHECK: ret { ptr, ptr } [[P2]] intrinsics::volatile_load(a) } @@ -42,8 +101,62 @@ pub unsafe fn volatile_store(a: *mut u8, b: u8) { // CHECK-LABEL: @unaligned_volatile_load #[no_mangle] -pub unsafe fn unaligned_volatile_load(a: *const u8) -> u8 { - // CHECK: load volatile +pub unsafe fn unaligned_volatile_load(a: *const u16) -> u16 { + // CHECK: [[TEMP:%.+]] = load volatile i16, ptr %a + // CHECK-SAME: align 1{{,|$}} + // CHECK-NEXT: ret i16 [[TEMP]] + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_load_bool +#[no_mangle] +pub unsafe fn unaligned_volatile_load_bool(a: *const bool) -> bool { + // CHECK: [[TEMP:%.+]] = load volatile i8, ptr %a + // CHECK-SAME: align 1{{,|$}} + // CHECK: [[TRUNC:%.+]] = trunc nuw i8 [[TEMP]] to i1 + // CHECK: ret i1 [[TRUNC]] + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_load_zst +#[no_mangle] +pub unsafe fn unaligned_volatile_load_zst(a: *const CustomZst) -> CustomZst { + // CHECK: start: + // CHECK-NEXT: ret void + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_load_array +// CHECK-SAME: ptr{{.+}}sret([16 x i8]){{.+}}%_0, +#[no_mangle] +pub unsafe fn unaligned_volatile_load_array(a: *const [u16; 8]) -> [u16; 8] { + // CHECK-NOT: alloca + // CHECK: [[TEMP:%.+]] = load volatile i128, ptr %a, + // CHECK-SAME: align 1{{,|$}} + // CHECK: store i128 [[TEMP]], ptr %_0, + // CHECK-SAME: align 2{{,|$}} + // CHECK-NEXT: ret void + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_load_fat +#[no_mangle] +pub unsafe fn unaligned_volatile_load_fat(a: *const UninitFatPointer) -> UninitFatPointer { + // CHECK: [[ALLOCA:%.+]] = alloca + // CHECK-SAME: [[SIZE]] x i8 + // CHECK-SAME: align [[ALIGN]] + + // CHECK: [[TEMP:%.+]] = load volatile [[INT]], ptr %a, + // CHECK-SAME: align 1{{,|$}} + // CHECK: store [[INT]] [[TEMP]], ptr [[ALLOCA]], + // CHECK-SAME: align [[ALIGN]]{{,|$}} + + // CHECK: [[T0:%.+]] = load ptr, ptr [[ALLOCA]], align [[ALIGN]] + // CHECK: [[T1:%.+]] = getelementptr inbounds i8, ptr [[ALLOCA]] + // CHECK: [[T2:%.+]] = load ptr, ptr [[T1]], align [[ALIGN]] + // CHECK: [[P1:%.+]] = insertvalue { ptr, ptr } poison, ptr [[T0]], 0 + // CHECK: [[P2:%.+]] = insertvalue { ptr, ptr } [[P1]], ptr [[T2]], 1 + // CHECK: ret { ptr, ptr } [[P2]] intrinsics::unaligned_volatile_load(a) } From 6d6fcc9f77e4473047c3763c155a9cf5995c3142 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 26 Jun 2026 14:36:44 +0900 Subject: [PATCH 12/14] add regression test for clone suggestion empty obligations --- ...constrained-impl-param-clone-suggestion.rs | 20 +++++++++++++++++++ ...trained-impl-param-clone-suggestion.stderr | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 tests/ui/moves/unconstrained-impl-param-clone-suggestion.rs create mode 100644 tests/ui/moves/unconstrained-impl-param-clone-suggestion.stderr diff --git a/tests/ui/moves/unconstrained-impl-param-clone-suggestion.rs b/tests/ui/moves/unconstrained-impl-param-clone-suggestion.rs new file mode 100644 index 0000000000000..694c95da7b482 --- /dev/null +++ b/tests/ui/moves/unconstrained-impl-param-clone-suggestion.rs @@ -0,0 +1,20 @@ +// Regression test for https://github.com/rust-lang/rust/issues/148631 + +struct C; + +struct S(T); + +trait Tr {} + +impl Clone for S +//~^ ERROR the type parameter `T` is not constrained +where + S: Tr, +{ + fn clone(&self) -> Self { + *self + //~^ ERROR cannot move out of `*self` + } +} + +fn main() {} diff --git a/tests/ui/moves/unconstrained-impl-param-clone-suggestion.stderr b/tests/ui/moves/unconstrained-impl-param-clone-suggestion.stderr new file mode 100644 index 0000000000000..9cf17045b0296 --- /dev/null +++ b/tests/ui/moves/unconstrained-impl-param-clone-suggestion.stderr @@ -0,0 +1,19 @@ +error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates + --> $DIR/unconstrained-impl-param-clone-suggestion.rs:9:6 + | +LL | impl Clone for S + | -^- + | || + | |unconstrained type parameter + | help: remove the unused type parameter `T` + +error[E0507]: cannot move out of `*self` which is behind a shared reference + --> $DIR/unconstrained-impl-param-clone-suggestion.rs:15:9 + | +LL | *self + | ^^^^^ move occurs because `*self` has type `S`, which does not implement the `Copy` trait + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0207, E0507. +For more information about an error, try `rustc --explain E0207`. From f08762cf489011e9d91fb5dbe47f6d31bcb2e83e Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 26 Jun 2026 14:38:51 +0900 Subject: [PATCH 13/14] guard clone suggestion against empty obligation errors --- .../src/diagnostics/conflict_errors.rs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index caa566e79e29b..6acf685459cf3 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -1461,15 +1461,19 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let cause = ObligationCause::misc(expr.span, self.mir_def_id()); ocx.register_bound(cause, self.infcx.param_env, ty, clone_trait); let errors = ocx.evaluate_obligations_error_on_ambiguity(); - if errors.iter().all(|error| { - match error.obligation.predicate.as_clause().and_then(|c| c.as_trait_clause()) { - Some(clause) => match clause.self_ty().skip_binder().kind() { - ty::Adt(def, _) => def.did().is_local() && clause.def_id() == clone_trait, - _ => false, - }, - None => false, - } - }) { + if !errors.is_empty() + && errors.iter().all(|error| { + match error.obligation.predicate.as_clause().and_then(|c| c.as_trait_clause()) { + Some(clause) => match clause.self_ty().skip_binder().kind() { + ty::Adt(def, _) => { + def.did().is_local() && clause.def_id() == clone_trait + } + _ => false, + }, + None => false, + } + }) + { let mut type_spans = vec![]; let mut types = FxIndexSet::default(); for clause in errors From c200d2fa5625ea8ddcd1e68ca65a6eaaf4b6cbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Wed, 24 Jun 2026 14:53:00 +0200 Subject: [PATCH 14/14] fixup the refactoring errors in 156246 --- .../src/solve/eval_ctxt/mod.rs | 6 +-- .../src/solve/eval_ctxt/probe.rs | 6 +-- .../src/solve/normalizes_to.rs | 50 +++++++------------ .../src/solve/search_graph.rs | 15 ++---- .../src/solve/trait_goals.rs | 17 +++---- 5 files changed, 32 insertions(+), 62 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 05532cdb8924b..fa6de4a431727 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -462,7 +462,7 @@ where Ok(i) => Ok(i), Err(NoSolutionOrRerunNonErased::NoSolution(NoSolution)) => Err(NoSolution), Err(NoSolutionOrRerunNonErased::RerunNonErased(_)) => { - // check th t the opaque_accesses state mirrors the result we got. + // Check that the opaque_accesses state mirrors the result we got. assert!(opaque_accesses.should_bail().is_err()); Err(NoSolution) } @@ -1442,7 +1442,7 @@ where uv: ty::UnevaluatedConst, ) -> Result, RerunNonErased> { if self.typing_mode().is_erased_not_coherence() { - self.opaque_accesses.rerun_always(RerunReason::EvaluateConst)?; + match self.opaque_accesses.rerun_always(RerunReason::EvaluateConst)? {} } Ok(self.delegate.evaluate_const(param_env, uv)) @@ -1515,7 +1515,7 @@ where symbol: I::Symbol, ) -> Result { if self.typing_mode().is_erased_not_coherence() { - self.opaque_accesses.rerun_always(RerunReason::MayUseUnstableFeature)?; + match self.opaque_accesses.rerun_always(RerunReason::MayUseUnstableFeature)? {} } Ok(may_use_unstable_feature(&**self.delegate, param_env, symbol)) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs index 1c5d6e0b14c65..d9d18bdeea7e7 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs @@ -98,10 +98,8 @@ where outer.opaque_accesses.update(nested.opaque_accesses)?; - let r = match r.map_err_to_rerun()? { - Ok(i) => Ok(i), - Err(NoSolution) => Err(NoSolution), - }; + // Unwrap is unreachable, we would have returned on the line above. + let r = r.map_err_to_rerun().unwrap(); if !nested.inspect.is_noop() { let probe_kind = probe_kind(&r); diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs index e0290dfbdebdf..eeba18434ccb8 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs @@ -81,15 +81,10 @@ where None }, |ecx| { - ecx.probe(|&result| ProbeKind::RigidAlias { result }) - .enter(|this| { - this.structurally_instantiate_normalizes_to_term( - goal, - goal.predicate.alias, - ); - this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }) - .map_err(Into::into) + ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| { + this.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias); + this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) }, ) } @@ -351,11 +346,9 @@ where GoalSource::Misc, goal.with(cx, PredicateKind::Ambiguous), )?; - return ecx - .evaluate_added_goals_and_make_canonical_response( - Certainty::Yes, - ) - .map_err(Into::into); + return ecx.evaluate_added_goals_and_make_canonical_response( + Certainty::Yes, + ); } // Outside of coherence, we treat the associated item as rigid instead. ty::TypingMode::Typeck { .. } @@ -367,11 +360,9 @@ where goal, goal.predicate.alias, ); - return ecx - .evaluate_added_goals_and_make_canonical_response( - Certainty::Yes, - ) - .map_err(Into::into); + return ecx.evaluate_added_goals_and_make_canonical_response( + Certainty::Yes, + ); } }; } @@ -401,10 +392,10 @@ where // This is not the case here and we only prefer adding an ambiguous // nested goal for consistency. ecx.add_goal(GoalSource::Misc, goal.with(cx, PredicateKind::Ambiguous))?; - return then(ecx, Certainty::Yes).map_err(Into::into); + return then(ecx, Certainty::Yes); } else { ecx.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias); - return then(ecx, Certainty::Yes).map_err(Into::into); + return then(ecx, Certainty::Yes); } } else { return error_response(ecx, cx.delay_bug("missing item")); @@ -472,7 +463,7 @@ where }; ecx.instantiate_normalizes_to_term(goal, term)?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes).map_err(Into::into) + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } @@ -572,7 +563,6 @@ where pred, [(GoalSource::ImplWhereBound, goal.with(cx, output_is_sized_pred))], ) - .map_err(Into::into) } fn consider_builtin_async_fn_trait_candidates( @@ -759,8 +749,9 @@ where // and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`. // FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't // exist. Instead, `Pointee` should be a supertrait of `Sized`. - let alias_bound_result = - ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { + let alias_bound_result = ecx + .probe_builtin_trait_candidate(BuiltinImplSource::Misc) + .enter(|ecx| { let sized_predicate = ty::TraitRef::new( cx, cx.require_trait_lang_item(SolverTraitLangItem::Sized), @@ -769,12 +760,8 @@ where ecx.add_goal(GoalSource::Misc, goal.with(cx, sized_predicate))?; ecx.instantiate_normalizes_to_term(goal, Ty::new_unit(cx).into())?; ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }); - - let alias_bound_result = match alias_bound_result.map_err_to_rerun()? { - Ok(i) => Ok(i), - Err(NoSolution) => Err(NoSolution), - }; + }) + .map_err_to_rerun()?; // In case the dummy alias-bound candidate does not apply, we instead treat this projection // as rigid. @@ -900,7 +887,6 @@ where // but that's already proven by the generator being WF. [], ) - .map_err(Into::into) } fn consider_builtin_fused_iterator_candidate( 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 549d5d903c045..778826ba60aee 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use rustc_type_ir::data_structures::ensure_sufficient_stack; use rustc_type_ir::search_graph::{self, PathKind}; use rustc_type_ir::solve::{ - AccessedOpaques, CanonicalInput, Certainty, NoSolution, NoSolutionOrRerunNonErased, QueryResult, + AccessedOpaques, CanonicalInput, Certainty, NoSolution, QueryResult, RerunResultExt, }; use rustc_type_ir::{Interner, MayBeErased, TypingMode}; @@ -141,17 +141,8 @@ where ) -> (QueryResult, AccessedOpaques) { ensure_sufficient_stack(|| { EvalCtxt::enter_canonical(cx, search_graph, input, inspect, |ecx, goal| { - let result = ecx.compute_goal(goal); - - // if we're in `RerunNonErased`, don't even bother with inspect, - // and immediately return - let result = match result { - Ok(i) => Ok(i), - Err(NoSolutionOrRerunNonErased::NoSolution(NoSolution)) => Err(NoSolution), - Err(NoSolutionOrRerunNonErased::RerunNonErased(e)) => { - return Err(e.into()); - } - }; + // if we're in `RerunNonErased`, don't even bother with inspect, and immediately return + let result = ecx.compute_goal(goal).map_err_to_rerun()?; ecx.inspect.query_result(result); result.map_err(Into::into) diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 79e0af72710fb..5c002d09a75cc 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -119,7 +119,7 @@ where .map(|pred| goal.with(cx, pred)), )?; - then(ecx, maximal_certainty).map_err(Into::into) + then(ecx, maximal_certainty) }) } @@ -399,7 +399,6 @@ where pred, [(GoalSource::ImplWhereBound, goal.with(cx, output_is_sized_pred))], ) - .map_err(Into::into) } fn consider_builtin_async_fn_trait_candidates( @@ -450,7 +449,6 @@ where .chain(nested_preds.into_iter().map(|pred| goal.with(cx, pred))) .map(|goal| (GoalSource::ImplWhereBound, goal)), ) - .map_err(Into::into) } fn consider_builtin_async_fn_kind_helper_candidate( @@ -696,7 +694,7 @@ where goal.predicate.trait_ref.args.type_at(1), assume, )?; - ecx.evaluate_added_goals_and_make_canonical_response(certainty).map_err(Into::into) + ecx.evaluate_added_goals_and_make_canonical_response(certainty) }, ) } @@ -1085,7 +1083,6 @@ where ecx.try_evaluate_added_goals() }, ) - .map_err(Into::into) }) .is_ok() }; @@ -1124,11 +1121,9 @@ where return Err(NoSolution.into()); }; if matching_projections.next().is_some() { - return ecx - .evaluate_added_goals_and_make_canonical_response( - Certainty::AMBIGUOUS, - ) - .map_err(Into::into); + return ecx.evaluate_added_goals_and_make_canonical_response( + Certainty::AMBIGUOUS, + ); } ecx.enter_forall_with_assumptions( target_projection, @@ -1156,7 +1151,7 @@ where Goal::new(ecx.cx(), param_env, ty::OutlivesPredicate(a_region, b_region)), )?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes).map_err(Into::into) + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) }