From a80f20df1ed7121908b65abfa6393beeaae7d702 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Tue, 8 Sep 2026 11:28:36 -0300 Subject: [PATCH] Keep type-op region constraints in borrowck Under `-Zassumptions-on-binders` the next solver stores its region constraints in the `InferCtxt` instead of registering region obligations. The canonical type-op path never copied them into `QueryResponse`, so they were dropped once the query's inference context went away. Borrowck then saw a type op with no constraints and lost the outlives error entirely. `QueryRegionConstraints` now carries the constraint next to the old style constraints and assumptions. It stays unspanned while passing through a canonical query and the caller attaches its own origin span when consuming the response. Borrowck accumulates these in `MirTypeckRegionConstraints` through `ConstraintConversion`, the same way it handles everything else, and destructures them into NLL outlives constraints at the end of MIR type checking. Implied bound normalization was dropping the same constraint before lexical regionck, so that path registers it now as well. --- .../rustc_borrowck/src/handle_placeholders.rs | 8 +++ .../src/type_check/constraint_conversion.rs | 6 ++- compiler/rustc_borrowck/src/type_check/mod.rs | 30 +++++++++++ .../src/infer/canonical/query_response.rs | 52 ++++++++++++------- .../src/infer/outlives/obligations.rs | 11 ++-- .../src/infer/solver_region_constraints.rs | 19 ++++++- .../infer/solver_region_constraints/tests.rs | 22 ++++++++ compiler/rustc_middle/src/infer/canonical.rs | 29 +++++++++-- .../src/solve/delegate.rs | 5 ++ .../src/traits/outlives_bounds.rs | 5 +- .../src/traits/query/type_op/custom.rs | 11 +++- .../src/traits/query/type_op/mod.rs | 5 +- .../rustc_traits/src/coroutine_witnesses.rs | 4 ++ .../assumptions_on_binders/alias_outlives.rs | 28 +++++++++- .../alias_outlives.stderr | 17 +++++- 15 files changed, 215 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_borrowck/src/handle_placeholders.rs b/compiler/rustc_borrowck/src/handle_placeholders.rs index 11b8890346dfb..bdb30090925d0 100644 --- a/compiler/rustc_borrowck/src/handle_placeholders.rs +++ b/compiler/rustc_borrowck/src/handle_placeholders.rs @@ -246,8 +246,16 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( mut outlives_constraints, universe_causes, type_tests, + solver_constraints, } = constraints; + // These have already been destructured into `outlives_constraints` at the + // end of MIR type checking. + assert!( + solver_constraints.is_true(), + "solver region constraints not lowered to NLL = {solver_constraints:#?}", + ); + let fr_static = universal_regions.fr_static; let compute_sccs = |constraints: &OutlivesConstraintSet<'tcx>, diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index f845d9137f759..f11e3b8fd6d80 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,9 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { self.convert(predicate, category, &assumptions); }); } + + self.constraints + .register_solver_constraint(solver_constraints.clone().with_spans(self.span)); } /// Given an instance of the closure type, this method instantiates the "extra" requirements diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 9f23a0d5ab631..ed080c26d60f3 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -17,6 +17,7 @@ use rustc_infer::infer::outlives::env::RegionBoundPairs; use rustc_infer::infer::region_constraints::RegionConstraintData; use rustc_infer::infer::{ BoundRegionConversionTime, InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin, + SolverRegionConstraint, }; use rustc_infer::traits::{Obligation, ObligationCause, PredicateObligations}; use rustc_middle::bug; @@ -113,6 +114,7 @@ pub(crate) fn type_check<'tcx>( outlives_constraints: OutlivesConstraintSet::default(), type_tests: Vec::default(), universe_causes: FxIndexMap::default(), + solver_constraints: SolverRegionConstraint::new_true(), }; let CreateResult { @@ -134,6 +136,13 @@ pub(crate) fn type_check<'tcx>( pre_assumptions.is_empty(), "there should be no incoming region assumptions = {pre_assumptions:#?}", ); + // Solver region constraints from computing the implied bounds went through + // `ConstraintConversion` and are already stored in `constraints`. + let pre_solver_constraints = infcx.take_solver_region_constraints(); + assert!( + pre_solver_constraints.is_true(), + "there should be no incoming solver region constraints = {pre_solver_constraints:#?}", + ); } debug!(?normalized_inputs_and_output); @@ -174,6 +183,10 @@ pub(crate) fn type_check<'tcx>( let polonius_context = typeck.polonius_context; if infcx.tcx.assumptions_on_binders() { + let solver_constraints = mem::replace( + &mut typeck.constraints.solver_constraints, + SolverRegionConstraint::new_true(), + ); let mut converter = constraint_conversion::ConstraintConversion::new( typeck.infcx, typeck.universal_regions, @@ -185,6 +198,7 @@ pub(crate) fn type_check<'tcx>( typeck.constraints, ); typeck.infcx.destructure_solver_region_constraints_for_borrowck( + solver_constraints, &mut converter, typeck.known_type_outlives_obligations, universal_region_relations.outlives.clone(), @@ -293,9 +307,25 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> { pub(crate) universe_causes: FxIndexMap>, pub(crate) type_tests: Vec>, + + /// The region constraints emitted by the next solver under + /// `-Zassumptions-on-binders`. Unlike the constraints above these are not yet + /// lowered to NLL, we destructure them into `outlives_constraints` at the end + /// of MIR type checking. + pub(crate) solver_constraints: SolverRegionConstraint<'tcx>, } impl<'tcx> MirTypeckRegionConstraints<'tcx> { + /// Adds `constraint` to the constraints we've accumulated so far. + pub(crate) fn register_solver_constraint(&mut self, constraint: SolverRegionConstraint<'tcx>) { + // FIXME(-Zassumptions-on-binders): This is pretty bad for perf, we rebuild the + // entire constraint every time instead of updating it incrementally. + self.solver_constraints = SolverRegionConstraint::build_and( + constraint, + mem::replace(&mut self.solver_constraints, SolverRegionConstraint::new_true()), + ); + } + /// Creates a `Region` for a given `PlaceholderRegion`, or returns the /// region that corresponds to a previously created one. pub(crate) fn placeholder_region( diff --git a/compiler/rustc_infer/src/infer/canonical/query_response.rs b/compiler/rustc_infer/src/infer/canonical/query_response.rs index cd35af8be73cf..d1c4480b59347 100644 --- a/compiler/rustc_infer/src/infer/canonical/query_response.rs +++ b/compiler/rustc_infer/src/infer/canonical/query_response.rs @@ -146,11 +146,13 @@ impl<'tcx> InferCtxt<'tcx> { let region_obligations = self.take_registered_region_obligations(); let region_assumptions = self.take_registered_region_assumptions(); debug!(?region_obligations); + let solver_constraints = self.take_solver_region_constraints(); let region_constraints = self.with_region_constraints(|region_constraints| { make_query_region_constraints( region_obligations, region_constraints, region_assumptions, + solver_constraints, ) }); debug!(?region_constraints); @@ -195,9 +197,10 @@ impl<'tcx> InferCtxt<'tcx> { let InferOk { value: result_args, obligations } = self.query_response_instantiation(cause, param_env, original_values, query_response)?; - for QueryRegionConstraint { constraint, visible_for_leak_check: vis, .. } in - &query_response.value.region_constraints.constraints - { + let QueryRegionConstraints { constraints, assumptions, solver_constraints } = + &query_response.value.region_constraints; + + for QueryRegionConstraint { constraint, visible_for_leak_check: vis, .. } in constraints { let constraint = instantiate_value(self.tcx, &result_args, *constraint); match constraint { ty::RegionConstraint::Outlives(clause) => { @@ -209,11 +212,15 @@ impl<'tcx> InferCtxt<'tcx> { } } - for assumption in &query_response.value.region_constraints.assumptions { + for assumption in assumptions { let assumption = instantiate_value(self.tcx, &result_args, *assumption); self.register_region_assumption(assumption); } + let solver_constraints = + instantiate_value(self.tcx, &result_args, solver_constraints.clone()); + self.register_solver_region_constraint(solver_constraints.with_spans(cause.span)); + let user_result: R = query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone()); @@ -325,27 +332,31 @@ impl<'tcx> InferCtxt<'tcx> { } } + let QueryRegionConstraints { constraints, assumptions, solver_constraints } = + &query_response.value.region_constraints; + // ...also include the other query region constraints from the query. - output_query_region_constraints.constraints.extend( - query_response.value.region_constraints.constraints.iter().filter_map(|&r_c| { - let r_c = instantiate_value(self.tcx, &result_args, r_c); + output_query_region_constraints.constraints.extend(constraints.iter().filter_map(|&r_c| { + let r_c = instantiate_value(self.tcx, &result_args, r_c); - // Screen out `'a: 'a` or `'a == 'a` cases. - if r_c.constraint.is_trivial() { None } else { Some(r_c) } - }), - ); + // Screen out `'a: 'a` or `'a == 'a` cases. + if r_c.constraint.is_trivial() { None } else { Some(r_c) } + })); // FIXME(higher_ranked_auto): Optimize this to instantiate all assumptions // at once, rather than calling `instantiate_value` repeatedly which may // create more universes. - output_query_region_constraints.assumptions.extend( - query_response - .value - .region_constraints - .assumptions - .iter() - .map(|&r_c| instantiate_value(self.tcx, &result_args, r_c)), - ); + output_query_region_constraints + .assumptions + .extend(assumptions.iter().map(|&r_c| instantiate_value(self.tcx, &result_args, r_c))); + + let solver_constraints = + instantiate_value(self.tcx, &result_args, solver_constraints.clone()); + output_query_region_constraints.solver_constraints = + ty::region_constraint::RegionConstraint::build_and( + std::mem::take(&mut output_query_region_constraints.solver_constraints), + solver_constraints, + ); let user_result: R = query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone()); @@ -619,6 +630,7 @@ pub fn make_query_region_constraints<'tcx>( outlives_obligations: Vec>, region_constraints: &RegionConstraintData<'tcx>, assumptions: Vec>, + solver_constraints: ty::region_constraint::RegionConstraint>, ) -> QueryRegionConstraints<'tcx> { let RegionConstraintData { constraints, verifys } = region_constraints; @@ -663,5 +675,5 @@ pub fn make_query_region_constraints<'tcx>( )) .collect(); - QueryRegionConstraints { constraints, assumptions } + QueryRegionConstraints { constraints, assumptions, solver_constraints } } diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 42f686b39136b..c7fafd00ce740 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -238,11 +238,16 @@ impl<'tcx> InferCtxt<'tcx> { outlives_env.known_type_outlives().into_iter().cloned().collect(), outlives_env.free_region_map().relation.clone(), ); - self.destructure_solver_region_constraints(assumptions, self); + let constraint = self.inner.borrow().solver_region_constraint_storage.get_constraint(); + self.destructure_solver_region_constraints(constraint, assumptions, self); } + /// Unlike regionck, borrowck doesn't keep these constraints in the `InferCtxt`. + /// It stores them in `MirTypeckRegionConstraints` alongside its other region + /// constraints, so it hands us the constraint to destructure. pub fn destructure_solver_region_constraints_for_borrowck( &self, + constraint: SolverRegionConstraint<'tcx>, // this is always ConstraintConversion but lol conversion: impl TypeOutlivesDelegate<'tcx>, known_type_outlives: &[PolyTypeOutlivesClause<'tcx>], @@ -252,19 +257,19 @@ impl<'tcx> InferCtxt<'tcx> { known_type_outlives.into_iter().cloned().collect(), region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(), ); - self.destructure_solver_region_constraints(assumptions, conversion); + self.destructure_solver_region_constraints(constraint, assumptions, conversion); } #[instrument(level = "debug", skip(self, conversion))] pub fn destructure_solver_region_constraints( &self, + constraint: SolverRegionConstraint<'tcx>, assumptions: rustc_type_ir::region_constraint::Assumptions>, mut conversion: impl TypeOutlivesDelegate<'tcx>, ) { assert!(self.tcx.assumptions_on_binders()); assert!(self.next_trait_solver()); - let constraint = self.inner.borrow().solver_region_constraint_storage.get_constraint(); debug!(?constraint); let constraint = region_constraint::destructure_type_outlives_constraints_in_root( self, diff --git a/compiler/rustc_infer/src/infer/solver_region_constraints.rs b/compiler/rustc_infer/src/infer/solver_region_constraints.rs index 09baae2ca8a42..965ccf70442b8 100644 --- a/compiler/rustc_infer/src/infer/solver_region_constraints.rs +++ b/compiler/rustc_infer/src/infer/solver_region_constraints.rs @@ -1,9 +1,11 @@ use rustc_middle::ty::TyCtxt; use rustc_span::Span; +use rustc_type_ir::region_constraint::RegionConstraint; use tracing::instrument; -pub type SolverRegionConstraint<'tcx> = - rustc_type_ir::region_constraint::RegionConstraint, Span>; +use super::InferCtxt; + +pub type SolverRegionConstraint<'tcx> = RegionConstraint, Span>; #[derive(Clone, Debug)] pub(crate) struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>); @@ -17,11 +19,24 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> { self.0.clone() } + pub(crate) fn take(&mut self) -> SolverRegionConstraint<'tcx> { + core::mem::replace(&mut self.0, SolverRegionConstraint::new_true()) + } + #[instrument(level = "debug", skip(self))] pub(crate) fn overwrite(&mut self, constraint: SolverRegionConstraint<'tcx>) { self.0 = constraint; } } +impl<'tcx> InferCtxt<'tcx> { + /// Trait queries just want to pass back the solver region constraints "as is", + /// mirroring `take_registered_region_obligations`. + pub fn take_solver_region_constraints(&self) -> RegionConstraint> { + assert!(!self.in_snapshot(), "cannot take solver region constraints in a snapshot"); + self.inner.borrow_mut().solver_region_constraint_storage.take().without_spans() + } +} + #[cfg(test)] mod tests; diff --git a/compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs b/compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs index 529ecf43f5ee1..540aa5e4f8cb2 100644 --- a/compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs +++ b/compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs @@ -1,7 +1,29 @@ +use rustc_middle::infer::canonical::QueryRegionConstraints; use rustc_middle::ty::TyCtxt; use rustc_span::{BytePos, Span}; use rustc_type_ir::region_constraint::{And, LeafRegionConstraint, Or}; +use super::{SolverRegionConstraint, SolverRegionConstraintStorage}; + +#[test] +fn true_constraint_keeps_query_response_empty() { + // Mirrors `register_solver_region_constraint`, which registers unconditionally: + // anding a trivially true constraint into an empty store has to leave the store + // trivially true, as the resulting query response would otherwise no longer be + // empty. This relies on `And`/`Or` being kept in canonical form. + let mut storage = SolverRegionConstraintStorage::<'static>::new(); + storage.overwrite(SolverRegionConstraint::build_and( + SolverRegionConstraint::new_true(), + storage.get_constraint(), + )); + + let constraints = QueryRegionConstraints { + solver_constraints: storage.get_constraint().without_spans(), + ..Default::default() + }; + assert!(constraints.is_empty()); +} + #[test] fn canonicalization_preserves_only_one_ambiguity() { let first = Span::with_root_ctxt(BytePos(1), BytePos(2)); diff --git a/compiler/rustc_middle/src/infer/canonical.rs b/compiler/rustc_middle/src/infer/canonical.rs index 46429f7adfb12..f99280bb744a1 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,23 @@ 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() + let QueryRegionConstraints { constraints, assumptions, solver_constraints } = self; + constraints.is_empty() && assumptions.is_empty() && solver_constraints.is_true() + } + + pub fn extend(&mut self, other: &Self) { + let QueryRegionConstraints { constraints, assumptions, solver_constraints } = self; + let QueryRegionConstraints { + constraints: other_constraints, + assumptions: other_assumptions, + solver_constraints: other_solver_constraints, + } = other; + constraints.extend(other_constraints.iter().cloned()); + assumptions.extend(other_assumptions.iter().cloned()); + *solver_constraints = ir::region_constraint::RegionConstraint::build_and( + std::mem::take(solver_constraints), + other_solver_constraints.clone(), + ); } } diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 818d8e1a4e0c3..60339b1982233 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -374,6 +374,11 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< region_obligations, region_constraints, region_assumptions, + // We're only called with `-Zassumptions-on-binders` disabled, in which + // case the solver never emits new-style region constraints. With it + // enabled the solver instead returns `ExternalRegionConstraints::NextGen`, + // reading the constraint straight out of the `InferCtxt`. + Default::default(), ) }); diff --git a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs index 84cae1e7bfa0a..bcb932f74b1f8 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs @@ -82,7 +82,10 @@ 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, assumptions: _, solver_constraints } = + constraints; + infcx.register_solver_region_constraint(solver_constraints.with_spans(span)); + 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..39f439cad64d3 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 +/// in the `infcx`, creating query-region-constraints. pub fn scrape_region_constraints<'tcx, Op, R>( infcx: &InferCtxt<'tcx>, root_def_id: LocalDefId, @@ -88,6 +88,11 @@ where pre_assumptions.is_empty(), "scrape_region_constraints: incoming region assumptions = {pre_assumptions:#?}", ); + let pre_solver_constraints = infcx.take_solver_region_constraints(); + assert!( + pre_solver_constraints.is_true(), + "scrape_region_constraints: incoming solver constraints = {pre_solver_constraints:#?}", + ); let value = infcx.commit_if_ok(|_| { let ocx = ObligationCtxt::new(infcx); @@ -144,11 +149,13 @@ where let region_obligations = infcx.take_registered_region_obligations(); let region_assumptions = infcx.take_registered_region_assumptions(); + let solver_constraints = infcx.take_solver_region_constraints(); let region_constraint_data = infcx.take_and_reset_region_constraints(); let region_constraints = query_response::make_query_region_constraints( region_obligations, ®ion_constraint_data, region_assumptions, + solver_constraints, ); if region_constraints.is_empty() { 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 250579a2b064a..ac516afbc783c 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 @@ -151,9 +151,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/compiler/rustc_traits/src/coroutine_witnesses.rs b/compiler/rustc_traits/src/coroutine_witnesses.rs index 762471eefe4dd..b7ff4cca0bb1f 100644 --- a/compiler/rustc_traits/src/coroutine_witnesses.rs +++ b/compiler/rustc_traits/src/coroutine_witnesses.rs @@ -84,6 +84,10 @@ fn compute_assumptions<'tcx>( region_obligations, ®ion_constraints, region_assumptions, + // We return early above unless the old solver is used globally, while + // `-Zassumptions-on-binders` enables the next solver globally. So there + // are never any new-style region constraints to pass along here. + Default::default(), ) .constraints .fold_with(&mut OpportunisticRegionResolver::new(&infcx)); diff --git a/tests/ui/assumptions_on_binders/alias_outlives.rs b/tests/ui/assumptions_on_binders/alias_outlives.rs index 0c2ed6585cf45..9e1516132dd9d 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!() @@ -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 1787c1912ae4f..a6eaf3e11bad7 100644 --- a/tests/ui/assumptions_on_binders/alias_outlives.stderr +++ b/tests/ui/assumptions_on_binders/alias_outlives.stderr @@ -4,5 +4,20 @@ 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: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 3 previous errors