Skip to content

Avoid obligation construction dance with query region constraints #141392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 26, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
@@ -742,7 +742,7 @@ fn ty_known_to_outlive<'tcx>(
region: ty::Region<'tcx>,
) -> bool {
test_region_obligations(tcx, id, param_env, wf_tys, |infcx| {
infcx.register_region_obligation(infer::RegionObligation {
infcx.register_type_outlives_constraint_inner(infer::TypeOutlivesConstraint {
sub_region: region,
sup_type: ty,
origin: infer::RelateParamBound(DUMMY_SP, ty, None),
62 changes: 8 additions & 54 deletions compiler/rustc_infer/src/infer/canonical/query_response.rs
Original file line number Diff line number Diff line change
@@ -12,23 +12,20 @@ use std::iter;

use rustc_index::{Idx, IndexVec};
use rustc_middle::arena::ArenaAllocatable;
use rustc_middle::bug;
use rustc_middle::mir::ConstraintCategory;
use rustc_middle::ty::{self, BoundVar, GenericArg, GenericArgKind, Ty, TyCtxt, TypeFoldable};
use rustc_middle::{bug, span_bug};
use tracing::{debug, instrument};

use crate::infer::canonical::instantiate::{CanonicalExt, instantiate_value};
use crate::infer::canonical::{
Canonical, CanonicalQueryResponse, CanonicalVarValues, Certainty, OriginalQueryValues,
QueryOutlivesConstraint, QueryRegionConstraints, QueryResponse,
QueryRegionConstraints, QueryResponse,
};
use crate::infer::region_constraints::{Constraint, RegionConstraintData};
use crate::infer::{DefineOpaqueTypes, InferCtxt, InferOk, InferResult, SubregionOrigin};
use crate::traits::query::NoSolution;
use crate::traits::{
Obligation, ObligationCause, PredicateObligation, PredicateObligations, ScrubbedTraitError,
TraitEngine,
};
use crate::traits::{ObligationCause, PredicateObligations, ScrubbedTraitError, TraitEngine};

impl<'tcx> InferCtxt<'tcx> {
/// This method is meant to be invoked as the final step of a canonical query
@@ -169,15 +166,13 @@ impl<'tcx> InferCtxt<'tcx> {
where
R: Debug + TypeFoldable<TyCtxt<'tcx>>,
{
let InferOk { value: result_args, mut obligations } =
let InferOk { value: result_args, obligations } =
self.query_response_instantiation(cause, param_env, original_values, query_response)?;

obligations.extend(self.query_outlives_constraints_into_obligations(
cause,
param_env,
&query_response.value.region_constraints.outlives,
&result_args,
));
for (predicate, _category) in &query_response.value.region_constraints.outlives {
let predicate = instantiate_value(self.tcx, &result_args, *predicate);
self.register_outlives_constraint(predicate, cause);
}

let user_result: R =
query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone());
@@ -525,47 +520,6 @@ impl<'tcx> InferCtxt<'tcx> {
self.unify_canonical_vars(cause, param_env, original_values, instantiated_query_response)
}

/// Converts the region constraints resulting from a query into an
/// iterator of obligations.
fn query_outlives_constraints_into_obligations(
&self,
cause: &ObligationCause<'tcx>,
param_env: ty::ParamEnv<'tcx>,
uninstantiated_region_constraints: &[QueryOutlivesConstraint<'tcx>],
result_args: &CanonicalVarValues<'tcx>,
) -> impl Iterator<Item = PredicateObligation<'tcx>> {
uninstantiated_region_constraints.iter().map(move |&constraint| {
let predicate = instantiate_value(self.tcx, result_args, constraint);
self.query_outlives_constraint_to_obligation(predicate, cause.clone(), param_env)
})
}

pub fn query_outlives_constraint_to_obligation(
&self,
(predicate, _): QueryOutlivesConstraint<'tcx>,
cause: ObligationCause<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> Obligation<'tcx, ty::Predicate<'tcx>> {
let ty::OutlivesPredicate(k1, r2) = predicate;

let atom = match k1.unpack() {
GenericArgKind::Lifetime(r1) => ty::PredicateKind::Clause(
ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(r1, r2)),
),
GenericArgKind::Type(t1) => ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(
ty::OutlivesPredicate(t1, r2),
)),
GenericArgKind::Const(..) => {
// Consts cannot outlive one another, so we don't expect to
// encounter this branch.
span_bug!(cause.span, "unexpected const outlives {:?}", predicate);
}
};
let predicate = ty::Binder::dummy(atom);

Obligation::new(self.tcx, cause, param_env, predicate)
}

/// Given two sets of values for the same set of canonical variables, unify them.
/// The second set is produced lazily by supplying indices from the first set.
fn unify_canonical_vars(
2 changes: 1 addition & 1 deletion compiler/rustc_infer/src/infer/context.rs
Original file line number Diff line number Diff line change
@@ -214,7 +214,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
}

fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) {
self.register_region_obligation_with_cause(ty, r, &ObligationCause::dummy_with_span(span));
self.register_type_outlives_constraint(ty, r, &ObligationCause::dummy_with_span(span));
}

type OpaqueTypeStorageEntries = OpaqueTypeStorageEntries;
19 changes: 3 additions & 16 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
@@ -150,7 +150,7 @@ pub struct InferCtxtInner<'tcx> {
/// for each body-id in this map, which will process the
/// obligations within. This is expected to be done 'late enough'
/// that all type inference variables have been bound and so forth.
region_obligations: Vec<RegionObligation<'tcx>>,
region_obligations: Vec<TypeOutlivesConstraint<'tcx>>,

/// Caches for opaque type inference.
opaque_type_storage: OpaqueTypeStorage<'tcx>,
@@ -173,7 +173,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
}

#[inline]
pub fn region_obligations(&self) -> &[RegionObligation<'tcx>] {
pub fn region_obligations(&self) -> &[TypeOutlivesConstraint<'tcx>] {
&self.region_obligations
}

@@ -488,7 +488,7 @@ impl fmt::Display for FixupError {

/// See the `region_obligations` field for more information.
#[derive(Clone, Debug)]
pub struct RegionObligation<'tcx> {
pub struct TypeOutlivesConstraint<'tcx> {
pub sub_region: ty::Region<'tcx>,
pub sup_type: Ty<'tcx>,
pub origin: SubregionOrigin<'tcx>,
@@ -738,19 +738,6 @@ impl<'tcx> InferCtxt<'tcx> {
})
}

pub fn region_outlives_predicate(
&self,
cause: &traits::ObligationCause<'tcx>,
predicate: ty::PolyRegionOutlivesPredicate<'tcx>,
) {
self.enter_forall(predicate, |ty::OutlivesPredicate(r_a, r_b)| {
let origin = SubregionOrigin::from_obligation_cause(cause, || {
RelateRegionParamBound(cause.span, None)
});
self.sub_regions(origin, r_b, r_a); // `b : a` ==> `a <= b`
})
}

/// Number of type variables created so far.
pub fn num_ty_vars(&self) -> usize {
self.inner.borrow_mut().type_variables().num_vars()
51 changes: 44 additions & 7 deletions compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
@@ -76,23 +76,56 @@ use crate::infer::outlives::env::RegionBoundPairs;
use crate::infer::outlives::verify::VerifyBoundCx;
use crate::infer::resolve::OpportunisticRegionResolver;
use crate::infer::snapshot::undo_log::UndoLog;
use crate::infer::{self, GenericKind, InferCtxt, RegionObligation, SubregionOrigin, VerifyBound};
use crate::infer::{
self, GenericKind, InferCtxt, SubregionOrigin, TypeOutlivesConstraint, VerifyBound,
};
use crate::traits::{ObligationCause, ObligationCauseCode};

impl<'tcx> InferCtxt<'tcx> {
pub fn register_outlives_constraint(
&self,
ty::OutlivesPredicate(arg, r2): ty::OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>,
cause: &ObligationCause<'tcx>,
) {
match arg.unpack() {
ty::GenericArgKind::Lifetime(r1) => {
self.register_region_outlives_constraint(ty::OutlivesPredicate(r1, r2), cause);
}
ty::GenericArgKind::Type(ty1) => {
self.register_type_outlives_constraint(ty1, r2, cause);
}
ty::GenericArgKind::Const(_) => unreachable!(),
}
}

pub fn register_region_outlives_constraint(
&self,
ty::OutlivesPredicate(r_a, r_b): ty::RegionOutlivesPredicate<'tcx>,
cause: &ObligationCause<'tcx>,
) {
let origin = SubregionOrigin::from_obligation_cause(cause, || {
SubregionOrigin::RelateRegionParamBound(cause.span, None)
});
// `'a: 'b` ==> `'b <= 'a`
self.sub_regions(origin, r_b, r_a);
}

/// Registers that the given region obligation must be resolved
/// from within the scope of `body_id`. These regions are enqueued
/// and later processed by regionck, when full type information is
/// available (see `region_obligations` field for more
/// information).
#[instrument(level = "debug", skip(self))]
pub fn register_region_obligation(&self, obligation: RegionObligation<'tcx>) {
pub fn register_type_outlives_constraint_inner(
&self,
obligation: TypeOutlivesConstraint<'tcx>,
) {
let mut inner = self.inner.borrow_mut();
inner.undo_log.push(UndoLog::PushRegionObligation);
inner.undo_log.push(UndoLog::PushTypeOutlivesConstraint);
inner.region_obligations.push(obligation);
}

pub fn register_region_obligation_with_cause(
pub fn register_type_outlives_constraint(
&self,
sup_type: Ty<'tcx>,
sub_region: Region<'tcx>,
@@ -124,11 +157,15 @@ impl<'tcx> InferCtxt<'tcx> {
)
});

self.register_region_obligation(RegionObligation { sup_type, sub_region, origin });
self.register_type_outlives_constraint_inner(TypeOutlivesConstraint {
sup_type,
sub_region,
origin,
});
}

/// Trait queries just want to pass back type obligations "as is"
pub fn take_registered_region_obligations(&self) -> Vec<RegionObligation<'tcx>> {
pub fn take_registered_region_obligations(&self) -> Vec<TypeOutlivesConstraint<'tcx>> {
std::mem::take(&mut self.inner.borrow_mut().region_obligations)
}

@@ -166,7 +203,7 @@ impl<'tcx> InferCtxt<'tcx> {
);
}

for RegionObligation { sup_type, sub_region, origin } in my_region_obligations {
for TypeOutlivesConstraint { sup_type, sub_region, origin } in my_region_obligations {
let outlives = ty::Binder::dummy(ty::OutlivesPredicate(sup_type, sub_region));
let ty::OutlivesPredicate(sup_type, sub_region) =
deeply_normalize_ty(outlives, origin.clone())
4 changes: 2 additions & 2 deletions compiler/rustc_infer/src/infer/snapshot/undo_log.rs
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ pub(crate) enum UndoLog<'tcx> {
RegionConstraintCollector(region_constraints::UndoLog<'tcx>),
RegionUnificationTable(sv::UndoLog<ut::Delegate<RegionVidKey<'tcx>>>),
ProjectionCache(traits::UndoLog<'tcx>),
PushRegionObligation,
PushTypeOutlivesConstraint,
}

macro_rules! impl_from {
@@ -72,7 +72,7 @@ impl<'tcx> Rollback<UndoLog<'tcx>> for InferCtxtInner<'tcx> {
self.region_constraint_storage.as_mut().unwrap().unification_table.reverse(undo)
}
UndoLog::ProjectionCache(undo) => self.projection_cache.reverse(undo),
UndoLog::PushRegionObligation => {
UndoLog::PushTypeOutlivesConstraint => {
self.region_obligations.pop();
}
}
Original file line number Diff line number Diff line change
@@ -1024,7 +1024,7 @@ where
}

pub(super) fn register_region_outlives(&self, a: I::Region, b: I::Region) {
// `b : a` ==> `a <= b`
// `'a: 'b` ==> `'b <= 'a`
self.delegate.sub_regions(b, a, self.origin_span);
}

2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/solve/delegate.rs
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<
Some(HasChanged::No)
}
ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) => {
self.0.register_region_obligation_with_cause(
self.0.register_type_outlives_constraint(
outlives.0,
outlives.1,
&ObligationCause::dummy_with_span(span),
8 changes: 5 additions & 3 deletions compiler/rustc_trait_selection/src/traits/auto_trait.rs
Original file line number Diff line number Diff line change
@@ -726,7 +726,9 @@ impl<'tcx> AutoTraitFinder<'tcx> {
}
ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(binder)) => {
let binder = bound_predicate.rebind(binder);
selcx.infcx.region_outlives_predicate(&dummy_cause, binder)
selcx.infcx.enter_forall(binder, |pred| {
selcx.infcx.register_region_outlives_constraint(pred, &dummy_cause);
});
}
ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(binder)) => {
let binder = bound_predicate.rebind(binder);
@@ -735,14 +737,14 @@ impl<'tcx> AutoTraitFinder<'tcx> {
binder.map_bound_ref(|pred| pred.0).no_bound_vars(),
) {
(None, Some(t_a)) => {
selcx.infcx.register_region_obligation_with_cause(
selcx.infcx.register_type_outlives_constraint(
t_a,
selcx.infcx.tcx.lifetimes.re_static,
&dummy_cause,
);
}
(Some(ty::OutlivesPredicate(t_a, r_b)), _) => {
selcx.infcx.register_region_obligation_with_cause(
selcx.infcx.register_type_outlives_constraint(
t_a,
r_b,
&dummy_cause,
4 changes: 2 additions & 2 deletions compiler/rustc_trait_selection/src/traits/fulfill.rs
Original file line number Diff line number Diff line change
@@ -428,7 +428,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {

ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(data)) => {
if infcx.considering_regions {
infcx.region_outlives_predicate(&obligation.cause, Binder::dummy(data));
infcx.register_region_outlives_constraint(data, &obligation.cause);
}

ProcessResult::Changed(Default::default())
@@ -439,7 +439,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {
r_b,
))) => {
if infcx.considering_regions {
infcx.register_region_obligation_with_cause(t_a, r_b, &obligation.cause);
infcx.register_type_outlives_constraint(t_a, r_b, &obligation.cause);
}
ProcessResult::Changed(Default::default())
}
21 changes: 3 additions & 18 deletions compiler/rustc_trait_selection/src/traits/outlives_bounds.rs
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ use rustc_span::def_id::LocalDefId;
use tracing::instrument;

use crate::infer::InferCtxt;
use crate::traits::{ObligationCause, ObligationCtxt};
use crate::traits::ObligationCause;

/// Implied bounds are region relationships that we deduce
/// automatically. The idea is that (e.g.) a caller must check that a
@@ -79,24 +79,9 @@ fn implied_outlives_bounds<'a, 'tcx>(

if !constraints.is_empty() {
let QueryRegionConstraints { outlives } = constraints;
// Instantiation may have produced new inference variables and constraints on those
// variables. Process these constraints.
let ocx = ObligationCtxt::new(infcx);
let cause = ObligationCause::misc(span, body_id);
for &constraint in &outlives {
ocx.register_obligation(infcx.query_outlives_constraint_to_obligation(
constraint,
cause.clone(),
param_env,
));
}

let errors = ocx.select_all_or_error();
if !errors.is_empty() {
infcx.dcx().span_bug(
span,
"implied_outlives_bounds failed to solve obligations from instantiation",
);
for &(predicate, _) in &outlives {
infcx.register_outlives_constraint(predicate, &cause);
}
};

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::ops::ControlFlow;

use rustc_infer::infer::RegionObligation;
use rustc_infer::infer::TypeOutlivesConstraint;
use rustc_infer::infer::canonical::CanonicalQueryInput;
use rustc_infer::traits::query::OutlivesBound;
use rustc_infer::traits::query::type_op::ImpliedOutlivesBounds;
@@ -141,7 +141,7 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
&& !ocx.infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat
&& ty.visit_with(&mut ContainsBevyParamSet { tcx: ocx.infcx.tcx }).is_break()
{
for RegionObligation { sup_type, sub_region, .. } in
for TypeOutlivesConstraint { sup_type, sub_region, .. } in
ocx.infcx.take_registered_region_obligations()
{
let mut components = smallvec![];