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
Show file tree
Hide file tree
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
Expand Up @@ -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),
Expand Down
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
Expand Up @@ -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
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_infer/src/infer/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 3 additions & 16 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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
}

Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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()
Expand Down
51 changes: 44 additions & 7 deletions compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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())
Expand Down
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
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/solve/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
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
Expand Up @@ -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);
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_trait_selection/src/traits/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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())
}
Expand Down
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
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
};

Expand Down
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;
Expand Down Expand Up @@ -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![];
Expand Down
Loading