From 10e3e523bebad34aab02f8e6cb8ac45a02fcd731 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Mon, 7 Sep 2026 21:20:42 -0700 Subject: [PATCH 1/3] add fcw for ill-formed HRTB args --- .../rustc_hir_analysis/src/check/wfcheck.rs | 144 ++++++++++++++++-- compiler/rustc_lint_defs/src/builtin.rs | 52 +++++++ .../rustc_trait_selection/src/traits/wf.rs | 127 +++++++++------ tests/ui/inference/issue-80409.compat.stderr | 18 ++- .../ui/inference/issue-80409.no-compat.stderr | 18 ++- tests/ui/inference/issue-80409.rs | 2 + tests/ui/wf/closure-wf.current.stderr | 30 ++++ tests/ui/wf/closure-wf.next.stderr | 39 +++++ tests/ui/wf/closure-wf.rs | 8 + tests/ui/wf/closure-wf.stderr | 20 --- .../unsatisfied-hrtb-arg-gated.current.stderr | 19 +++ .../wf/unsatisfied-hrtb-arg-gated.next.stderr | 19 +++ tests/ui/wf/unsatisfied-hrtb-arg-gated.rs | 15 ++ ...satisfied-hrtb-arg-lifetime.current.stderr | 12 ++ .../unsatisfied-hrtb-arg-lifetime.next.stderr | 22 +++ tests/ui/wf/unsatisfied-hrtb-arg-lifetime.rs | 25 +++ tests/ui/wf/unsatisfied-hrtb-arg-no-warn.rs | 15 ++ .../ui/wf/unsatisfied-hrtb-arg.current.stderr | 12 ++ tests/ui/wf/unsatisfied-hrtb-arg.next.stderr | 12 ++ tests/ui/wf/unsatisfied-hrtb-arg.rs | 27 ++++ 20 files changed, 549 insertions(+), 87 deletions(-) create mode 100644 tests/ui/wf/closure-wf.current.stderr create mode 100644 tests/ui/wf/closure-wf.next.stderr delete mode 100644 tests/ui/wf/closure-wf.stderr create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg-gated.current.stderr create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg-gated.next.stderr create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg-gated.rs create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg-lifetime.current.stderr create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg-lifetime.next.stderr create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg-lifetime.rs create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg-no-warn.rs create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg.current.stderr create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg.next.stderr create mode 100644 tests/ui/wf/unsatisfied-hrtb-arg.rs diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 429f45c2d8358..8bc0d3941ce58 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -1,4 +1,4 @@ -use std::cell::LazyCell; +use std::cell::{LazyCell, RefCell}; use std::ops::{ControlFlow, Deref}; use hir::intravisit::{self, Visitor}; @@ -15,9 +15,13 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{AmbigArg, ItemKind, find_attr}; use rustc_infer::infer::outlives::env::OutlivesEnvironment; -use rustc_infer::infer::{BoundRegionConversionTime, SolverRegionConstraint, TyCtxtInferExt}; +use rustc_infer::infer::{ + BoundRegionConversionTime, RegionResolutionError, SolverRegionConstraint, TyCtxtInferExt, +}; use rustc_infer::traits::{PredicateObligations, TraitErrors}; -use rustc_lint_defs::builtin::{REDUNDANT_LIFETIMES, SHADOWING_SUPERTRAIT_ITEMS}; +use rustc_lint_defs::builtin::{ + REDUNDANT_LIFETIMES, SHADOWING_SUPERTRAIT_ITEMS, UNSATISFIED_HRTB_ARG, +}; use rustc_macros::{Diagnostic, TypeFoldable, TypeVisitable}; use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::traits::solve::NoSolution; @@ -53,6 +57,11 @@ pub(super) struct WfCheckingCtxt<'a, 'tcx> { pub(super) ocx: ObligationCtxt<'a, 'tcx, FulfillmentError<'tcx>>, body_def_id: LocalDefId, param_env: ty::ParamEnv<'tcx>, + + /// Obligations with escaping bound vars. As emitting a hard error if such + /// obligations fail would cause significant crater breakage, we instead + /// prove them in a forked infcx and emit a FCW if any fail. + fcw_obligations: RefCell>, } impl<'a, 'tcx> Deref for WfCheckingCtxt<'a, 'tcx> { type Target = ObligationCtxt<'a, 'tcx, FulfillmentError<'tcx>>; @@ -138,6 +147,10 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { )); } + pub(super) fn register_fcw_obligations(&self, obligations: PredicateObligations<'tcx>) { + self.fcw_obligations.borrow_mut().extend(obligations); + } + pub(super) fn unnormalized_obligations( &self, span: Span, @@ -164,8 +177,9 @@ where let param_env = tcx.param_env(body_def_id); let infcx = &tcx.infer_ctxt().build(TypingMode::non_body_analysis()); let ocx = ObligationCtxt::new_with_diagnostics(infcx); + let fcw_obligations = RefCell::new(PredicateObligations::new()); - let mut wfcx = WfCheckingCtxt { ocx, body_def_id, param_env }; + let mut wfcx = WfCheckingCtxt { ocx, body_def_id, param_env, fcw_obligations }; // As of now, bounds are only enforced on checked type aliases, they're ignored for most type // aliases. So, only check for false global bounds if we're not ignoring bounds altogether. @@ -186,6 +200,76 @@ where debug!(?assumed_wf_types); let infcx_compat = infcx.fork(); + let infcx_fcw = infcx.fork(); + + let check_escaping_bound_vars_fcw = || { + if wfcx.fcw_obligations.borrow().is_empty() { + return; + } + + let ocx = ObligationCtxt::new_with_diagnostics(&infcx_fcw); + ocx.register_obligations(wfcx.fcw_obligations.into_inner()); + let trait_errors = ocx.try_evaluate_obligations(); + + let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat( + &infcx_fcw, + body_def_id, + param_env, + assumed_wf_types.iter().copied(), + true, + ); + let mut region_errors = infcx_fcw.resolve_regions_with_outlives_env(&outlives_env); + + if let TraitErrors::HasErrors(errors) = trait_errors { + for error in errors { + tcx.emit_node_span_lint( + UNSATISFIED_HRTB_ARG, + tcx.local_def_id_to_hir_id(body_def_id), + error.obligation.cause.span, + rustc_errors::DiagDecorator(|err| { + let predicate = + tcx.short_string(error.obligation.predicate, err.long_ty_path()); + err.primary_message(format!( + "the trait bound `{predicate}` is not satisfied" + )); + }), + ); + } + } else if !region_errors.is_empty() { + let is_bound_failure = |e: &RegionResolutionError<'tcx>| { + matches!(e, RegionResolutionError::GenericBoundFailure(..)) + }; + if !region_errors.iter().all(&is_bound_failure) { + region_errors.retain(|e| !is_bound_failure(e)); + } + region_errors.sort_by_key(|e| e.origin().span()); + + for error in ®ion_errors { + let span = error.origin().span(); + tcx.emit_node_span_lint( + UNSATISFIED_HRTB_ARG, + tcx.local_def_id_to_hir_id(body_def_id), + span, + rustc_errors::DiagDecorator(|err| match error { + RegionResolutionError::GenericBoundFailure(_, kind, sub) => { + let desc = kind.to_ty(tcx).prefix_string(tcx); + err.primary_message(format!( + "the {desc} `{kind}` may not live long enough" + )); + err.span_label(span, format!("`{kind}` must outlive `{sub}`")); + } + RegionResolutionError::ConcreteFailure(_, sub, sup) => { + err.primary_message("lifetime may not live long enough"); + err.span_label(span, format!("`{sup}` must outlive `{sub}`")); + } + _ => { + err.primary_message("unsatisfied lifetime requirements"); + } + }), + ); + } + } + }; // We specifically want to *disable* the implied bounds hack, first, // so we can detect when failures are due to bevy's implied bounds. @@ -201,6 +285,7 @@ where let errors = infcx.resolve_regions_with_outlives_env(&outlives_env); if errors.is_empty() { + check_escaping_bound_vars_fcw(); return Ok(()); } @@ -208,13 +293,15 @@ where &infcx_compat, body_def_id, param_env, - assumed_wf_types, + assumed_wf_types.iter().copied(), // Don't *disable* the implied bounds hack; though this will only apply // the implied bounds hack if this contains `bevy_ecs`'s `ParamSet` type. false, ); let errors_compat = infcx_compat.resolve_regions_with_outlives_env(&outlives_env); if errors_compat.is_empty() { + check_escaping_bound_vars_fcw(); + // FIXME: Once we fix bevy, this would be the place to insert a warning // to upgrade bevy. Ok(()) @@ -1113,13 +1200,15 @@ fn check_associated_type_bounds(wfcx: &WfCheckingCtxt<'_, '_>, item: ty::AssocIt debug!("check_associated_type_bounds: bounds={:?}", bounds); let wf_obligations = bounds.iter_identity_copied().map(Unnormalized::skip_norm_wip).flat_map( |(bound, bound_span)| { - traits::wf::clause_obligations( + let (obligations, fcw_obligations) = traits::wf::clause_obligations( wfcx.infcx, wfcx.param_env, wfcx.body_def_id, bound, bound_span, - ) + ); + wfcx.register_fcw_obligations(fcw_obligations); + obligations }, ); @@ -1589,13 +1678,40 @@ pub(super) fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, def_id: assert_eq!(gen_clauses.clauses.len(), gen_clauses.spans.len()); let wf_obligations = gen_clauses.into_iter().flat_map(|(p, sp)| { - traits::wf::clause_obligations( - infcx, - wfcx.param_env, - wfcx.body_def_id, - p.skip_norm_wip(), - sp, - ) + let clause = p.skip_norm_wip(); + let (obligations, mut fcw_obligations) = + traits::wf::clause_obligations(infcx, wfcx.param_env, wfcx.body_def_id, clause, sp); + + // On a where-clause like `for<'a> W<'a, T>: Sized`, we want to point to the entire + // clause including the self ty, not just the `Sized` bound. + let self_ty_span_for_fcw = || { + // A self ty that has no escaping bound vars, like the one in + // `for<'a> X: Trait>`, already has the correct span. + if clause.as_trait_clause().is_none_or(|trait_pred| { + !trait_pred.skip_binder().trait_ref.self_ty().has_escaping_bound_vars() + }) { + return None; + } + + for pred in tcx.hir_node_by_def_id(def_id).generics()?.predicates { + if let hir::WherePredicateKind::BoundPredicate(bound_pred) = pred.kind + && bound_pred.bounds.iter().any(|bound| bound.span() == sp) + { + return Some(pred.span); + } + } + None + }; + if !fcw_obligations.is_empty() + && let Some(span) = self_ty_span_for_fcw() + { + for obligation in &mut fcw_obligations { + obligation.cause.span = span; + } + } + wfcx.register_fcw_obligations(fcw_obligations); + + obligations }); let obligations: Vec<_> = wf_obligations.chain(default_obligations).chain(assoc_const_obligations).collect(); diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 2a0901e0243b6..d339ee7bda087 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -137,6 +137,7 @@ pub mod hardwired { UNREACHABLE_PATTERNS, UNSAFE_ATTR_OUTSIDE_UNSAFE, UNSAFE_OP_IN_UNSAFE_FN, + UNSATISFIED_HRTB_ARG, UNSTABLE_NAME_COLLISIONS, UNSTABLE_SYNTAX_PRE_EXPANSION, UNSUPPORTED_CALLING_CONVENTIONS, @@ -5084,6 +5085,57 @@ declare_lint! { }; } +declare_lint! { + /// The `unsatisfied_hrtb_arg` lint detects arguments of [higher-ranked + /// trait bounds] that don't satisfy the bounds declared on their own + /// types. + /// + /// ### Example + /// + /// ```rust + /// trait Bound {} + /// struct W<'a, T: Bound>(&'a T); + /// + /// fn wf() where for<'a> W<'a, T>: Sized {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The compiler previously accepted higher-ranked trait bounds without + /// checking that their arguments satisfy the bounds declared on the + /// argument types, if the arguments of the type mention one of the bound + /// variables (like `'a` in the above example). `W` requires that + /// `T: Bound`, which isn't satisfied in `wf`, yet the compiler used to + /// accept this code. + /// + /// To fix this, add the bound that `W` requires to `wf`: + /// + /// ```rust + /// trait Bound {} + /// struct W<'a, T: Bound>(&'a T); + /// + /// fn wf() where for<'a> W<'a, T>: Sized {} + /// ``` + /// + /// This is a lint instead of a hard error because many existing projects + /// were found to hit this error. It will become a hard error in a future + /// release. + /// + /// See [issue #162200] for more details. + /// + /// [higher-ranked trait bounds]: https://doc.rust-lang.org/reference/trait-bounds.html#higher-ranked-trait-bounds + /// [issue #162200]: https://github.com/rust-lang/rust/issues/162200 + /// [future-incompatible]: ../index.md#future-incompatible-lints + pub UNSATISFIED_HRTB_ARG, + Warn, + "arguments of higher-ranked trait bounds that don't satisfy the bounds their types declare", + @future_incompatible = FutureIncompatibleInfo { + reason: fcw!(FutureReleaseError #162200), + }; +} + declare_lint! { /// The `unsafe_attr_outside_unsafe` lint detects a missing unsafe keyword /// on attributes considered unsafe. diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index 175183ce633ab..69d0fc81e7007 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -142,7 +142,7 @@ pub fn trait_obligations<'tcx>( item: Some(item), visited_tys: Default::default(), }; - wf.add_wf_preds_for_trait_pred(trait_pred, Elaborate::All); + wf.add_wf_preds_for_trait_pred(trait_pred, Elaborate::All, None); debug!(obligations = ?wf.out); wf.normalize(infcx) } @@ -159,7 +159,7 @@ pub fn clause_obligations<'tcx>( body_def_id: LocalDefId, clause: ty::Clause<'tcx>, span: Span, -) -> PredicateObligations<'tcx> { +) -> (PredicateObligations<'tcx>, PredicateObligations<'tcx>) { let mut wf = WfPredicates { infcx, param_env, @@ -170,11 +170,12 @@ pub fn clause_obligations<'tcx>( item: None, visited_tys: Default::default(), }; + let mut fcw_obligations = PredicateObligations::new(); // It's ok to skip the binder here because wf code is prepared for it match clause.kind().skip_binder() { ty::ClauseKind::Trait(t) => { - wf.add_wf_preds_for_trait_pred(t, Elaborate::None); + wf.add_wf_preds_for_trait_pred(t, Elaborate::None, Some(&mut fcw_obligations)); } ty::ClauseKind::HostEffect(..) => { // Technically the well-formedness of this clause is implied by @@ -185,7 +186,7 @@ pub fn clause_obligations<'tcx>( wf.add_wf_preds_for_term(ty.into()); } ty::ClauseKind::Projection(t) => { - wf.add_wf_preds_for_projection_term(t.projection_term); + wf.add_wf_preds_for_projection_term(t.projection_term, &mut fcw_obligations); wf.add_wf_preds_for_term(t.term); } ty::ClauseKind::ConstArgHasType(ct, ty) => { @@ -202,7 +203,9 @@ pub fn clause_obligations<'tcx>( ty::ClauseKind::UnstableFeature(_) => {} } - wf.normalize(infcx) + // `fcw_obligations` is already normalized, since everything that writes to it + // goes through `obligations()`, which calls `wf.normalize()` at the end. + (wf.normalize(infcx), fcw_obligations) } struct WfPredicates<'a, 'tcx> { @@ -380,6 +383,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { &mut self, trait_pred: ty::TraitClause<'tcx>, elaborate: Elaborate, + mut fcw_obligations: Option<&mut PredicateObligations<'tcx>>, ) { let tcx = self.tcx(); let trait_ref = trait_pred.trait_ref; @@ -423,32 +427,45 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { }); } - self.out.extend( - trait_ref - .args - .iter() - .enumerate() - .filter_map(|(i, arg)| arg.as_term().map(|t| (i, t))) - .filter(|(_, term)| !term.has_escaping_bound_vars()) - .map(|(i, term)| { - let mut cause = traits::ObligationCause::misc(self.span, self.body_def_id); - // The first arg is the self ty - use the correct span for it. - if i == 0 { - if let Some(hir::ItemKind::Impl(hir::Impl { self_ty, .. })) = - item.map(|i| &i.kind) - { - cause.span = self_ty.span; - } + self.out.extend(trait_ref.args.iter().enumerate().filter_map(|(i, arg)| { + let term = arg.as_term()?; + + if term.has_escaping_bound_vars() { + let obligations = obligations( + self.infcx, + param_env, + self.body_def_id, + depth + 1, + term, + self.span, + ) + .expect("inference vars shouldn't have escaping bound vars"); + + fcw_obligations + .as_deref_mut() + .expect("impl trait refs shouldn't have escaping bound vars") + .extend(obligations); + + None + } else { + let mut cause = traits::ObligationCause::misc(self.span, self.body_def_id); + // The first arg is the self ty - use the correct span for it. + if i == 0 { + if let Some(hir::ItemKind::Impl(hir::Impl { self_ty, .. })) = + item.map(|i| &i.kind) + { + cause.span = self_ty.span; } - traits::Obligation::with_depth( - tcx, - cause, - depth, - param_env, - ty::ClauseKind::WellFormed(term), - ) - }), - ); + } + Some(traits::Obligation::with_depth( + tcx, + cause, + depth, + param_env, + ty::ClauseKind::WellFormed(term), + )) + } + })); } // Compute the obligations that are required for `trait_ref` to be WF, @@ -462,7 +479,11 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { } /// Pushes the obligations required for a projection to be WF into `self.out`. - fn add_wf_preds_for_projection_term(&mut self, data: ty::AliasTerm<'tcx>) { + fn add_wf_preds_for_projection_term( + &mut self, + data: ty::AliasTerm<'tcx>, + fcw_obligations: &mut PredicateObligations<'tcx>, + ) { // A projection is well-formed if // // (a) its predicates hold (*) @@ -488,7 +509,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { this.out.push(obligation) }); - self.add_wf_preds_for_projection_args(data.args); + self.add_wf_preds_for_projection_args(data.args, fcw_obligations); } /// Pushes the obligations required for an inherent alias to be WF @@ -530,26 +551,42 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { data.args.visit_with(self); } - fn add_wf_preds_for_projection_args(&mut self, args: GenericArgsRef<'tcx>) { + fn add_wf_preds_for_projection_args( + &mut self, + args: GenericArgsRef<'tcx>, + fcw_obligations: &mut PredicateObligations<'tcx>, + ) { let tcx = self.tcx(); let cause = self.cause(ObligationCauseCode::WellFormed(None)); let param_env = self.param_env; let depth = self.recursion_depth; - self.out.extend( - args.iter() - .filter_map(|arg| arg.as_term()) - .filter(|term| !term.has_escaping_bound_vars()) - .map(|term| { - traits::Obligation::with_depth( - tcx, - cause.clone(), - depth, + self.out.extend(args.iter().filter_map(|arg| { + let term = arg.as_term()?; + + if term.has_escaping_bound_vars() { + fcw_obligations.extend( + obligations( + self.infcx, param_env, - ty::ClauseKind::WellFormed(term), + self.body_def_id, + depth + 1, + term, + self.span, ) - }), - ); + .expect("inference vars shouldn't have escaping bound vars"), + ); + None + } else { + Some(traits::Obligation::with_depth( + tcx, + cause.clone(), + depth, + param_env, + ty::ClauseKind::WellFormed(term), + )) + } + })); } fn require_sized(&mut self, subty: Ty<'tcx>, cause: traits::ObligationCauseCode<'tcx>) { diff --git a/tests/ui/inference/issue-80409.compat.stderr b/tests/ui/inference/issue-80409.compat.stderr index 2f3f6cef20926..a28abcd4cd7bc 100644 --- a/tests/ui/inference/issue-80409.compat.stderr +++ b/tests/ui/inference/issue-80409.compat.stderr @@ -1,20 +1,30 @@ +warning: the trait bound `TFsm: Fsm` is not satisfied + --> $DIR/issue-80409.rs:23:26 + | +LL | fn on_entry)>(&self, _action: TAction) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: `#[warn(unsatisfied_hrtb_arg)]` (part of `#[warn(future_incompatible)]`) on by default + error[E0277]: the trait bound `usize: Fsm` is not satisfied - --> $DIR/issue-80409.rs:36:31 + --> $DIR/issue-80409.rs:38:31 | LL | builder.state().on_entry(|_| {}); | ^ the trait `Fsm` is not implemented for `usize` | help: this trait has no implementations, consider adding one - --> $DIR/issue-80409.rs:26:1 + --> $DIR/issue-80409.rs:28:1 | LL | trait Fsm { | ^^^^^^^^^ note: required by a bound in `StateContext` - --> $DIR/issue-80409.rs:30:31 + --> $DIR/issue-80409.rs:32:31 | LL | struct StateContext<'a, TFsm: Fsm> { | ^^^ required by this bound in `StateContext` -error: aborting due to 1 previous error +error: aborting due to 1 previous error; 1 warning emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/inference/issue-80409.no-compat.stderr b/tests/ui/inference/issue-80409.no-compat.stderr index 2f3f6cef20926..a28abcd4cd7bc 100644 --- a/tests/ui/inference/issue-80409.no-compat.stderr +++ b/tests/ui/inference/issue-80409.no-compat.stderr @@ -1,20 +1,30 @@ +warning: the trait bound `TFsm: Fsm` is not satisfied + --> $DIR/issue-80409.rs:23:26 + | +LL | fn on_entry)>(&self, _action: TAction) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: `#[warn(unsatisfied_hrtb_arg)]` (part of `#[warn(future_incompatible)]`) on by default + error[E0277]: the trait bound `usize: Fsm` is not satisfied - --> $DIR/issue-80409.rs:36:31 + --> $DIR/issue-80409.rs:38:31 | LL | builder.state().on_entry(|_| {}); | ^ the trait `Fsm` is not implemented for `usize` | help: this trait has no implementations, consider adding one - --> $DIR/issue-80409.rs:26:1 + --> $DIR/issue-80409.rs:28:1 | LL | trait Fsm { | ^^^^^^^^^ note: required by a bound in `StateContext` - --> $DIR/issue-80409.rs:30:31 + --> $DIR/issue-80409.rs:32:31 | LL | struct StateContext<'a, TFsm: Fsm> { | ^^^ required by this bound in `StateContext` -error: aborting due to 1 previous error +error: aborting due to 1 previous error; 1 warning emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/inference/issue-80409.rs b/tests/ui/inference/issue-80409.rs index 86dac3cda9199..a096a003d335b 100644 --- a/tests/ui/inference/issue-80409.rs +++ b/tests/ui/inference/issue-80409.rs @@ -21,6 +21,8 @@ struct FsmStateBuilder { impl FsmStateBuilder { fn on_entry)>(&self, _action: TAction) {} + //~^ WARN the trait bound `TFsm: Fsm` is not satisfied + //~| WARN this was previously accepted by the compiler } trait Fsm { diff --git a/tests/ui/wf/closure-wf.current.stderr b/tests/ui/wf/closure-wf.current.stderr new file mode 100644 index 0000000000000..003dda9c20be9 --- /dev/null +++ b/tests/ui/wf/closure-wf.current.stderr @@ -0,0 +1,30 @@ +warning: the trait bound `T: Bound` is not satisfied + --> $DIR/closure-wf.rs:10:26 + | +LL | fn constrain_inner FnOnce(&'a (), NeedsBound)>(_: T, _: F) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: `#[warn(unsatisfied_hrtb_arg)]` (part of `#[warn(future_incompatible)]`) on by default + +error[E0277]: the trait bound `u32: Bound` is not satisfied + --> $DIR/closure-wf.rs:17:33 + | +LL | constrain_inner(1u32, |&(), _| ()); + | ^ the trait `Bound` is not implemented for `u32` + | +help: this trait has no implementations, consider adding one + --> $DIR/closure-wf.rs:5:1 + | +LL | trait Bound {} + | ^^^^^^^^^^^ +note: required by a bound in `NeedsBound` + --> $DIR/closure-wf.rs:6:22 + | +LL | struct NeedsBound(T); + | ^^^^^ required by this bound in `NeedsBound` + +error: aborting due to 1 previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/wf/closure-wf.next.stderr b/tests/ui/wf/closure-wf.next.stderr new file mode 100644 index 0000000000000..15b68bd9d65a2 --- /dev/null +++ b/tests/ui/wf/closure-wf.next.stderr @@ -0,0 +1,39 @@ +warning: the trait bound `T: Bound` is not satisfied + --> $DIR/closure-wf.rs:10:26 + | +LL | fn constrain_inner FnOnce(&'a (), NeedsBound)>(_: T, _: F) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: `#[warn(unsatisfied_hrtb_arg)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: the trait bound `T: Bound` is not satisfied + --> $DIR/closure-wf.rs:10:34 + | +LL | fn constrain_inner FnOnce(&'a (), NeedsBound)>(_: T, _: F) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + +error[E0277]: the trait bound `u32: Bound` is not satisfied + --> $DIR/closure-wf.rs:17:33 + | +LL | constrain_inner(1u32, |&(), _| ()); + | ^ the trait `Bound` is not implemented for `u32` + | +help: this trait has no implementations, consider adding one + --> $DIR/closure-wf.rs:5:1 + | +LL | trait Bound {} + | ^^^^^^^^^^^ +note: required by a bound in `NeedsBound` + --> $DIR/closure-wf.rs:6:22 + | +LL | struct NeedsBound(T); + | ^^^^^ required by this bound in `NeedsBound` + +error: aborting due to 1 previous error; 2 warnings emitted + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/wf/closure-wf.rs b/tests/ui/wf/closure-wf.rs index 48baeb30ce59b..6c9c93217932f 100644 --- a/tests/ui/wf/closure-wf.rs +++ b/tests/ui/wf/closure-wf.rs @@ -1,9 +1,17 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) + trait Bound {} struct NeedsBound(T); // Checks that we enforce that closure args are WF. fn constrain_inner FnOnce(&'a (), NeedsBound)>(_: T, _: F) {} +//~^ WARN the trait bound `T: Bound` is not satisfied +//~| WARN this was previously accepted by the compiler +//[next]~| WARN the trait bound `T: Bound` is not satisfied +//[next]~| WARN this was previously accepted by the compiler fn main() { constrain_inner(1u32, |&(), _| ()); diff --git a/tests/ui/wf/closure-wf.stderr b/tests/ui/wf/closure-wf.stderr deleted file mode 100644 index 4beef3bb7c577..0000000000000 --- a/tests/ui/wf/closure-wf.stderr +++ /dev/null @@ -1,20 +0,0 @@ -error[E0277]: the trait bound `u32: Bound` is not satisfied - --> $DIR/closure-wf.rs:9:33 - | -LL | constrain_inner(1u32, |&(), _| ()); - | ^ the trait `Bound` is not implemented for `u32` - | -help: this trait has no implementations, consider adding one - --> $DIR/closure-wf.rs:1:1 - | -LL | trait Bound {} - | ^^^^^^^^^^^ -note: required by a bound in `NeedsBound` - --> $DIR/closure-wf.rs:2:22 - | -LL | struct NeedsBound(T); - | ^^^^^ required by this bound in `NeedsBound` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/wf/unsatisfied-hrtb-arg-gated.current.stderr b/tests/ui/wf/unsatisfied-hrtb-arg-gated.current.stderr new file mode 100644 index 0000000000000..4b3ac33211ffa --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg-gated.current.stderr @@ -0,0 +1,19 @@ +error[E0277]: the trait bound `T: Bound` is not satisfied + --> $DIR/unsatisfied-hrtb-arg-gated.rs:12:37 + | +LL | fn errors() where W<'static, T>: Sized, for<'a> W<'a, T>: Sized {} + | ^^^^^ the trait `Bound` is not implemented for `T` + | +note: required by a bound in `W` + --> $DIR/unsatisfied-hrtb-arg-gated.rs:10:17 + | +LL | struct W<'a, T: Bound>(&'a T); + | ^^^^^ required by this bound in `W` +help: consider further restricting type parameter `T` with trait `Bound` + | +LL | fn errors() where W<'static, T>: Sized, for<'a> W<'a, T>: Sized, T: Bound {} + | ++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/wf/unsatisfied-hrtb-arg-gated.next.stderr b/tests/ui/wf/unsatisfied-hrtb-arg-gated.next.stderr new file mode 100644 index 0000000000000..4b3ac33211ffa --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg-gated.next.stderr @@ -0,0 +1,19 @@ +error[E0277]: the trait bound `T: Bound` is not satisfied + --> $DIR/unsatisfied-hrtb-arg-gated.rs:12:37 + | +LL | fn errors() where W<'static, T>: Sized, for<'a> W<'a, T>: Sized {} + | ^^^^^ the trait `Bound` is not implemented for `T` + | +note: required by a bound in `W` + --> $DIR/unsatisfied-hrtb-arg-gated.rs:10:17 + | +LL | struct W<'a, T: Bound>(&'a T); + | ^^^^^ required by this bound in `W` +help: consider further restricting type parameter `T` with trait `Bound` + | +LL | fn errors() where W<'static, T>: Sized, for<'a> W<'a, T>: Sized, T: Bound {} + | ++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/wf/unsatisfied-hrtb-arg-gated.rs b/tests/ui/wf/unsatisfied-hrtb-arg-gated.rs new file mode 100644 index 0000000000000..61b53750b163e --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg-gated.rs @@ -0,0 +1,15 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) + +// Do not emit the UNSATISFIED_HRTB_ARG FCW if we have ill-formed types. + +#![allow(dead_code)] + +trait Bound {} +struct W<'a, T: Bound>(&'a T); + +fn errors() where W<'static, T>: Sized, for<'a> W<'a, T>: Sized {} +//~^ ERROR the trait bound `T: Bound` is not satisfied + +fn main() {} diff --git a/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.current.stderr b/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.current.stderr new file mode 100644 index 0000000000000..8097260563ea0 --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.current.stderr @@ -0,0 +1,12 @@ +warning: the type parameter `T` may not live long enough + --> $DIR/unsatisfied-hrtb-arg-lifetime.rs:16:34 + | +LL | fn warns<'b, T>(_: &'b ()) where for<'a> W<'a, 'b, T>: Sized {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `T` must outlive `'b` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: `#[warn(unsatisfied_hrtb_arg)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.next.stderr b/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.next.stderr new file mode 100644 index 0000000000000..f255c0543546e --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.next.stderr @@ -0,0 +1,22 @@ +warning: the type parameter `T` may not live long enough + --> $DIR/unsatisfied-hrtb-arg-lifetime.rs:16:34 + | +LL | fn warns<'b, T>(_: &'b ()) where for<'a> W<'a, 'b, T>: Sized {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `T` must outlive `'b` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: `#[warn(unsatisfied_hrtb_arg)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: the type parameter `T` may not live long enough + --> $DIR/unsatisfied-hrtb-arg-lifetime.rs:16:34 + | +LL | fn warns<'b, T>(_: &'b ()) where for<'a> W<'a, 'b, T>: Sized {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `T` must outlive `'b` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +warning: 2 warnings emitted + diff --git a/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.rs b/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.rs new file mode 100644 index 0000000000000..01cac32c18459 --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg-lifetime.rs @@ -0,0 +1,25 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ check-pass + +// Arguments of HRTBs must satisfy the lifetime bounds declared +// on the argument types. We currently emit a FCW for such +// code, as emitting a hard error would cause substantial +// crater breakage. +// See . + +#![allow(dead_code)] + +struct W<'a, 'b, T: 'b>(&'a (), &'b T); + +fn warns<'b, T>(_: &'b ()) where for<'a> W<'a, 'b, T>: Sized {} +//~^ WARN the type parameter `T` may not live long enough +//~| WARN this was previously accepted by the compiler +//[next]~| WARN the type parameter `T` may not live long enough +//[next]~| WARN this was previously accepted by the compiler + +// Adding the required bound silences the lint. +fn fixed<'b, T: 'b>(_: &'b ()) where for<'a> W<'a, 'b, T>: Sized {} + +fn main() {} diff --git a/tests/ui/wf/unsatisfied-hrtb-arg-no-warn.rs b/tests/ui/wf/unsatisfied-hrtb-arg-no-warn.rs new file mode 100644 index 0000000000000..755d97b9c0b70 --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg-no-warn.rs @@ -0,0 +1,15 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ check-pass +//@ edition: 2021 + +// Do not emit the UNSATISFIED_HRTB_ARG FCW for perfectly fine code. + +#![allow(dead_code)] + +fn f(_: impl for<'a> Fn(&'a T)) {} + +fn g(_: &T) where for<'a> &'a T: Copy {} + +fn main() {} diff --git a/tests/ui/wf/unsatisfied-hrtb-arg.current.stderr b/tests/ui/wf/unsatisfied-hrtb-arg.current.stderr new file mode 100644 index 0000000000000..a6b9eec1f0fac --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg.current.stderr @@ -0,0 +1,12 @@ +warning: the trait bound `T: Bound` is not satisfied + --> $DIR/unsatisfied-hrtb-arg.rs:17:21 + | +LL | fn warns() where for<'a> W<'a, T>: Sized {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: `#[warn(unsatisfied_hrtb_arg)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/wf/unsatisfied-hrtb-arg.next.stderr b/tests/ui/wf/unsatisfied-hrtb-arg.next.stderr new file mode 100644 index 0000000000000..a6b9eec1f0fac --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg.next.stderr @@ -0,0 +1,12 @@ +warning: the trait bound `T: Bound` is not satisfied + --> $DIR/unsatisfied-hrtb-arg.rs:17:21 + | +LL | fn warns() where for<'a> W<'a, T>: Sized {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #162200 + = note: `#[warn(unsatisfied_hrtb_arg)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/wf/unsatisfied-hrtb-arg.rs b/tests/ui/wf/unsatisfied-hrtb-arg.rs new file mode 100644 index 0000000000000..f619c910fc72a --- /dev/null +++ b/tests/ui/wf/unsatisfied-hrtb-arg.rs @@ -0,0 +1,27 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ check-pass + +// Arguments of HRTBs must satisfy the trait bounds declared +// on the argument types. We currently emit a FCW for such +// code, as emitting a hard error would cause substantial +// crater breakage. +// See . + +#![allow(dead_code)] + +trait Bound {} +struct W<'a, T: Bound>(&'a T); + +fn warns() where for<'a> W<'a, T>: Sized {} +//~^ WARN the trait bound `T: Bound` is not satisfied +//~| WARN this was previously accepted by the compiler + +// Adding the required bound silences the lint. +fn fixed() where for<'a> W<'a, T>: Sized {} + +#[allow(unsatisfied_hrtb_arg)] +fn allowed() where for<'a> W<'a, T>: Sized {} + +fn main() {} From 6ac7650e20b8677c10b21d9e8a10e97025f3f3d6 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:45:12 -0700 Subject: [PATCH 2/3] cr8r --- compiler/rustc_lint_defs/src/builtin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index d339ee7bda087..50906eff25174 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -5129,7 +5129,7 @@ declare_lint! { /// [issue #162200]: https://github.com/rust-lang/rust/issues/162200 /// [future-incompatible]: ../index.md#future-incompatible-lints pub UNSATISFIED_HRTB_ARG, - Warn, + Forbid, "arguments of higher-ranked trait bounds that don't satisfy the bounds their types declare", @future_incompatible = FutureIncompatibleInfo { reason: fcw!(FutureReleaseError #162200), From bf102d811feb6f51b42f4106b9202b6e5c90a0b3 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Tue, 22 Sep 2026 17:21:42 -0700 Subject: [PATCH 3/3] fix bootstrap --- src/build_helper/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/build_helper/src/lib.rs b/src/build_helper/src/lib.rs index e23a158ac0598..4888490a03dad 100644 --- a/src/build_helper/src/lib.rs +++ b/src/build_helper/src/lib.rs @@ -30,7 +30,6 @@ pub const RUSTC_PGO_CRATES: &[&str] = &[ "token-stream-stress", "match-stress", "tuple-stress", - "diesel-2.2.10", "bitmaps-3.2.1", "serde-1.0.219-new-solver", ];