Auto merge of #155258 - ShoyuVanilla:eq-constraint, r=lcnr

Make region equality emits Eq constraints



For context, see..

- The box named *coroutine witness Send lifetime requirements now considered by leakcheck* from [this roadmap](https://raw.githubusercontent.com/hexcatnl/roadmap/6f23e638f65249ef02af86a5454275103a71552d/next-solver.svg)
- [#t-types/trait-system-refactor > A question on #251 @ 💬](https://rust-lang.zulipchat.com/#narrow/channel/364551-t-types.2Ftrait-system-refactor/topic/A.20question.20on.20.23251/near/584166935)
- Comments on `rustc_type_ir::predicate::RegionEqPredicate`
This commit is contained in:
bors
2026-04-18 16:04:22 +00:00
24 changed files with 361 additions and 99 deletions
@@ -451,7 +451,7 @@ fn try_extract_error_from_region_constraints<'a, 'tcx>(
(RePlaceholder(a_p), RePlaceholder(b_p)) => a_p.bound == b_p.bound,
_ => a_region == b_region,
};
let mut check = |c: &Constraint<'tcx>, cause: &SubregionOrigin<'tcx>, exact| match c.kind {
let mut check = |c: Constraint<'tcx>, cause: &SubregionOrigin<'tcx>, exact| match c.kind {
ConstraintKind::RegSubReg
if ((exact && c.sup == placeholder_region)
|| (!exact && regions_the_same(c.sup, placeholder_region)))
@@ -467,13 +467,23 @@ fn try_extract_error_from_region_constraints<'a, 'tcx>(
{
Some((c.sub, cause.clone()))
}
_ => None,
ConstraintKind::VarSubVar
| ConstraintKind::RegSubVar
| ConstraintKind::VarSubReg
| ConstraintKind::RegSubReg => None,
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
unreachable!()
}
};
let mut find_culprit = |exact_match: bool| {
region_constraints
.constraints
.iter()
.flat_map(|(constraint, cause)| {
constraint.iter_outlives().map(move |constraint| (constraint, cause))
})
.find_map(|(constraint, cause)| check(constraint, cause, exact_match))
};
@@ -70,12 +70,14 @@ pub(crate) fn new(
#[instrument(skip(self), level = "debug")]
pub(super) fn convert_all(&mut self, query_constraints: &QueryRegionConstraints<'tcx>) {
let QueryRegionConstraints { outlives, assumptions } = query_constraints;
let QueryRegionConstraints { constraints, assumptions } = query_constraints;
let assumptions =
elaborate::elaborate_outlives_assumptions(self.infcx.tcx, assumptions.iter().copied());
for &(predicate, constraint_category) in outlives {
self.convert(predicate, constraint_category, &assumptions);
for &(constraint, constraint_category) in constraints {
constraint.iter_outlives().for_each(|predicate| {
self.convert(predicate, constraint_category, &assumptions);
});
}
}
@@ -292,8 +294,12 @@ fn normalize_and_add_type_outlives_constraints(
) {
Ok(TypeOpOutput { output: ty, constraints, .. }) => {
// FIXME(higher_ranked_auto): What should we do with the assumptions here?
if let Some(QueryRegionConstraints { outlives, assumptions: _ }) = constraints {
next_outlives_predicates.extend(outlives.iter().copied());
if let Some(QueryRegionConstraints { constraints, assumptions: _ }) = constraints {
next_outlives_predicates.extend(constraints.iter().flat_map(
|(constraint, category)| {
constraint.iter_outlives().map(|outlives| (outlives, *category))
},
));
}
ty
}
@@ -22,7 +22,7 @@
Canonical, CanonicalQueryResponse, CanonicalVarValues, Certainty, OriginalQueryValues,
QueryRegionConstraints, QueryResponse,
};
use crate::infer::region_constraints::RegionConstraintData;
use crate::infer::region_constraints::{ConstraintKind, RegionConstraintData};
use crate::infer::{
DefineOpaqueTypes, InferCtxt, InferOk, InferResult, OpaqueTypeStorageEntries, SubregionOrigin,
TypeOutlivesConstraint,
@@ -188,9 +188,16 @@ pub fn instantiate_query_response_and_region_obligations<R>(
let InferOk { value: result_args, obligations } =
self.query_response_instantiation(cause, param_env, original_values, query_response)?;
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);
for (constraint, _category) in &query_response.value.region_constraints.constraints {
let constraint = instantiate_value(self.tcx, &result_args, *constraint);
match constraint {
ty::RegionConstraint::Outlives(predicate) => {
self.register_outlives_constraint(predicate, cause);
}
ty::RegionConstraint::Eq(predicate) => {
self.register_region_eq_constraint(predicate, cause);
}
}
}
for assumption in &query_response.value.region_constraints.assumptions {
@@ -277,14 +284,11 @@ pub fn instantiate_nll_query_response_and_region_obligations<R>(
}
(GenericArgKind::Lifetime(v_o), GenericArgKind::Lifetime(v_r)) => {
// To make `v_o = v_r`, we emit `v_o: v_r` and `v_r: v_o`.
if v_o != v_r {
output_query_region_constraints
.outlives
.push((ty::OutlivesPredicate(v_o.into(), v_r), constraint_category));
output_query_region_constraints
.outlives
.push((ty::OutlivesPredicate(v_r.into(), v_o), constraint_category));
output_query_region_constraints.constraints.push((
ty::RegionEqPredicate(v_o.into(), v_r).into(),
constraint_category,
));
}
}
@@ -311,13 +315,12 @@ pub fn instantiate_nll_query_response_and_region_obligations<R>(
}
// ...also include the other query region constraints from the query.
output_query_region_constraints.outlives.extend(
query_response.value.region_constraints.outlives.iter().filter_map(|&r_c| {
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);
// Screen out `'a: 'a` cases.
let ty::OutlivesPredicate(k1, r2) = r_c.0;
if k1 != r2.into() { Some(r_c) } else { None }
// Screen out `'a: 'a` or `'a == 'a` cases.
if r_c.0.is_trivial() { None } else { Some(r_c) }
}),
);
@@ -611,20 +614,30 @@ pub fn make_query_region_constraints<'tcx>(
debug!(?constraints);
let outlives: Vec<_> = constraints
let constraints: Vec<_> = constraints
.iter()
.map(|(c, origin)| {
// Swap regions because we are going from sub (<=) to outlives (>=).
let constraint = ty::OutlivesPredicate(c.sup.into(), c.sub);
(constraint, origin.to_constraint_category())
.map(|(c, origin)| match c.kind {
ConstraintKind::VarSubVar
| ConstraintKind::RegSubVar
| ConstraintKind::VarSubReg
| ConstraintKind::RegSubReg => {
// Swap regions because we are going from sub (<=) to outlives (>=).
let constraint = ty::OutlivesPredicate(c.sup.into(), c.sub).into();
(constraint, origin.to_constraint_category())
}
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
let constraint = ty::RegionEqPredicate(c.sup, c.sub).into();
(constraint, origin.to_constraint_category())
}
})
.chain(outlives_obligations.into_iter().map(|obl| {
(
ty::OutlivesPredicate(obl.sup_type.into(), obl.sub_region),
ty::OutlivesPredicate(obl.sup_type.into(), obl.sub_region).into(),
obl.origin.to_constraint_category(),
)
}))
.collect();
QueryRegionConstraints { outlives, assumptions }
QueryRegionConstraints { constraints, assumptions }
}
@@ -13,13 +13,13 @@ pub(super) enum EdgeDirection {
/// Type alias for the pairs stored in [`RegionConstraintData::constraints`],
/// which we are indexing.
type ConstraintPair<'tcx> = (Constraint<'tcx>, SubregionOrigin<'tcx>);
type ConstraintPair<'data, 'tcx> = (Constraint<'tcx>, &'data SubregionOrigin<'tcx>);
/// An index from region variables to their corresponding constraint edges,
/// used on some error paths.
pub(super) struct IndexedConstraintEdges<'data, 'tcx> {
out_edges: IndexVec<RegionVid, Vec<&'data ConstraintPair<'tcx>>>,
in_edges: IndexVec<RegionVid, Vec<&'data ConstraintPair<'tcx>>>,
out_edges: IndexVec<RegionVid, Vec<ConstraintPair<'data, 'tcx>>>,
in_edges: IndexVec<RegionVid, Vec<ConstraintPair<'data, 'tcx>>>,
}
impl<'data, 'tcx> IndexedConstraintEdges<'data, 'tcx> {
@@ -27,25 +27,46 @@ pub(super) fn build_index(num_vars: usize, data: &'data RegionConstraintData<'tc
let mut out_edges = IndexVec::from_fn_n(|_| vec![], num_vars);
let mut in_edges = IndexVec::from_fn_n(|_| vec![], num_vars);
for pair @ (c, _) in &data.constraints {
for pair @ (c, _) in data
.constraints
.iter()
.flat_map(|(c, origin)| c.iter_outlives().map(move |c| (c, origin)))
{
// Only push a var out-edge for `VarSub...` constraints.
match c.kind {
ConstraintKind::VarSubVar | ConstraintKind::VarSubReg => {
out_edges[c.sub.as_var()].push(pair)
out_edges[c.sub.as_var()].push(pair);
}
ConstraintKind::RegSubVar | ConstraintKind::RegSubReg => {}
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
unreachable!();
}
}
}
// FIXME: We should merge this loop with the above one eventually.
// Index in-edges in reverse order, to match what current tests expect.
// (It's unclear whether this is important or not.)
for pair @ (c, _) in data.constraints.iter().rev() {
for pair @ (c, _) in data
.constraints
.iter()
.rev()
.flat_map(|(c, origin)| c.iter_outlives().map(move |c| (c, origin)))
{
// Only push a var in-edge for `...SubVar` constraints.
match c.kind {
ConstraintKind::VarSubVar | ConstraintKind::RegSubVar => {
in_edges[c.sup.as_var()].push(pair)
in_edges[c.sup.as_var()].push(pair);
}
ConstraintKind::VarSubReg | ConstraintKind::RegSubReg => {}
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
unreachable!();
}
}
}
@@ -58,7 +79,7 @@ pub(super) fn adjacent_edges(
&self,
region_vid: RegionVid,
dir: EdgeDirection,
) -> &[&'data ConstraintPair<'tcx>] {
) -> &[ConstraintPair<'data, 'tcx>] {
let edges = match dir {
EdgeDirection::Out => &self.out_edges,
EdgeDirection::In => &self.in_edges,
@@ -34,6 +34,18 @@ pub(crate) fn resolve<'tcx>(
var_infos: VarInfos<'tcx>,
data: RegionConstraintData<'tcx>,
) -> (LexicalRegionResolutions<'tcx>, Vec<RegionResolutionError<'tcx>>) {
assert!(
data.constraints.iter().all(|(c, _)| match c.kind {
ConstraintKind::VarSubVar
| ConstraintKind::RegSubVar
| ConstraintKind::VarSubReg
| ConstraintKind::RegSubReg => true,
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => false,
}),
"Every constraint should be decomposed into outlives here"
);
let mut errors = vec![];
let mut resolver = LexicalResolver { region_rels, var_infos, data };
let values = resolver.infer_variable_values(&mut errors);
@@ -279,6 +291,10 @@ fn expansion(&self, var_values: &mut LexicalRegionResolutions<'tcx>) {
// is done, in `collect_errors`.
continue;
}
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
unreachable!()
}
}
}
@@ -575,6 +591,10 @@ fn collect_errors(
*sub_data = VarValue::ErrorValue;
}
}
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
unreachable!()
}
}
}
@@ -852,12 +872,12 @@ fn process_edges<'tcx>(
}
ConstraintKind::RegSubVar => {
let origin = origin.clone();
let origin = (*origin).clone();
state.result.push(RegionAndOrigin { region: c.sub, origin });
}
ConstraintKind::VarSubReg => {
let origin = origin.clone();
let origin = (*origin).clone();
state.result.push(RegionAndOrigin { region: c.sup, origin });
}
@@ -865,6 +885,10 @@ fn process_edges<'tcx>(
"cannot reach reg-sub-reg edge in region inference \
post-processing"
),
ConstraintKind::VarEqVar
| ConstraintKind::VarEqReg
| ConstraintKind::RegEqReg => unreachable!(),
}
}
}
+10
View File
@@ -702,6 +702,16 @@ pub fn sub_regions(
self.inner.borrow_mut().unwrap_region_constraints().make_subregion(origin, a, b);
}
#[instrument(skip(self), level = "debug")]
pub fn equate_regions(
&self,
origin: SubregionOrigin<'tcx>,
a: ty::Region<'tcx>,
b: ty::Region<'tcx>,
) {
self.inner.borrow_mut().unwrap_region_constraints().make_eqregion(origin, a, b);
}
/// Processes a `Coerce` predicate from the fulfillment context.
/// This is NOT the preferred way to handle coercion, which is to
/// invoke `FnCtxt::coerce` or a similar method (see `coercion.rs`).
+19 -1
View File
@@ -1,5 +1,7 @@
//! Various code related to computing outlives relations.
use std::iter;
use rustc_data_structures::undo_log::UndoLogs;
use rustc_middle::traits::query::{NoSolution, OutlivesBound};
use rustc_middle::ty;
@@ -67,6 +69,15 @@ pub fn resolve_regions_with_normalize(
inner.region_constraint_storage.take().expect("regions already resolved")
};
storage.data.constraints = storage
.data
.constraints
.iter()
.flat_map(|(constraint, origin)| {
constraint.iter_outlives().zip(iter::repeat_with(|| origin.clone()))
})
.collect();
// Filter out any region-region outlives assumptions that are implied by
// coroutine well-formedness.
if self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions {
@@ -74,7 +85,14 @@ pub fn resolve_regions_with_normalize(
ConstraintKind::RegSubReg => !outlives_env
.higher_ranked_assumptions()
.contains(&ty::OutlivesPredicate(c.sup.into(), c.sub)),
_ => true,
ConstraintKind::VarSubVar
| ConstraintKind::RegSubVar
| ConstraintKind::VarSubReg => true,
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
unreachable!();
}
});
}
@@ -98,6 +98,17 @@ pub fn register_outlives_constraint(
}
}
pub fn register_region_eq_constraint(
&self,
ty::RegionEqPredicate(r_a, r_b): ty::RegionEqPredicate<'tcx>,
cause: &ObligationCause<'tcx>,
) {
let origin = SubregionOrigin::from_obligation_cause(cause, || {
SubregionOrigin::RelateRegionParamBound(cause.span, None)
});
self.equate_regions(origin, r_a, r_b);
}
pub fn register_region_outlives_constraint(
&self,
ty::OutlivesPredicate(r_a, r_b): ty::RegionOutlivesPredicate<'tcx>,
@@ -392,8 +392,10 @@ fn iterate_region_constraints(
{
match undo_entry {
&AddConstraint(i) => {
let c = region_constraints.data().constraints[i].0;
each_edge(c.sub, c.sup);
region_constraints.data().constraints[i]
.0
.iter_outlives()
.for_each(|c| each_edge(c.sub, c.sup));
}
&AddVerify(i) => span_bug!(
region_constraints.data().verifys[i].origin.span(),
@@ -403,7 +405,12 @@ fn iterate_region_constraints(
}
}
} else {
region_constraints.data().constraints.iter().for_each(|(c, _)| each_edge(c.sub, c.sup))
region_constraints
.data()
.constraints
.iter()
.flat_map(|(c, _)| c.iter_outlives())
.for_each(|c| each_edge(c.sub, c.sup))
}
}
@@ -1,7 +1,7 @@
//! See `README.md`.
use std::ops::Range;
use std::{cmp, fmt, mem};
use std::{cmp, fmt, iter, mem};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::undo_log::UndoLogs;
@@ -96,6 +96,19 @@ pub enum ConstraintKind {
/// directly affect inference, but instead is checked after
/// inference is complete.
RegSubReg,
/// A region variable is equal to another.
VarEqVar,
/// A region variable is equal to a concrete region. This does not
/// directly affect inference, but instead is checked after
/// inference is complete.
VarEqReg,
/// An equality constraint where neither side is a variable. This does not
/// directly affect inference, but instead is checked after
/// inference is complete.
RegEqReg,
}
/// Represents a constraint that influences the inference process.
@@ -112,6 +125,30 @@ impl Constraint<'_> {
pub fn involves_placeholders(&self) -> bool {
self.sub.is_placeholder() || self.sup.is_placeholder()
}
pub fn iter_outlives(self) -> impl Iterator<Item = Self> {
let Constraint { kind, sub, sup } = self;
match kind {
ConstraintKind::VarSubVar
| ConstraintKind::RegSubVar
| ConstraintKind::VarSubReg
| ConstraintKind::RegSubReg => iter::once(self).chain(None),
ConstraintKind::VarEqVar => {
iter::once(Constraint { kind: ConstraintKind::VarSubVar, sub, sup })
.chain(Some(Constraint { kind: ConstraintKind::VarSubVar, sub: sup, sup: sub }))
}
ConstraintKind::VarEqReg => {
iter::once(Constraint { kind: ConstraintKind::VarSubReg, sub, sup })
.chain(Some(Constraint { kind: ConstraintKind::RegSubVar, sub: sup, sup: sub }))
}
ConstraintKind::RegEqReg => {
iter::once(Constraint { kind: ConstraintKind::RegSubReg, sub, sup })
.chain(Some(Constraint { kind: ConstraintKind::RegSubReg, sub: sup, sup: sub }))
}
}
}
}
#[derive(Debug, Clone)]
@@ -422,39 +459,57 @@ pub(super) fn make_eqregion(
b: Region<'tcx>,
) {
if a != b {
// Eventually, it would be nice to add direct support for
// equating regions.
self.make_subregion(origin.clone(), a, b);
self.make_subregion(origin, b, a);
match (a.kind(), b.kind()) {
(ty::ReVar(a), ty::ReVar(b)) => {
debug!("make_eqregion: unifying {:?} with {:?}", a, b);
if self.unification_table_mut().unify_var_var(a, b).is_ok() {
// FIXME: We could only emit constraints if `unify_var_{var, value}` fails when
// equating region vars.
match (a.kind(), b.kind(), a, b) {
(ReBound(..), _, _, _) | (_, ReBound(..), _, _) => {
span_bug!(origin.span(), "cannot relate bound region: {:?} == {:?}", a, b);
}
(ReVar(a_vid), ReVar(b_vid), _, _) => {
self.add_constraint(
Constraint { kind: ConstraintKind::VarEqVar, sub: a, sup: b },
origin,
);
debug!("make_eqregion: unifying {:?} with {:?}", a_vid, b_vid);
if self.unification_table_mut().unify_var_var(a_vid, b_vid).is_ok() {
self.storage.any_unifications = true;
}
}
(ty::ReVar(vid), _) => {
debug!("make_eqregion: unifying {:?} with {:?}", vid, b);
(ReVar(vid), _, var, reg) | (_, ReVar(vid), reg, var) => {
if reg.is_static() {
// all regions are subregions of static, so don't go bidirectional here
self.add_constraint(
Constraint { kind: ConstraintKind::RegSubVar, sub: reg, sup: var },
origin,
);
} else {
self.add_constraint(
Constraint { kind: ConstraintKind::VarEqReg, sub: var, sup: reg },
origin,
);
}
debug!("make_eqregion: unifying {:?} with {:?}", vid, reg);
if self
.unification_table_mut()
.unify_var_value(vid, RegionVariableValue::Known { value: b })
.unify_var_value(vid, RegionVariableValue::Known { value: reg })
.is_ok()
{
self.storage.any_unifications = true;
};
}
(_, ty::ReVar(vid)) => {
debug!("make_eqregion: unifying {:?} with {:?}", a, vid);
if self
.unification_table_mut()
.unify_var_value(vid, RegionVariableValue::Known { value: a })
.is_ok()
{
self.storage.any_unifications = true;
};
(ReStatic, _, st, reg) | (_, ReStatic, reg, st) => {
// all regions are subregions of static, so don't go bidirectional here
self.add_constraint(
Constraint { kind: ConstraintKind::RegSubReg, sub: st, sup: reg },
origin,
);
}
_ => {
self.add_constraint(
Constraint { kind: ConstraintKind::RegEqReg, sub: a, sup: b },
origin,
);
}
(_, _) => {}
}
}
}
+4 -3
View File
@@ -79,7 +79,7 @@ pub struct QueryResponse<'tcx, R> {
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[derive(HashStable, TypeFoldable, TypeVisitable)]
pub struct QueryRegionConstraints<'tcx> {
pub outlives: Vec<QueryOutlivesConstraint<'tcx>>,
pub constraints: Vec<QueryRegionConstraint<'tcx>>,
pub assumptions: Vec<ty::ArgOutlivesPredicate<'tcx>>,
}
@@ -91,7 +91,8 @@ 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 {
self.outlives.is_empty() && self.assumptions.is_empty()
let QueryRegionConstraints { constraints, assumptions } = self;
constraints.is_empty() && assumptions.is_empty()
}
}
@@ -134,7 +135,7 @@ pub fn is_proven(&self) -> bool {
}
}
pub type QueryOutlivesConstraint<'tcx> = (ty::ArgOutlivesPredicate<'tcx>, ConstraintCategory<'tcx>);
pub type QueryRegionConstraint<'tcx> = (ty::RegionConstraint<'tcx>, ConstraintCategory<'tcx>);
#[derive(Default)]
pub struct CanonicalParamEnvCache<'tcx> {
+2 -1
View File
@@ -97,7 +97,8 @@
PolyExistentialPredicate, PolyExistentialProjection, PolyExistentialTraitRef,
PolyProjectionPredicate, PolyRegionOutlivesPredicate, PolySubtypePredicate, PolyTraitPredicate,
PolyTraitRef, PolyTypeOutlivesPredicate, Predicate, PredicateKind, ProjectionPredicate,
RegionOutlivesPredicate, SubtypePredicate, TraitPredicate, TraitRef, TypeOutlivesPredicate,
RegionConstraint, RegionEqPredicate, RegionOutlivesPredicate, SubtypePredicate, TraitPredicate,
TraitRef, TypeOutlivesPredicate,
};
pub use self::region::{
EarlyParamRegion, LateParamRegion, LateParamRegionKind, Region, RegionKind, RegionVid,
@@ -26,6 +26,8 @@
pub type RegionOutlivesPredicate<'tcx> = OutlivesPredicate<'tcx, ty::Region<'tcx>>;
pub type TypeOutlivesPredicate<'tcx> = OutlivesPredicate<'tcx, Ty<'tcx>>;
pub type ArgOutlivesPredicate<'tcx> = OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>;
pub type RegionEqPredicate<'tcx> = ir::RegionEqPredicate<TyCtxt<'tcx>>;
pub type RegionConstraint<'tcx> = ir::RegionConstraint<TyCtxt<'tcx>>;
pub type PolyTraitPredicate<'tcx> = ty::Binder<'tcx, TraitPredicate<'tcx>>;
pub type PolyRegionOutlivesPredicate<'tcx> = ty::Binder<'tcx, RegionOutlivesPredicate<'tcx>>;
pub type PolyTypeOutlivesPredicate<'tcx> = ty::Binder<'tcx, TypeOutlivesPredicate<'tcx>>;
@@ -250,17 +250,22 @@ fn unify_query_var_values<D, I>(
fn register_region_constraints<D, I>(
delegate: &D,
outlives: &[ty::OutlivesPredicate<I, I::GenericArg>],
constraints: &[ty::RegionConstraint<I>],
span: I::Span,
) where
D: SolverDelegate<Interner = I>,
I: Interner,
{
for &ty::OutlivesPredicate(lhs, rhs) in outlives {
match lhs.kind() {
ty::GenericArgKind::Lifetime(lhs) => delegate.sub_regions(rhs, lhs, span),
ty::GenericArgKind::Type(lhs) => delegate.register_ty_outlives(lhs, rhs, span),
ty::GenericArgKind::Const(_) => panic!("const outlives: {lhs:?}: {rhs:?}"),
for &constraint in constraints {
match constraint {
ty::RegionConstraint::Outlives(ty::OutlivesPredicate(lhs, rhs)) => match lhs.kind() {
ty::GenericArgKind::Lifetime(lhs) => delegate.sub_regions(rhs, lhs, span),
ty::GenericArgKind::Type(lhs) => delegate.register_ty_outlives(lhs, rhs, span),
ty::GenericArgKind::Const(_) => panic!("const outlives: {lhs:?}: {rhs:?}"),
},
ty::RegionConstraint::Eq(ty::RegionEqPredicate(lhs, rhs)) => {
delegate.equate_regions(lhs, rhs, span)
}
}
}
}
@@ -45,9 +45,7 @@ fn well_formed_goals(
term: <Self::Interner as Interner>::Term,
) -> Option<Vec<Goal<Self::Interner, <Self::Interner as Interner>::Predicate>>>;
fn make_deduplicated_outlives_constraints(
&self,
) -> Vec<ty::OutlivesPredicate<Self::Interner, <Self::Interner as Interner>::GenericArg>>;
fn make_deduplicated_region_constraints(&self) -> Vec<ty::RegionConstraint<Self::Interner>>;
fn instantiate_canonical<V>(
&self,
@@ -1294,9 +1294,9 @@ pub(in crate::solve) fn evaluate_added_goals_and_make_canonical_response(
// Remove any trivial or duplicated region constraints once we've resolved regions
let mut unique = HashSet::default();
external_constraints.region_constraints.retain(|outlives| {
outlives.0.as_region().is_none_or(|re| re != outlives.1) && unique.insert(*outlives)
});
external_constraints
.region_constraints
.retain(|outlives| !outlives.is_trivial() && unique.insert(*outlives));
let canonical = canonicalize_response(
self.delegate,
@@ -1350,7 +1350,7 @@ fn compute_external_query_constraints(
// `tests/ui/higher-ranked/leak-check/leak-check-in-selection-5-ambig.rs` and
// `tests/ui/higher-ranked/leak-check/leak-check-in-selection-6-ambig-unify.rs`.
let region_constraints = if certainty == Certainty::Yes {
self.delegate.make_deduplicated_outlives_constraints()
self.delegate.make_deduplicated_region_constraints()
} else {
Default::default()
};
@@ -204,7 +204,7 @@ fn well_formed_goals(
.map(|obligations| obligations.into_iter().map(|obligation| obligation.as_goal()).collect())
}
fn make_deduplicated_outlives_constraints(&self) -> Vec<ty::ArgOutlivesPredicate<'tcx>> {
fn make_deduplicated_region_constraints(&self) -> Vec<ty::RegionConstraint<'tcx>> {
// Cannot use `take_registered_region_obligations` as we may compute the response
// inside of a `probe` whenever we have multiple choices inside of the solver.
let region_obligations = self.0.inner.borrow().region_obligations().to_owned();
@@ -219,7 +219,7 @@ fn make_deduplicated_outlives_constraints(&self) -> Vec<ty::ArgOutlivesPredicate
let mut seen = FxHashSet::default();
region_constraints
.outlives
.constraints
.into_iter()
.filter(|&(outlives, _)| seen.insert(outlives))
.map(|(outlives, _)| outlives)
@@ -453,7 +453,7 @@ fn map_vid_to_region<'cx>(
let mut vid_map = FxIndexMap::<RegionTarget<'cx>, RegionDeps<'cx>>::default();
let mut finished_map = FxIndexMap::default();
for (c, _) in &regions.constraints {
for c in regions.constraints.iter().flat_map(|(c, _)| c.iter_outlives()) {
match c.kind {
ConstraintKind::VarSubVar => {
let sub_vid = c.sub.as_var();
@@ -489,6 +489,10 @@ fn map_vid_to_region<'cx>(
let deps2 = vid_map.entry(RegionTarget::Region(c.sup)).or_default();
deps2.smaller.insert(RegionTarget::Region(c.sub));
}
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
unreachable!()
}
}
}
@@ -81,10 +81,17 @@ 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 { outlives, assumptions: _ } = constraints;
let QueryRegionConstraints { constraints, assumptions: _ } = constraints;
let cause = ObligationCause::misc(span, body_id);
for &(predicate, _) in &outlives {
infcx.register_outlives_constraint(predicate, &cause);
for &(constraint, _) in &constraints {
match constraint {
ty::RegionConstraint::Outlives(predicate) => {
infcx.register_outlives_constraint(predicate, &cause)
}
ty::RegionConstraint::Eq(predicate) => {
infcx.register_region_eq_constraint(predicate, &cause)
}
}
}
};
@@ -180,8 +180,8 @@ fn fully_perform(
Ok(output)
})?;
output.error_info = error_info;
if let Some(QueryRegionConstraints { outlives, assumptions }) = output.constraints {
region_constraints.outlives.extend(outlives.iter().cloned());
if let Some(QueryRegionConstraints { constraints, assumptions }) = output.constraints {
region_constraints.constraints.extend(constraints.iter().cloned());
region_constraints.assumptions.extend(assumptions.iter().cloned());
}
output.constraints = if region_constraints.is_empty() {
@@ -69,18 +69,18 @@ fn compute_assumptions<'tcx>(
let region_assumptions = infcx.take_registered_region_assumptions();
let region_constraints = infcx.take_and_reset_region_constraints();
let outlives = make_query_region_constraints(
let constraints = make_query_region_constraints(
region_obligations,
&region_constraints,
region_assumptions,
)
.outlives
.constraints
.fold_with(&mut OpportunisticRegionResolver::new(&infcx));
tcx.mk_outlives_from_iter(
outlives
constraints
.into_iter()
.map(|(o, _)| o)
.flat_map(|(constraint, _)| constraint.iter_outlives())
// FIXME(higher_ranked_auto): We probably should deeply resolve these before
// filtering out infers which only correspond to unconstrained infer regions
// which we can sometimes get.
+67 -1
View File
@@ -1,5 +1,5 @@
use std::fmt;
use std::hash::Hash;
use std::{fmt, iter};
use derive_where::derive_where;
#[cfg(feature = "nightly")]
@@ -42,6 +42,72 @@ fn lift_to_interner(self, cx: U) -> Option<Self::Lifted> {
}
}
/// `'a == 'b`.
/// For the rationale behind having this instead of a pair of bidirectional
/// `'a: 'b` and `'b: 'a`, see
/// [this discusstion on Zulip](https://rust-lang.zulipchat.com/#narrow/channel/364551-t-types.2Ftrait-system-refactor/topic/A.20question.20on.20.23251/near/584167074).
#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)]
#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)]
#[cfg_attr(
feature = "nightly",
derive(Decodable_NoContext, Encodable_NoContext, HashStable_NoContext)
)]
pub struct RegionEqPredicate<I: Interner>(pub I::Region, pub I::Region);
impl<I: Interner> RegionEqPredicate<I> {
/// Decompose `'a == 'b` into `['a: 'b, 'b: 'a]`
pub fn into_bidirectional_outlives(self) -> [OutlivesPredicate<I, I::GenericArg>; 2] {
[OutlivesPredicate(self.0.into(), self.1), OutlivesPredicate(self.1.into(), self.0)]
}
}
#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)]
#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)]
#[cfg_attr(
feature = "nightly",
derive(Decodable_NoContext, Encodable_NoContext, HashStable_NoContext)
)]
pub enum RegionConstraint<I: Interner> {
Outlives(OutlivesPredicate<I, I::GenericArg>),
Eq(RegionEqPredicate<I>),
}
impl<I: Interner> From<OutlivesPredicate<I, I::GenericArg>> for RegionConstraint<I> {
fn from(value: OutlivesPredicate<I, I::GenericArg>) -> Self {
RegionConstraint::Outlives(value)
}
}
impl<I: Interner> From<RegionEqPredicate<I>> for RegionConstraint<I> {
fn from(value: RegionEqPredicate<I>) -> Self {
RegionConstraint::Eq(value)
}
}
impl<I: Interner> RegionConstraint<I> {
/// Whether the given constraint is either `'a: 'a` or `'a == 'a`.
pub fn is_trivial(self) -> bool {
match self {
RegionConstraint::Outlives(outlives) => {
outlives.0.as_region().is_some_and(|re| re == outlives.1)
}
RegionConstraint::Eq(eq) => eq.0 == eq.1,
}
}
/// If `self` is an eq constraint, iterate through its decomposed bidirectional outlives
/// bounds and if not, just iterate once for the outlives bound itself.
pub fn iter_outlives(self) -> impl Iterator<Item = OutlivesPredicate<I, I::GenericArg>> {
match self {
RegionConstraint::Outlives(outlives) => iter::once(outlives).chain(None),
RegionConstraint::Eq(eq) => {
let [outlives1, outlives2] = eq.into_bidirectional_outlives();
iter::once(outlives1).chain(Some(outlives2))
}
}
}
}
/// A complete reference to a trait.
///
/// These take numerous guises in syntax,
+1 -1
View File
@@ -253,7 +253,7 @@ impl<I: Interner> Eq for Response<I> {}
#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)]
#[cfg_attr(feature = "nightly", derive(HashStable_NoContext))]
pub struct ExternalConstraintsData<I: Interner> {
pub region_constraints: Vec<ty::OutlivesPredicate<I, I::GenericArg>>,
pub region_constraints: Vec<ty::RegionConstraint<I>>,
pub opaque_types: Vec<(ty::OpaqueTypeKey<I>, I::Ty)>,
pub normalization_nested_goals: NestedNormalizationGoals<I>,
}
+4 -1
View File
@@ -234,7 +234,7 @@ fn clean_region_outlives_constraints<'tcx>(
// Each `RegionTarget` (a `RegionVid` or a `Region`) maps to its smaller and larger regions.
// Note that "larger" regions correspond to sub regions in the surface language.
// E.g., in `'a: 'b`, `'a` is the larger region.
for (c, _) in &regions.constraints {
for c in regions.constraints.iter().flat_map(|(c, _)| c.iter_outlives()) {
match c.kind {
ConstraintKind::VarSubVar => {
let sub_vid = c.sub.as_var();
@@ -265,6 +265,9 @@ fn clean_region_outlives_constraints<'tcx>(
.push(c.sub);
}
}
ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => {
unreachable!()
}
}
}