Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -77,6 +78,9 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
self.convert(predicate, category, &assumptions);
});
}

self.infcx
.register_solver_region_constraint(solver_constraints.clone().with_span(self.span));
}

/// Given an instance of the closure type, this method instantiates the "extra" requirements
Expand Down
21 changes: 20 additions & 1 deletion compiler/rustc_infer/src/infer/canonical/query_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.clone_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);
Expand Down Expand Up @@ -214,6 +216,13 @@ 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(),
);
self.register_solver_region_constraint(solver_constraints.with_span(cause.span));

let user_result: R =
query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone());

Expand Down Expand Up @@ -347,6 +356,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());

Expand Down Expand Up @@ -619,6 +637,7 @@ pub fn make_query_region_constraints<'tcx>(
outlives_obligations: Vec<TypeOutlivesConstraint<'tcx>>,
region_constraints: &RegionConstraintData<'tcx>,
assumptions: Vec<ty::ArgOutlivesClause<'tcx>>,
solver_constraints: ty::region_constraint::RegionConstraint<TyCtxt<'tcx>>,
) -> QueryRegionConstraints<'tcx> {
let RegionConstraintData { constraints, verifys } = region_constraints;

Expand Down Expand Up @@ -663,5 +682,5 @@ pub fn make_query_region_constraints<'tcx>(
))
.collect();

QueryRegionConstraints { constraints, assumptions }
QueryRegionConstraints { constraints, assumptions, solver_constraints }
}
5 changes: 3 additions & 2 deletions compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,9 @@ impl<'tcx> InferCtxt<'tcx> {
pub fn register_solver_region_constraint(&self, c: SolverRegionConstraint<'tcx>) {
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(c);
if inner.solver_region_constraint_storage.push(c) {
inner.undo_log.push(UndoLog::PushSolverRegionConstraint { previous_was_and });
}
}

pub fn register_type_outlives_constraint(
Expand Down
42 changes: 40 additions & 2 deletions compiler/rustc_infer/src/infer/solver_region_constraints.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use rustc_middle::ty::TyCtxt;
use rustc_type_ir::region_constraint::SpannedRegionConstraint;
use rustc_type_ir::region_constraint::{
RegionConstraint as UnspannedRegionConstraint, SpannedRegionConstraint,
};
use tracing::instrument;

use super::InferCtxt;

pub type SolverRegionConstraint<'tcx> = SpannedRegionConstraint<TyCtxt<'tcx>>;

#[derive(Clone, Debug)]
Expand All @@ -16,6 +20,10 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> {
self.0.clone()
}

fn take(&mut self) -> SolverRegionConstraint<'tcx> {
core::mem::take(&mut self.0)
}

pub(crate) fn is_and(&self) -> bool {
self.0.is_and()
}
Expand All @@ -38,7 +46,11 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> {
}

#[instrument(level = "debug")]
pub(crate) fn push(&mut self, constraint: SolverRegionConstraint<'tcx>) {
pub(crate) fn push(&mut self, constraint: SolverRegionConstraint<'tcx>) -> bool {
if constraint.is_true() {
return false;
}

match core::mem::replace(&mut self.0, SolverRegionConstraint::new_true()) {
SolverRegionConstraint::And(and) => {
let and =
Expand All @@ -49,6 +61,8 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> {
self.0 = SolverRegionConstraint::And(Box::new([previous, constraint]));
}
}

true
}

#[instrument(level = "debug", skip(self))]
Expand All @@ -57,5 +71,29 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> {
}
}

impl<'tcx> InferCtxt<'tcx> {
pub(crate) fn clone_solver_region_constraints(
&self,
) -> UnspannedRegionConstraint<TyCtxt<'tcx>> {
self.get_solver_region_constraint().without_spans()
}

/// 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<R>(

@BoxyUwU BoxyUwU Aug 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this necessary for new style constraints but not old style?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is that the old obligations and assumptions are taken before the op and checked to be empty, so everything collected afterward belongs to that op. We cannot make that same check for the solver tree. Borrowck may already have constraints from an earlier type op in the same InferCtxt, and those stay there until the end of typeck. If we took the full tree afterward, this response would also contain the caller's older constraints. Clearing it first would lose them. The helper parks the old tree, captures what this op made, then restores the old one.

I do not love the extra swap, but I think this part is a real difference in how the constraints are stored and when they are consumed.

&self,
op: impl FnOnce() -> R,
) -> (R, UnspannedRegionConstraint<TyCtxt<'tcx>>) {
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(previous);

(result, current.without_spans())
}
}

#[cfg(test)]
mod tests;
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use rustc_middle::infer::canonical::QueryRegionConstraints;
use rustc_span::{BytePos, DUMMY_SP, Span};
use rustc_type_ir::region_constraint::evaluate_solver_constraint;

use super::SolverRegionConstraint;
use super::{SolverRegionConstraint, SolverRegionConstraintStorage};

fn and(constraints: Vec<SolverRegionConstraint<'static>>) -> SolverRegionConstraint<'static> {
SolverRegionConstraint::And(constraints.into_boxed_slice())
Expand All @@ -15,6 +16,18 @@ fn ambiguity() -> SolverRegionConstraint<'static> {
SolverRegionConstraint::Ambiguity(DUMMY_SP)
}

#[test]
fn true_constraint_keeps_query_response_empty() {
let mut storage: SolverRegionConstraintStorage<'static> = SolverRegionConstraintStorage::new();
storage.push(and(vec![]));

let constraints = QueryRegionConstraints {
solver_constraints: storage.get_constraint().without_spans(),
..Default::default()
};
assert!(constraints.is_empty());
}

#[test]
fn evaluation_is_span_agnostic() {
let constraints = [
Expand Down
27 changes: 24 additions & 3 deletions compiler/rustc_middle/src/infer/canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<QueryRegionConstraint<'tcx>>,
pub assumptions: Vec<ty::ArgOutlivesClause<'tcx>>,
/// 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<TyCtxt<'tcx>>,
}

impl Eq for QueryRegionConstraints<'_> {}

impl QueryRegionConstraints<'_> {
/// Represents an empty (trivially true) set of region constraints.
///
Expand All @@ -91,8 +99,21 @@ 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;

@BoxyUwU BoxyUwU Aug 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the destructuring here is intentional. it means that adding new fields doesn't silently keep compiling and doing the wrong thing. please keep the let QueryRegionConstraints :3

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, put the full destructuring back in is_empty and extend, and removed the .. from the implied-bound path too. This PR adding solver_constraints is exactly the kind of change that match was meant to catch, so keeping it exhaustive makes sense.

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 =
std::mem::take(solver_constraints).and(other_solver_constraints.clone());
}
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_trait_selection/src/solve/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<
region_obligations,
region_constraints,
region_assumptions,
Default::default(),
)
});

Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_trait_selection/src/traits/outlives_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_span(span));

let cause = ObligationCause::misc(span, body_def_id);
for &QueryRegionConstraint { constraint, visible_for_leak_check: vis, .. } in &constraints {
match constraint {
Expand Down
44 changes: 25 additions & 19 deletions compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ impl<F> fmt::Debug for CustomTypeOp<F> {
}
}

/// 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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -149,6 +154,7 @@ where
region_obligations,
&region_constraint_data,
region_assumptions,
solver_constraints,
);

if region_constraints.is_empty() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_traits/src/coroutine_witnesses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ fn compute_assumptions<'tcx>(
region_obligations,
&region_constraints,
region_assumptions,
Default::default(),
)
.constraints
.fold_with(&mut OpportunisticRegionResolver::new(&infcx));
Expand Down
28 changes: 27 additions & 1 deletion tests/ui/assumptions_on_binders/alias_outlives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
<T as AliasHaver>::Assoc: 'a,
{
let _: ReqTrait<T::Assoc>;
//~^ ERROR: higher-ranked lifetime bound could not be satisfied
}

const REGIONCK_ENV_PASS<'a, T: AliasHaver>: ReqTrait<T::Assoc> = todo!()
Expand All @@ -39,4 +39,30 @@ const REGIONCK_ENV_FAIL<'a, T: AliasHaver>: ReqTrait<T::Assoc> = todo!()
where
<T as AliasHaver>::Assoc: 'a;

// Solver constraints produced while normalizing implied bounds must be returned
// to lexical regionck.
trait Project {
type Assoc;
}

impl<T: AliasHaver> Project for (T,)
where
T::Assoc: for<'a> Trait<'a>,
{
type Assoc = ();
}

struct Normalizes<T: Project>(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() {}
Loading
Loading