From 2022438f9ab1cd22da9ae6af3f96e3aaedc400ba Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sat, 22 Aug 2026 20:38:48 -0300 Subject: [PATCH 1/2] Carry solver region constraints through type ops --- .../src/type_check/constraint_conversion.rs | 7 ++- .../src/infer/canonical/query_response.rs | 23 +++++++++- compiler/rustc_infer/src/infer/context.rs | 8 +--- .../src/infer/solver_region_constraints.rs | 45 ++++++++++++++++++ compiler/rustc_middle/src/infer/canonical.rs | 22 +++++++-- .../src/traits/outlives_bounds.rs | 2 +- .../src/traits/query/type_op/custom.rs | 46 +++++++++++-------- .../src/traits/query/type_op/mod.rs | 5 +- .../assumptions_on_binders/alias_outlives.rs | 2 +- .../alias_outlives.stderr | 8 +++- 10 files changed, 129 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index f845d9137f759..65c5167028e30 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -68,7 +68,8 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { #[instrument(skip(self), level = "debug")] pub(super) fn convert_all(&mut self, query_constraints: &QueryRegionConstraints<'tcx>) { - let QueryRegionConstraints { constraints, assumptions } = query_constraints; + let QueryRegionConstraints { constraints, assumptions, solver_constraints } = + query_constraints; let assumptions = elaborate::elaborate_outlives_assumptions(self.infcx.tcx, assumptions.iter().copied()); @@ -77,6 +78,10 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { self.convert(predicate, category, &assumptions); }); } + + if !solver_constraints.is_true() { + self.infcx.add_solver_region_constraint(solver_constraints.clone(), self.span); + } } /// Given an instance of the closure type, this method instantiates the "extra" requirements diff --git a/compiler/rustc_infer/src/infer/canonical/query_response.rs b/compiler/rustc_infer/src/infer/canonical/query_response.rs index cd35af8be73cf..8efa38c63a6ca 100644 --- a/compiler/rustc_infer/src/infer/canonical/query_response.rs +++ b/compiler/rustc_infer/src/infer/canonical/query_response.rs @@ -146,13 +146,14 @@ impl<'tcx> InferCtxt<'tcx> { let region_obligations = self.take_registered_region_obligations(); let region_assumptions = self.take_registered_region_assumptions(); debug!(?region_obligations); - let region_constraints = self.with_region_constraints(|region_constraints| { + let mut region_constraints = self.with_region_constraints(|region_constraints| { make_query_region_constraints( region_obligations, region_constraints, region_assumptions, ) }); + region_constraints.solver_constraints = self.clone_solver_region_constraints(); debug!(?region_constraints); let opaque_types = self @@ -214,6 +215,15 @@ impl<'tcx> InferCtxt<'tcx> { self.register_region_assumption(assumption); } + let solver_constraints = instantiate_value( + self.tcx, + &result_args, + query_response.value.region_constraints.solver_constraints.clone(), + ); + if !solver_constraints.is_true() { + self.add_solver_region_constraint(solver_constraints, cause.span); + } + let user_result: R = query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone()); @@ -347,6 +357,15 @@ impl<'tcx> InferCtxt<'tcx> { .map(|&r_c| instantiate_value(self.tcx, &result_args, r_c)), ); + let solver_constraints = instantiate_value( + self.tcx, + &result_args, + query_response.value.region_constraints.solver_constraints.clone(), + ); + output_query_region_constraints.solver_constraints = + std::mem::take(&mut output_query_region_constraints.solver_constraints) + .and(solver_constraints); + let user_result: R = query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone()); @@ -663,5 +682,5 @@ pub fn make_query_region_constraints<'tcx>( )) .collect(); - QueryRegionConstraints { constraints, assumptions } + QueryRegionConstraints { constraints, assumptions, solver_constraints: Default::default() } } diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index 84ffccd6f2c80..85d1f9a9cbb2a 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -334,13 +334,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { c: rustc_type_ir::region_constraint::RegionConstraint>, span: Span, ) { - let mut inner = self.inner.borrow_mut(); - use rustc_data_structures::undo_log::UndoLogs; - - use crate::infer::UndoLog; - let previous_was_and = inner.solver_region_constraint_storage.is_and(); - inner.undo_log.push(UndoLog::PushSolverRegionConstraint { previous_was_and }); - inner.solver_region_constraint_storage.push(c, span); + self.add_solver_region_constraint(c, span); } fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) { diff --git a/compiler/rustc_infer/src/infer/solver_region_constraints.rs b/compiler/rustc_infer/src/infer/solver_region_constraints.rs index 885bf53dbe6c2..619b9ae8e7db5 100644 --- a/compiler/rustc_infer/src/infer/solver_region_constraints.rs +++ b/compiler/rustc_infer/src/infer/solver_region_constraints.rs @@ -5,6 +5,8 @@ use rustc_type_ir::region_constraint::{ }; use tracing::instrument; +use super::InferCtxt; + pub(crate) type SolverRegionConstraint<'tcx> = SpannedRegionConstraint>; #[derive(Clone, Debug)] @@ -23,6 +25,10 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> { self.0.clone().without_spans() } + fn take(&mut self) -> SolverRegionConstraint<'tcx> { + core::mem::take(&mut self.0) + } + pub(crate) fn is_and(&self) -> bool { self.0.is_and() } @@ -73,5 +79,44 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> { } } +impl<'tcx> InferCtxt<'tcx> { + pub fn add_solver_region_constraint( + &self, + constraint: UnspannedRegionConstraint>, + span: Span, + ) { + use rustc_data_structures::undo_log::UndoLogs; + + use super::UndoLog; + + let mut inner = self.inner.borrow_mut(); + let previous_was_and = inner.solver_region_constraint_storage.is_and(); + inner.undo_log.push(UndoLog::PushSolverRegionConstraint { previous_was_and }); + inner.solver_region_constraint_storage.push(constraint, span); + } + + pub(crate) fn clone_solver_region_constraints( + &self, + ) -> UnspannedRegionConstraint> { + self.inner.borrow().solver_region_constraint_storage.get_unspanned_constraint() + } + + /// Runs `op` with an empty solver-region-constraint store, restores the + /// caller's constraints, and returns the constraints produced by `op`. + pub fn with_fresh_solver_region_constraints( + &self, + op: impl FnOnce() -> R, + ) -> (R, UnspannedRegionConstraint>) { + assert!(!self.in_snapshot(), "cannot isolate solver region constraints in a snapshot"); + + let previous = self.inner.borrow_mut().solver_region_constraint_storage.take(); + let result = op(); + let current = self.inner.borrow_mut().solver_region_constraint_storage.take(); + self.inner.borrow_mut().solver_region_constraint_storage.overwrite_spanned(previous); + + (result, current.without_spans()) + } +} + #[cfg(test)] mod tests; diff --git a/compiler/rustc_middle/src/infer/canonical.rs b/compiler/rustc_middle/src/infer/canonical.rs index 46429f7adfb12..cfe5cf77b7f85 100644 --- a/compiler/rustc_middle/src/infer/canonical.rs +++ b/compiler/rustc_middle/src/infer/canonical.rs @@ -76,13 +76,21 @@ pub struct QueryResponse<'tcx, R> { pub value: R, } -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Hash)] #[derive(StableHash, TypeFoldable, TypeVisitable)] pub struct QueryRegionConstraints<'tcx> { pub constraints: Vec>, pub assumptions: Vec>, + /// Region constraints emitted by the next solver under + /// `-Zassumptions-on-binders`. + /// + /// These stay unspanned while passing through a canonical query. The type-op + /// caller attaches its origin span when consuming the response. + pub solver_constraints: ir::region_constraint::RegionConstraint>, } +impl Eq for QueryRegionConstraints<'_> {} + impl QueryRegionConstraints<'_> { /// Represents an empty (trivially true) set of region constraints. /// @@ -91,8 +99,16 @@ impl QueryRegionConstraints<'_> { /// discharge a requirement from another query, which is a potential problem if we did throw /// away these assumptions because there were no constraints. pub fn is_empty(&self) -> bool { - let QueryRegionConstraints { constraints, assumptions } = self; - constraints.is_empty() && assumptions.is_empty() + self.constraints.is_empty() + && self.assumptions.is_empty() + && self.solver_constraints.is_true() + } + + pub fn extend(&mut self, other: &Self) { + self.constraints.extend(other.constraints.iter().cloned()); + self.assumptions.extend(other.assumptions.iter().cloned()); + self.solver_constraints = + std::mem::take(&mut self.solver_constraints).and(other.solver_constraints.clone()); } } diff --git a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs index 84cae1e7bfa0a..1501ed3190885 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs @@ -82,7 +82,7 @@ fn implied_outlives_bounds<'a, 'tcx>( // FIXME(higher_ranked_auto): Should we register assumptions here? // We otherwise would get spurious errors if normalizing an implied // outlives bound required proving some higher-ranked coroutine obl. - let QueryRegionConstraints { constraints, assumptions: _ } = constraints; + let QueryRegionConstraints { constraints, .. } = constraints; let cause = ObligationCause::misc(span, body_def_id); for &QueryRegionConstraint { constraint, visible_for_leak_check: vis, .. } in &constraints { match constraint { diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs index 25385d15e36f4..3f58d64df0c61 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs @@ -60,8 +60,8 @@ impl fmt::Debug for CustomTypeOp { } } -/// Executes `op` and then scrapes out all the "old style" region -/// constraints that result, creating query-region-constraints. +/// Executes `op` and then scrapes out all resulting region constraints, +/// creating query-region-constraints. pub fn scrape_region_constraints<'tcx, Op, R>( infcx: &InferCtxt<'tcx>, root_def_id: LocalDefId, @@ -89,10 +89,11 @@ where "scrape_region_constraints: incoming region assumptions = {pre_assumptions:#?}", ); - let value = infcx.commit_if_ok(|_| { - let ocx = ObligationCtxt::new(infcx); - let value = op(&ocx).map_err(|_| { - infcx.tcx.check_potentially_region_dependent_goals(root_def_id).err().unwrap_or_else( + let (value, solver_constraints) = infcx.with_fresh_solver_region_constraints(|| { + infcx.commit_if_ok(|_| { + let ocx = ObligationCtxt::new(infcx); + let value = op(&ocx).map_err(|_| { + infcx.tcx.check_potentially_region_dependent_goals(root_def_id).err().unwrap_or_else( // FIXME: In this region-dependent context, `type_op` should only fail due to // region-dependent goals. Any other kind of failure indicates a bug and we // should ICE. @@ -125,19 +126,23 @@ where .dcx() .span_delayed_bug(span, format!("error performing operation: {name}")) }, - ) - })?; - let errors = ocx.evaluate_obligations_error_on_ambiguity(); - if errors.no_errors() { - Ok(value) - } else if let Err(guar) = infcx.tcx.check_potentially_region_dependent_goals(root_def_id) { - Err(guar) - } else { - Err(infcx.dcx().delayed_bug(format!( - "errors selecting obligation during MIR typeck: {name} {root_def_id:?} {errors:?}" - ))) - } - })?; + ) + })?; + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + if errors.no_errors() { + Ok(value) + } else if let Err(guar) = + infcx.tcx.check_potentially_region_dependent_goals(root_def_id) + { + Err(guar) + } else { + Err(infcx.dcx().delayed_bug(format!( + "errors selecting obligation during MIR typeck: {name} {root_def_id:?} {errors:?}" + ))) + } + }) + }); + let value = value?; // Next trait solver performs operations locally, and normalize goals should resolve vars. let value = infcx.resolve_vars_if_possible(value); @@ -145,11 +150,12 @@ where let region_obligations = infcx.take_registered_region_obligations(); let region_assumptions = infcx.take_registered_region_assumptions(); let region_constraint_data = infcx.take_and_reset_region_constraints(); - let region_constraints = query_response::make_query_region_constraints( + let mut region_constraints = query_response::make_query_region_constraints( region_obligations, ®ion_constraint_data, region_assumptions, ); + region_constraints.solver_constraints = solver_constraints; if region_constraints.is_empty() { Ok(( diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs index 62d636f2de046..ad18afdf48169 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs @@ -163,9 +163,8 @@ where Ok(output) })?; output.error_info = error_info; - if let Some(QueryRegionConstraints { constraints, assumptions }) = output.constraints { - region_constraints.constraints.extend(constraints.iter().cloned()); - region_constraints.assumptions.extend(assumptions.iter().cloned()); + if let Some(constraints) = output.constraints { + region_constraints.extend(constraints); } output.constraints = if region_constraints.is_empty() { None diff --git a/tests/ui/assumptions_on_binders/alias_outlives.rs b/tests/ui/assumptions_on_binders/alias_outlives.rs index 0c2ed6585cf45..c529461458ded 100644 --- a/tests/ui/assumptions_on_binders/alias_outlives.rs +++ b/tests/ui/assumptions_on_binders/alias_outlives.rs @@ -23,11 +23,11 @@ where } fn borrowck_env_fail<'a, T: AliasHaver>() -// FIXME: ^ this should raise an ERROR: unsatisfied lifetime constraint from -Zassumptions-on-binders where ::Assoc: 'a, { let _: ReqTrait; + //~^ ERROR: higher-ranked lifetime bound could not be satisfied } const REGIONCK_ENV_PASS<'a, T: AliasHaver>: ReqTrait = todo!() diff --git a/tests/ui/assumptions_on_binders/alias_outlives.stderr b/tests/ui/assumptions_on_binders/alias_outlives.stderr index 1787c1912ae4f..55672d3ee96aa 100644 --- a/tests/ui/assumptions_on_binders/alias_outlives.stderr +++ b/tests/ui/assumptions_on_binders/alias_outlives.stderr @@ -4,5 +4,11 @@ error: higher-ranked lifetime bound could not be satisfied LL | const REGIONCK_ENV_FAIL<'a, T: AliasHaver>: ReqTrait = todo!() | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error: higher-ranked lifetime bound could not be satisfied + --> $DIR/alias_outlives.rs:29:12 + | +LL | let _: ReqTrait; + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors From 2959d13d98b234138ff3e029b9a7870f3ff96d3c Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sat, 22 Aug 2026 20:42:08 -0300 Subject: [PATCH 2/2] Register solver constraints from implied bounds --- .../src/traits/outlives_bounds.rs | 6 ++++- .../assumptions_on_binders/alias_outlives.rs | 26 +++++++++++++++++++ .../alias_outlives.stderr | 11 +++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs index 1501ed3190885..f881e5fbbda2f 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs @@ -82,7 +82,11 @@ fn implied_outlives_bounds<'a, 'tcx>( // FIXME(higher_ranked_auto): Should we register assumptions here? // We otherwise would get spurious errors if normalizing an implied // outlives bound required proving some higher-ranked coroutine obl. - let QueryRegionConstraints { constraints, .. } = constraints; + let QueryRegionConstraints { constraints, solver_constraints, .. } = constraints; + if !solver_constraints.is_true() { + infcx.add_solver_region_constraint(solver_constraints, span); + } + let cause = ObligationCause::misc(span, body_def_id); for &QueryRegionConstraint { constraint, visible_for_leak_check: vis, .. } in &constraints { match constraint { diff --git a/tests/ui/assumptions_on_binders/alias_outlives.rs b/tests/ui/assumptions_on_binders/alias_outlives.rs index c529461458ded..9e1516132dd9d 100644 --- a/tests/ui/assumptions_on_binders/alias_outlives.rs +++ b/tests/ui/assumptions_on_binders/alias_outlives.rs @@ -39,4 +39,30 @@ const REGIONCK_ENV_FAIL<'a, T: AliasHaver>: ReqTrait = todo!() where ::Assoc: 'a; +// Solver constraints produced while normalizing implied bounds must be returned +// to lexical regionck. +trait Project { + type Assoc; +} + +impl Project for (T,) +where + T::Assoc: for<'a> Trait<'a>, +{ + type Assoc = (); +} + +struct Normalizes(T) +where + T::Assoc: Clone; + +trait TestTrait {} + +impl<'a, T: AliasHaver> TestTrait for [Normalizes<(T,)>; 1] +//~^ ERROR: higher-ranked lifetime bound could not be satisfied +where + T::Assoc: 'a, +{ +} + fn main() {} diff --git a/tests/ui/assumptions_on_binders/alias_outlives.stderr b/tests/ui/assumptions_on_binders/alias_outlives.stderr index 55672d3ee96aa..a6eaf3e11bad7 100644 --- a/tests/ui/assumptions_on_binders/alias_outlives.stderr +++ b/tests/ui/assumptions_on_binders/alias_outlives.stderr @@ -4,11 +4,20 @@ error: higher-ranked lifetime bound could not be satisfied LL | const REGIONCK_ENV_FAIL<'a, T: AliasHaver>: ReqTrait = todo!() | ^^^^^^^^^^^^^^^^^^ +error: higher-ranked lifetime bound could not be satisfied + --> $DIR/alias_outlives.rs:61:1 + | +LL | / impl<'a, T: AliasHaver> TestTrait for [Normalizes<(T,)>; 1] +LL | | +LL | | where +LL | | T::Assoc: 'a, + | |_________________^ + error: higher-ranked lifetime bound could not be satisfied --> $DIR/alias_outlives.rs:29:12 | LL | let _: ReqTrait; | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors