diff --git a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs index a927c30fae325..13c705137e0ee 100644 --- a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs @@ -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)) }; diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index 703223e2e54a3..868c6f11b68dc 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -70,12 +70,14 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { #[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 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { ) { 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 } diff --git a/compiler/rustc_infer/src/infer/canonical/query_response.rs b/compiler/rustc_infer/src/infer/canonical/query_response.rs index 846123b8aad93..5203b4ee5d99f 100644 --- a/compiler/rustc_infer/src/infer/canonical/query_response.rs +++ b/compiler/rustc_infer/src/infer/canonical/query_response.rs @@ -22,7 +22,7 @@ use crate::infer::canonical::{ 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 @@ impl<'tcx> InferCtxt<'tcx> { 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 @@ impl<'tcx> InferCtxt<'tcx> { } (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 @@ impl<'tcx> InferCtxt<'tcx> { } // ...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 } } diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/indexed_edges.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/indexed_edges.rs index ffc6e54f3cb06..382998e55d2e2 100644 --- a/compiler/rustc_infer/src/infer/lexical_region_resolve/indexed_edges.rs +++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/indexed_edges.rs @@ -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>>, - in_edges: IndexVec>>, + out_edges: IndexVec>>, + in_edges: IndexVec>>, } impl<'data, 'tcx> IndexedConstraintEdges<'data, 'tcx> { @@ -27,25 +27,46 @@ impl<'data, 'tcx> IndexedConstraintEdges<'data, 'tcx> { 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 @@ impl<'data, 'tcx> IndexedConstraintEdges<'data, 'tcx> { &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, diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs index e99dcd1ef15cb..9e5c31eecbe77 100644 --- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs +++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs @@ -34,6 +34,18 @@ pub(crate) fn resolve<'tcx>( var_infos: VarInfos<'tcx>, data: RegionConstraintData<'tcx>, ) -> (LexicalRegionResolutions<'tcx>, Vec>) { + 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 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { // is done, in `collect_errors`. continue; } + + ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => { + unreachable!() + } } } @@ -575,6 +591,10 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { *sub_data = VarValue::ErrorValue; } } + + ConstraintKind::VarEqVar | ConstraintKind::VarEqReg | ConstraintKind::RegEqReg => { + unreachable!() + } } } @@ -852,12 +872,12 @@ impl<'cx, 'tcx> LexicalResolver<'cx, '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 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { "cannot reach reg-sub-reg edge in region inference \ post-processing" ), + + ConstraintKind::VarEqVar + | ConstraintKind::VarEqReg + | ConstraintKind::RegEqReg => unreachable!(), } } } diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 583eb1a6dbc4a..2fb4a173a1956 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -687,6 +687,16 @@ impl<'tcx> InferCtxt<'tcx> { 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`). diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index c992cda8aaed0..2538df46575e4 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -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 @@ impl<'tcx> InferCtxt<'tcx> { 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 @@ impl<'tcx> InferCtxt<'tcx> { 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!(); + } }); } diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index b7cea848098a7..28e1b07182457 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -98,6 +98,17 @@ impl<'tcx> InferCtxt<'tcx> { } } + 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>, diff --git a/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs b/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs index 4ef1ea5a1c4d9..81a3ca6755d20 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs @@ -392,8 +392,10 @@ impl<'tcx> MiniGraph<'tcx> { { 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 @@ impl<'tcx> MiniGraph<'tcx> { } } } 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)) } } diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs index ae7481b5d1e76..38b87eb7a9863 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs @@ -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 { + 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 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { 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, + ); } - (_, _) => {} } } } diff --git a/compiler/rustc_middle/src/infer/canonical.rs b/compiler/rustc_middle/src/infer/canonical.rs index 32c6b6e9c0ba1..033e3e36b0247 100644 --- a/compiler/rustc_middle/src/infer/canonical.rs +++ b/compiler/rustc_middle/src/infer/canonical.rs @@ -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>, + pub constraints: Vec>, pub assumptions: Vec>, } @@ -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 @@ impl<'tcx, R> QueryResponse<'tcx, R> { } } -pub type QueryOutlivesConstraint<'tcx> = (ty::ArgOutlivesPredicate<'tcx>, ConstraintCategory<'tcx>); +pub type QueryRegionConstraint<'tcx> = (ty::RegionConstraint<'tcx>, ConstraintCategory<'tcx>); #[derive(Default)] pub struct CanonicalParamEnvCache<'tcx> { diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index dbf7d643a42ce..0f513912b977c 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -96,7 +96,8 @@ pub use self::predicate::{ 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, diff --git a/compiler/rustc_middle/src/ty/predicate.rs b/compiler/rustc_middle/src/ty/predicate.rs index 3baeb7141de50..4d9f08a3e58ac 100644 --- a/compiler/rustc_middle/src/ty/predicate.rs +++ b/compiler/rustc_middle/src/ty/predicate.rs @@ -26,6 +26,8 @@ pub type OutlivesPredicate<'tcx, T> = ir::OutlivesPredicate, T>; 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>; +pub type RegionConstraint<'tcx> = ir::RegionConstraint>; 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>>; diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 1f64f09fe787f..b80f5a7d9f3fa 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -247,17 +247,22 @@ fn unify_query_var_values( fn register_region_constraints( delegate: &D, - outlives: &[ty::OutlivesPredicate], + constraints: &[ty::RegionConstraint], span: I::Span, ) where D: SolverDelegate, 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) + } } } } diff --git a/compiler/rustc_next_trait_solver/src/delegate.rs b/compiler/rustc_next_trait_solver/src/delegate.rs index 9d5aa8bc124b6..7cf4e8a9238a5 100644 --- a/compiler/rustc_next_trait_solver/src/delegate.rs +++ b/compiler/rustc_next_trait_solver/src/delegate.rs @@ -45,9 +45,7 @@ pub trait SolverDelegate: Deref + Sized { term: ::Term, ) -> Option::Predicate>>>; - fn make_deduplicated_outlives_constraints( - &self, - ) -> Vec::GenericArg>>; + fn make_deduplicated_region_constraints(&self) -> Vec>; fn instantiate_canonical( &self, diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 6841fe1c5124e..8933ac16b2b10 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -1294,9 +1294,9 @@ where // 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 @@ where // `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() }; diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 62572694de326..05ecc4725a7b6 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -204,7 +204,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< .map(|obligations| obligations.into_iter().map(|obligation| obligation.as_goal()).collect()) } - fn make_deduplicated_outlives_constraints(&self) -> Vec> { + fn make_deduplicated_region_constraints(&self) -> Vec> { // 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 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< let mut seen = FxHashSet::default(); region_constraints - .outlives + .constraints .into_iter() .filter(|&(outlives, _)| seen.insert(outlives)) .map(|(outlives, _)| outlives) diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs index a21ff44f40f38..f28e6f32b5f86 100644 --- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs +++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs @@ -453,7 +453,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { let mut vid_map = FxIndexMap::, RegionDeps<'cx>>::default(); let mut finished_map = FxIndexMap::default(); - for (c, _) in ®ions.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 @@ impl<'tcx> AutoTraitFinder<'tcx> { 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!() + } } } diff --git a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs index 53518038f8d5e..8bbdc9d76c514 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs @@ -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) + } + } } }; 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 4b8bf86812317..ea062db3128ab 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 @@ -180,8 +180,8 @@ where 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() { diff --git a/compiler/rustc_traits/src/coroutine_witnesses.rs b/compiler/rustc_traits/src/coroutine_witnesses.rs index 2544cd8a13cd8..0dd4ea823c949 100644 --- a/compiler/rustc_traits/src/coroutine_witnesses.rs +++ b/compiler/rustc_traits/src/coroutine_witnesses.rs @@ -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, ®ion_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. diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index 5277e0a992fcd..e480b089b655c 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -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 @@ where } } +/// `'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(pub I::Region, pub I::Region); + +impl RegionEqPredicate { + /// Decompose `'a == 'b` into `['a: 'b, 'b: 'a]` + pub fn into_bidirectional_outlives(self) -> [OutlivesPredicate; 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 { + Outlives(OutlivesPredicate), + Eq(RegionEqPredicate), +} + +impl From> for RegionConstraint { + fn from(value: OutlivesPredicate) -> Self { + RegionConstraint::Outlives(value) + } +} + +impl From> for RegionConstraint { + fn from(value: RegionEqPredicate) -> Self { + RegionConstraint::Eq(value) + } +} + +impl RegionConstraint { + /// 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> { + 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, diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index 72b7df22b30d5..fe779b66dc245 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -253,7 +253,7 @@ impl Eq for Response {} #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] #[cfg_attr(feature = "nightly", derive(HashStable_NoContext))] pub struct ExternalConstraintsData { - pub region_constraints: Vec>, + pub region_constraints: Vec>, pub opaque_types: Vec<(ty::OpaqueTypeKey, I::Ty)>, pub normalization_nested_goals: NestedNormalizationGoals, } diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index fc61103d939fb..8379cb8c6eaa0 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -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 ®ions.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!() + } } }