diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 6ed70b39c5b7f..8e15bf47196e9 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -181,7 +181,7 @@ pub(crate) enum Cause { /// /// For more information about this translation, see /// `InferCtxt::process_registered_region_obligations` and -/// `InferCtxt::type_must_outlive` in `rustc_infer::infer::InferCtxt`. +/// [`rustc_infer::infer::outlives::obligations::require_type_outlives`]. #[derive(Clone, Debug)] pub(crate) struct TypeTest<'tcx> { /// The type `T` that must outlive the region. @@ -837,8 +837,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { lub } - /// Tests if `test` is true when applied to `lower_bound` at - /// `point`. + /// Tests if `generic_ty: lower_bound` holds by evaluating `verify_bound` fn eval_verify_bound( &self, infcx: &InferCtxt<'tcx>, diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index 703223e2e54a3..c17a5c56a8a0a 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -3,7 +3,9 @@ use rustc_hir::def_id::LocalDefId; use rustc_infer::infer::SubregionOrigin; use rustc_infer::infer::canonical::QueryRegionConstraints; use rustc_infer::infer::outlives::env::RegionBoundPairs; -use rustc_infer::infer::outlives::obligations::{TypeOutlives, TypeOutlivesDelegate}; +use rustc_infer::infer::outlives::obligations::{ + OutlivesHandlingDelegate, TypeOutlivesOpCtxt, require_type_outlives, +}; use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound}; use rustc_infer::traits::query::type_op::DeeplyNormalize; use rustc_middle::bug; @@ -191,14 +193,14 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { // we don't actually use this for anything, but // the `TypeOutlives` code needs an origin. let origin = SubregionOrigin::RelateParamBound(self.span, t1, None); - TypeOutlives::new( - &mut *self, - tcx, - region_bound_pairs, - Some(implicit_region_bound), - known_type_outlives_obligations, - ) - .type_must_outlive( + require_type_outlives( + &mut TypeOutlivesOpCtxt::new( + &mut *self, + tcx, + region_bound_pairs, + Some(implicit_region_bound), + known_type_outlives_obligations, + ), origin, t1, r2, @@ -302,7 +304,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { } } -impl<'a, 'b, 'tcx> TypeOutlivesDelegate<'tcx> for &'a mut ConstraintConversion<'b, 'tcx> { +impl<'a, 'b, 'tcx> OutlivesHandlingDelegate<'tcx> for &'a mut ConstraintConversion<'b, 'tcx> { fn push_sub_region_constraint( &mut self, _origin: SubregionOrigin<'tcx>, diff --git a/compiler/rustc_hir_analysis/src/outlives/utils.rs b/compiler/rustc_hir_analysis/src/outlives/utils.rs index 99a633e2b7d1f..2345d5e9d7dc1 100644 --- a/compiler/rustc_hir_analysis/src/outlives/utils.rs +++ b/compiler/rustc_hir_analysis/src/outlives/utils.rs @@ -1,9 +1,8 @@ use rustc_data_structures::fx::FxIndexMap; -use rustc_middle::ty::outlives::{Component, push_outlives_components}; +use rustc_middle::ty::outlives::{Component, compute_outlives_components}; use rustc_middle::ty::{self, GenericArg, GenericArgKind, Region, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_span::Span; -use smallvec::smallvec; /// Tracks the `T: 'a` or `'a: 'a` predicates that we have inferred /// must be added to the struct header. @@ -33,8 +32,7 @@ pub(crate) fn insert_outlives_predicate<'tcx>( // // Or if within `struct Foo` you had `T = Vec`, then // we would want to add `U: 'outlived_region` - let mut components = smallvec![]; - push_outlives_components(tcx, ty, &mut components); + let components = compute_outlives_components(tcx, ty); for component in components { match component { Component::Region(r) => { diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index c992cda8aaed0..c914645c3b38e 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -2,21 +2,21 @@ use rustc_data_structures::undo_log::UndoLogs; use rustc_middle::traits::query::{NoSolution, OutlivesBound}; -use rustc_middle::ty; -use tracing::instrument; +use rustc_middle::ty::{self, Region, Ty, TypeVisitableExt}; +use tracing::{debug, instrument}; use self::env::OutlivesEnvironment; use super::region_constraints::{RegionConstraintData, UndoLog}; use super::{InferCtxt, RegionResolutionError, SubregionOrigin}; use crate::infer::free_regions::RegionRelations; -use crate::infer::lexical_region_resolve; use crate::infer::region_constraints::ConstraintKind; +use crate::infer::{TypeOutlivesConstraint, lexical_region_resolve}; +use crate::traits::{ObligationCause, ObligationCauseCode}; pub mod env; pub mod for_liveness; pub mod obligations; pub mod test_type_match; -pub(crate) mod verify; #[instrument(level = "debug", skip(param_env), ret)] pub fn explicit_outlives_bounds<'tcx>( @@ -31,6 +31,88 @@ pub fn explicit_outlives_bounds<'tcx>( } impl<'tcx> InferCtxt<'tcx> { + pub fn register_outlives_constraint( + &self, + ty::OutlivesPredicate(arg, r2): ty::ArgOutlivesPredicate<'tcx>, + cause: &ObligationCause<'tcx>, + ) { + match arg.kind() { + 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_type_outlives_constraint_inner( + &self, + obligation: TypeOutlivesConstraint<'tcx>, + ) { + let mut inner = self.inner.borrow_mut(); + inner.undo_log.push(crate::infer::UndoLog::PushTypeOutlivesConstraint); + inner.region_obligations.push(obligation); + } + + pub fn register_type_outlives_constraint( + &self, + sup_type: Ty<'tcx>, + sub_region: Region<'tcx>, + cause: &ObligationCause<'tcx>, + ) { + // `is_global` means the type has no params, infer, placeholder, or non-`'static` + // free regions. If the type has none of these things, then we can skip registering + // this outlives obligation since it has no components which affect lifetime + // checking in an interesting way. + if sup_type.is_global() { + return; + } + + debug!(?sup_type, ?sub_region, ?cause); + let origin = SubregionOrigin::from_obligation_cause(cause, || { + SubregionOrigin::RelateParamBound( + cause.span, + sup_type, + match cause.code().peel_derives() { + ObligationCauseCode::WhereClause(_, span) + | ObligationCauseCode::WhereClauseInExpr(_, span, ..) + | ObligationCauseCode::OpaqueTypeBound(span, _) + if !span.is_dummy() => + { + Some(*span) + } + _ => None, + }, + ) + }); + + self.register_type_outlives_constraint_inner(TypeOutlivesConstraint { + sup_type, + sub_region, + origin, + }); + } + /// Process the region constraints and return any errors that /// result. After this, no more unification operations should be /// done -- or the compiler will panic -- but it is legal to use @@ -89,6 +171,27 @@ impl<'tcx> InferCtxt<'tcx> { errors } + /// Trait queries just want to pass back type obligations "as is" + pub fn take_registered_region_obligations(&self) -> Vec> { + assert!(!self.in_snapshot(), "cannot take registered region obligations in a snapshot"); + std::mem::take(&mut self.inner.borrow_mut().region_obligations) + } + + pub fn clone_registered_region_obligations(&self) -> Vec> { + self.inner.borrow().region_obligations.clone() + } + + pub fn register_region_assumption(&self, assumption: ty::ArgOutlivesPredicate<'tcx>) { + let mut inner = self.inner.borrow_mut(); + inner.undo_log.push(crate::infer::UndoLog::PushRegionAssumption); + inner.region_assumptions.push(assumption); + } + + pub fn take_registered_region_assumptions(&self) -> Vec> { + assert!(!self.in_snapshot(), "cannot take registered region assumptions in a snapshot"); + std::mem::take(&mut self.inner.borrow_mut().region_assumptions) + } + /// Obtains (and clears) the current set of region /// constraints. The inference context is still usable: further /// unifications will simply add new constraints. diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index f06f50785eccf..4e6f9832a2ea6 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -1,5 +1,5 @@ //! Code that handles "type-outlives" constraints like `T: 'a`. This -//! is based on the `push_outlives_components` function defined in rustc_infer, +//! is based on the `compute_outlives_components` function defined in rustc_infer, //! but it adds a bit of heuristics on top, in particular to deal with //! associated types and projections. //! @@ -59,132 +59,28 @@ //! might later infer `?U` to something like `&'b u32`, which would //! imply that `'b: 'a`. -use rustc_data_structures::undo_log::UndoLogs; +use rustc_data_structures::assert_matches; use rustc_middle::bug; use rustc_middle::mir::ConstraintCategory; use rustc_middle::traits::query::NoSolution; -use rustc_middle::ty::outlives::{Component, push_outlives_components}; +use rustc_middle::ty::outlives::{ + Component, compute_alias_components_recursive, compute_outlives_components, +}; use rustc_middle::ty::{ - self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesPredicate, Region, Ty, TyCtxt, + self, GenericArgKind, GenericArgsRef, OutlivesPredicate, PolyTypeOutlivesPredicate, Ty, TyCtxt, TypeFoldable as _, TypeVisitableExt, }; -use smallvec::smallvec; -use tracing::{debug, instrument}; +use tracing::{debug, instrument, trace}; use super::env::OutlivesEnvironment; use crate::infer::outlives::env::RegionBoundPairs; -use crate::infer::outlives::verify::VerifyBoundCx; +use crate::infer::region_constraints::VerifyIfEq; use crate::infer::resolve::OpportunisticRegionResolver; -use crate::infer::snapshot::undo_log::UndoLog; 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::ArgOutlivesPredicate<'tcx>, - cause: &ObligationCause<'tcx>, - ) { - match arg.kind() { - 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_type_outlives_constraint_inner( - &self, - obligation: TypeOutlivesConstraint<'tcx>, - ) { - let mut inner = self.inner.borrow_mut(); - inner.undo_log.push(UndoLog::PushTypeOutlivesConstraint); - inner.region_obligations.push(obligation); - } - - pub fn register_type_outlives_constraint( - &self, - sup_type: Ty<'tcx>, - sub_region: Region<'tcx>, - cause: &ObligationCause<'tcx>, - ) { - // `is_global` means the type has no params, infer, placeholder, or non-`'static` - // free regions. If the type has none of these things, then we can skip registering - // this outlives obligation since it has no components which affect lifetime - // checking in an interesting way. - if sup_type.is_global() { - return; - } - - debug!(?sup_type, ?sub_region, ?cause); - let origin = SubregionOrigin::from_obligation_cause(cause, || { - SubregionOrigin::RelateParamBound( - cause.span, - sup_type, - match cause.code().peel_derives() { - ObligationCauseCode::WhereClause(_, span) - | ObligationCauseCode::WhereClauseInExpr(_, span, ..) - | ObligationCauseCode::OpaqueTypeBound(span, _) - if !span.is_dummy() => - { - Some(*span) - } - _ => None, - }, - ) - }); - - 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> { - assert!(!self.in_snapshot(), "cannot take registered region obligations in a snapshot"); - std::mem::take(&mut self.inner.borrow_mut().region_obligations) - } - - pub fn clone_registered_region_obligations(&self) -> Vec> { - self.inner.borrow().region_obligations.clone() - } - - pub fn register_region_assumption(&self, assumption: ty::ArgOutlivesPredicate<'tcx>) { - let mut inner = self.inner.borrow_mut(); - inner.undo_log.push(UndoLog::PushRegionAssumption); - inner.region_assumptions.push(assumption); - } - - pub fn take_registered_region_assumptions(&self) -> Vec> { - assert!(!self.in_snapshot(), "cannot take registered region assumptions in a snapshot"); - std::mem::take(&mut self.inner.borrow_mut().region_assumptions) - } - /// Process the region obligations that must be proven (during /// `regionck`) for the given `body_id`, given information about /// the region bounds in scope and so forth. @@ -245,15 +141,20 @@ impl<'tcx> InferCtxt<'tcx> { debug!(?sup_type, ?sub_region, ?origin); - let outlives = &mut TypeOutlives::new( - self, - self.tcx, - outlives_env.region_bound_pairs(), - None, - outlives_env.known_type_outlives(), - ); let category = origin.to_constraint_category(); - outlives.type_must_outlive(origin, sup_type, sub_region, category); + require_type_outlives( + &mut TypeOutlivesOpCtxt::new( + self, + self.tcx, + outlives_env.region_bound_pairs(), + None, + outlives_env.known_type_outlives(), + ), + origin, + sup_type, + sub_region, + category, + ); } } @@ -261,24 +162,305 @@ impl<'tcx> InferCtxt<'tcx> { } } -/// The `TypeOutlives` struct has the job of "lowering" a `T: 'a` -/// obligation into a series of `'a: 'b` constraints and "verify"s, as -/// described on the module comment. The final constraints are emitted -/// via a "delegate" of type `D` -- this is usually the `infcx`, which -/// accrues them into the `region_obligations` code, but for NLL we -/// use something else. -pub struct TypeOutlives<'cx, 'tcx, D> +/// Context used for constructing a [`VerifyBound`]. See module comments +/// for more information +pub(crate) struct VerifyBoundCx<'cx, 'tcx> { + tcx: TyCtxt<'tcx>, + region_bound_pairs: &'cx RegionBoundPairs<'tcx>, + /// During borrowck, if there are no outlives bounds on a generic + /// parameter `T`, we assume that `T: 'in_fn_body` holds. + /// + /// Outside of borrowck the only way to prove `T: '?0` is by + /// setting `'?0` to `'empty`. + implicit_region_bound: Option>, + caller_bounds: &'cx [ty::PolyTypeOutlivesPredicate<'tcx>], +} + +impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { + pub(crate) fn new( + tcx: TyCtxt<'tcx>, + region_bound_pairs: &'cx RegionBoundPairs<'tcx>, + implicit_region_bound: Option>, + caller_bounds: &'cx [ty::PolyTypeOutlivesPredicate<'tcx>], + ) -> Self { + Self { tcx, region_bound_pairs, implicit_region_bound, caller_bounds } + } + + #[instrument(level = "debug", skip(self))] + pub(crate) fn param_or_placeholder_bound(&self, ty: Ty<'tcx>) -> VerifyBound<'tcx> { + // Start with anything like `T: 'a` we can scrape from the + // environment. If the environment contains something like + // `for<'a> T: 'a`, then we know that `T` outlives everything. + let declared_bounds_from_env = self.declared_generic_bounds_from_env(ty); + debug!(?declared_bounds_from_env); + let mut param_bounds = vec![]; + for declared_bound in declared_bounds_from_env { + let bound_region = declared_bound.map_bound(|outlives| outlives.1); + if let Some(region) = bound_region.no_bound_vars() { + // This is `T: 'a` for some free region `'a`. + param_bounds.push(VerifyBound::OutlivedBy(region)); + } else { + // This is `for<'a> T: 'a`. This means that `T` outlives everything! All done here. + debug!("found that {ty:?} outlives any lifetime, returning empty vector"); + return VerifyBound::AllBounds(vec![]); + } + } + + // Add in the default bound of fn body that applies to all in + // scope type parameters: + if let Some(r) = self.implicit_region_bound { + debug!("adding implicit region bound of {r:?}"); + param_bounds.push(VerifyBound::OutlivedBy(r)); + } + + if param_bounds.is_empty() { + // We know that all types `T` outlive `'empty`, so if we + // can find no other bound, then check that the region + // being tested is `'empty`. + VerifyBound::IsEmpty + } else if param_bounds.len() == 1 { + // Micro-opt: no need to store the vector if it's just len 1 + param_bounds.pop().unwrap() + } else { + // If we can find any other bound `R` such that `T: R`, then + // we don't need to check for `'empty`, because `R: 'empty`. + VerifyBound::AnyBound(param_bounds) + } + } + + /// Given a projection like `T::Item`, searches the environment + /// for where-clauses like `T::Item: 'a`. Returns the set of + /// regions `'a` that it finds. + /// + /// This is an "approximate" check -- it may not find all + /// applicable bounds, and not all the bounds it returns can be + /// relied upon. In particular, this check ignores region + /// identity. So, for example, if we have `>::Item` where `'0` is a region variable, and the + /// user has `>::Item: 'b` in the environment, then + /// the clause from the environment only applies if `'0 = 'a`, + /// which we don't know yet. But we would still include `'b` in + /// this list. + pub(crate) fn approx_declared_bounds_from_env( + &self, + alias_ty: ty::AliasTy<'tcx>, + ) -> Vec> { + let erased_alias_ty = self.tcx.erase_and_anonymize_regions(alias_ty.to_ty(self.tcx)); + self.declared_generic_bounds_from_env_for_erased_ty(erased_alias_ty) + } + + #[instrument(level = "debug", skip(self))] + pub(crate) fn alias_ty_bound(&self, alias_ty: ty::AliasTy<'tcx>) -> VerifyBound<'tcx> { + // Search the env for where clauses like `P: 'a`. + let env_bounds = self.approx_declared_bounds_from_env(alias_ty).into_iter().map(|binder| { + if let Some(ty::OutlivesPredicate(ty, r)) = binder.no_bound_vars() + && let ty::Alias(_, alias_ty_from_bound) = *ty.kind() + && alias_ty_from_bound == alias_ty + { + // Micro-optimize if this is an exact match (this + // occurs often when there are no region variables + // involved). + VerifyBound::OutlivedBy(r) + } else { + let verify_if_eq_b = + binder.map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound }); + VerifyBound::IfEq(verify_if_eq_b) + } + }); + + // Extend with bounds that we can find from the definition. + let definition_bounds = + self.declared_bounds_from_definition(alias_ty).map(|r| VerifyBound::OutlivedBy(r)); + + // see the extensive comment in alias_ty_must_outlive + let recursive_bound = { + let kind = alias_ty.kind(self.tcx); + let components = compute_alias_components_recursive(self.tcx, kind, alias_ty); + self.bound_from_components(&components) + }; + + VerifyBound::AnyBound(env_bounds.chain(definition_bounds).collect()).or(recursive_bound) + } + + fn bound_from_components(&self, components: &[Component>]) -> VerifyBound<'tcx> { + let mut bounds = components + .iter() + .map(|component| self.bound_from_single_component(component)) + // Remove bounds that must hold, since they are not interesting. + .filter(|bound| !bound.must_hold()); + + match (bounds.next(), bounds.next()) { + (Some(first), None) => first, + (first, second) => { + VerifyBound::AllBounds(first.into_iter().chain(second).chain(bounds).collect()) + } + } + } + + fn bound_from_single_component( + &self, + component: &Component>, + ) -> VerifyBound<'tcx> { + match *component { + Component::Region(lt) => VerifyBound::OutlivedBy(lt), + Component::Param(param_ty) => self.param_or_placeholder_bound(param_ty.to_ty(self.tcx)), + Component::Placeholder(placeholder_ty) => { + self.param_or_placeholder_bound(Ty::new_placeholder(self.tcx, placeholder_ty)) + } + Component::Alias(alias_ty) => self.alias_ty_bound(alias_ty), + Component::EscapingAlias(ref components) => self.bound_from_components(components), + Component::UnresolvedInferenceVariable(v) => { + // Ignore this, we presume it will yield an error later, since + // if a type variable is not resolved by this point it never + // will be. + self.tcx + .dcx() + .delayed_bug(format!("unresolved inference variable in outlives: {v:?}")); + // Add a bound that never holds. + VerifyBound::AnyBound(vec![]) + } + } + } + + /// Searches the environment for where-clauses like `G: 'a` where + /// `G` is either some type parameter `T` or a projection like + /// `T::Item`. Returns a vector of the `'a` bounds it can find. + /// + /// This is a conservative check -- it may not find all applicable + /// bounds, but all the bounds it returns can be relied upon. + fn declared_generic_bounds_from_env( + &self, + generic_ty: Ty<'tcx>, + ) -> Vec> { + assert_matches!(generic_ty.kind(), ty::Param(_) | ty::Placeholder(_)); + self.declared_generic_bounds_from_env_for_erased_ty(generic_ty) + } + + /// Searches the environment to find all bounds that apply to `erased_ty`. + /// Obviously these must be approximate -- they are in fact both *over* and + /// and *under* approximated: + /// + /// * Over-approximated because we don't consider equality of regions. + /// * Under-approximated because we look for syntactic equality and so for complex types + /// like `>::Item` or whatever we may fail to figure out + /// all the subtleties. + /// + /// In some cases, such as when `erased_ty` represents a `ty::Param`, however, + /// the result is precise. + #[instrument(level = "debug", skip(self))] + fn declared_generic_bounds_from_env_for_erased_ty( + &self, + erased_ty: Ty<'tcx>, + ) -> Vec> { + let tcx = self.tcx; + let mut bounds = vec![]; + + // To start, collect bounds from user environment. Note that + // parameter environments are already elaborated, so we don't + // have to worry about that. + bounds.extend(self.caller_bounds.iter().copied().filter(move |outlives_predicate| { + super::test_type_match::can_match_erased_ty(tcx, *outlives_predicate, erased_ty) + })); + + // Next, collect regions we scraped from the well-formedness + // constraints in the fn signature. To do that, we walk the list + // of known relations from the fn ctxt. + // + // This is crucial because otherwise code like this fails: + // + // fn foo<'a, A>(x: &'a A) { x.bar() } + // + // The problem is that the type of `x` is `&'a A`. To be + // well-formed, then, A must outlive `'a`, but we don't know that + // this holds from first principles. + bounds.extend(self.region_bound_pairs.iter().filter_map(|&OutlivesPredicate(p, r)| { + debug!( + "declared_generic_bounds_from_env_for_erased_ty: region_bound_pair = {:?}", + (r, p) + ); + // Fast path for the common case. + match (&p, erased_ty.kind()) { + // In outlive routines, all types are expected to be fully normalized. + // And therefore we can safely use structural equality for alias types. + (GenericKind::Param(p1), ty::Param(p2)) if p1 == p2 => {} + (GenericKind::Placeholder(p1), ty::Placeholder(p2)) if p1 == p2 => {} + (GenericKind::Alias(a1), ty::Alias(_, a2)) if a1.def_id == a2.def_id => {} + _ => return None, + } + + let p_ty = p.to_ty(tcx); + let erased_p_ty = self.tcx.erase_and_anonymize_regions(p_ty); + (erased_p_ty == erased_ty).then_some(ty::Binder::dummy(ty::OutlivesPredicate(p_ty, r))) + })); + + bounds + } + + /// Given a projection like `>::Bar`, returns any bounds + /// declared in the trait definition. For example, if the trait were + /// + /// ```rust + /// trait Foo<'a> { + /// type Bar: 'a; + /// } + /// ``` + /// + /// If we were given the `DefId` of `Foo::Bar`, we would return + /// `'a`. You could then apply the instantiations from the + /// projection to convert this into your namespace. This also + /// works if the user writes `where >::Bar: 'a` on + /// the trait. In fact, it works by searching for just such a + /// where-clause. + /// + /// It will not, however, work for higher-ranked bounds like: + /// + /// ```ignore(this does compile today, previously was marked as `compile_fail,E0311`) + /// trait Foo<'a, 'b> + /// where for<'x> >::Bar: 'x + /// { + /// type Bar; + /// } + /// ``` + /// + /// This is for simplicity, and because we are not really smart + /// enough to cope with such bounds anywhere. + pub(crate) fn declared_bounds_from_definition( + &self, + alias_ty: ty::AliasTy<'tcx>, + ) -> impl Iterator> { + let tcx = self.tcx; + let bounds = tcx.item_self_bounds(alias_ty.def_id); + trace!("{:#?}", bounds.skip_binder()); + bounds + .iter_instantiated(tcx, alias_ty.args) + .filter_map(|p| p.as_type_outlives_clause()) + .filter_map(|p| p.no_bound_vars()) + .map(|OutlivesPredicate(_, r)| r) + } +} + +/// Holds necessary context for the [`require_type_outlives`] operation. +pub struct TypeOutlivesOpCtxt<'cx, 'tcx, D> where - D: TypeOutlivesDelegate<'tcx>, + D: OutlivesHandlingDelegate<'tcx>, { - // See the comments on `process_registered_region_obligations` for the meaning - // of these fields. delegate: D, tcx: TyCtxt<'tcx>, - verify_bound: VerifyBoundCx<'cx, 'tcx>, + verify_bound_cx: VerifyBoundCx<'cx, 'tcx>, } -pub trait TypeOutlivesDelegate<'tcx> { +pub trait OutlivesHandlingDelegate<'tcx> { + /// Subtle: this trait exists to abstract the outlives handling between + /// regular regionck and NLL. Unfortunately, NLL and regionck don't agree + /// on how subtyping works. + /// + /// In NLL `'a sub 'b` means `'a outlives 'b`. + /// In regionck `'a sub 'b` means the set of locations `'a` is live at is a subset + /// of the locations that `'b` is, or in other words, `'b outlives 'a`. + /// + /// This method will be called with the regionck meaning of subtyping. i.e. if + /// there is some `&'b u32: 'static` constraint, we will give a `'b sub 'static` + /// constraint. fn push_sub_region_constraint( &mut self, origin: SubregionOrigin<'tcx>, @@ -296,9 +478,9 @@ pub trait TypeOutlivesDelegate<'tcx> { ); } -impl<'cx, 'tcx, D> TypeOutlives<'cx, 'tcx, D> +impl<'cx, 'tcx, D> TypeOutlivesOpCtxt<'cx, 'tcx, D> where - D: TypeOutlivesDelegate<'tcx>, + D: OutlivesHandlingDelegate<'tcx>, { pub fn new( delegate: D, @@ -310,7 +492,7 @@ where Self { delegate, tcx, - verify_bound: VerifyBoundCx::new( + verify_bound_cx: VerifyBoundCx::new( tcx, region_bound_pairs, implicit_region_bound, @@ -318,30 +500,35 @@ where ), } } +} - /// Adds constraints to inference such that `T: 'a` holds (or - /// reports an error if it cannot). - /// - /// # Parameters - /// - /// - `origin`, the reason we need this constraint - /// - `ty`, the type `T` - /// - `region`, the region `'a` - #[instrument(level = "debug", skip(self))] - pub fn type_must_outlive( - &mut self, - origin: infer::SubregionOrigin<'tcx>, - ty: Ty<'tcx>, - region: ty::Region<'tcx>, - category: ConstraintCategory<'tcx>, - ) { - assert!(!ty.has_escaping_bound_vars()); - - let mut components = smallvec![]; - push_outlives_components(self.tcx, ty, &mut components); - self.components_must_outlive(origin, &components, region, category); - } +/// "lowers" a `T: 'a` obligation into a series of `'a: 'b` constraints +/// and "verify"s, as described on the module comment. The final constraints +/// are emitted via a "delegate" of type `D` -- this is either the `infcx`, which +/// accrues them into the `region_obligations` code, or a `ConstraintConversion` +/// (used during borrow checking). +/// +/// # Parameters +/// +/// - `origin`, the reason we need this constraint +/// - `ty`, the type `T` +/// - `region`, the region `'a` +#[instrument(level = "debug", skip(ctxt))] +pub fn require_type_outlives<'tcx, D: OutlivesHandlingDelegate<'tcx>>( + ctxt: &mut TypeOutlivesOpCtxt<'_, 'tcx, D>, + // ideally this would be in `ctxt` but then we lose ownership + origin: infer::SubregionOrigin<'tcx>, + ty: Ty<'tcx>, + region: ty::Region<'tcx>, + category: ConstraintCategory<'tcx>, +) { + assert!(!ty.has_escaping_bound_vars()); + + let components = compute_outlives_components(ctxt.tcx, ty); + ctxt.components_must_outlive(origin, &components, region, category); +} +impl<'tcx, D: OutlivesHandlingDelegate<'tcx>> TypeOutlivesOpCtxt<'_, 'tcx, D> { fn components_must_outlive( &mut self, origin: infer::SubregionOrigin<'tcx>, @@ -385,7 +572,8 @@ where region: ty::Region<'tcx>, param_ty: ty::ParamTy, ) { - let verify_bound = self.verify_bound.param_or_placeholder_bound(param_ty.to_ty(self.tcx)); + let verify_bound = + self.verify_bound_cx.param_or_placeholder_bound(param_ty.to_ty(self.tcx)); self.delegate.push_verify(origin, GenericKind::Param(param_ty), region, verify_bound); } @@ -397,7 +585,7 @@ where placeholder_ty: ty::PlaceholderType<'tcx>, ) { let verify_bound = self - .verify_bound + .verify_bound_cx .param_or_placeholder_bound(Ty::new_placeholder(self.tcx, placeholder_ty)); self.delegate.push_verify( origin, @@ -444,14 +632,14 @@ where // These are guaranteed to apply, no matter the inference // results. let trait_bounds: Vec<_> = - self.verify_bound.declared_bounds_from_definition(alias_ty).collect(); + self.verify_bound_cx.declared_bounds_from_definition(alias_ty).collect(); debug!(?trait_bounds); // Compute the bounds we can derive from the environment. This // is an "approximate" match -- in some cases, these bounds // may not apply. - let approx_env_bounds = self.verify_bound.approx_declared_bounds_from_env(alias_ty); + let approx_env_bounds = self.verify_bound_cx.approx_declared_bounds_from_env(alias_ty); debug!(?approx_env_bounds); // If declared bounds list is empty, the only applicable rule is @@ -516,7 +704,7 @@ where // projection outlive; in some cases, this may add insufficient // edges into the inference graph, leading to inference failures // even though a satisfactory solution exists. - let verify_bound = self.verify_bound.alias_bound(alias_ty); + let verify_bound = self.verify_bound_cx.alias_ty_bound(alias_ty); debug!("alias_must_outlive: pushing {:?}", verify_bound); self.delegate.push_verify(origin, GenericKind::Alias(alias_ty), region, verify_bound); } @@ -548,7 +736,7 @@ where } } GenericArgKind::Type(ty) => { - self.type_must_outlive(origin.clone(), ty, region, constraint); + require_type_outlives(self, origin.clone(), ty, region, constraint); } GenericArgKind::Const(_) => { // Const parameters don't impose constraints. @@ -558,7 +746,7 @@ where } } -impl<'cx, 'tcx> TypeOutlivesDelegate<'tcx> for &'cx InferCtxt<'tcx> { +impl<'cx, 'tcx> OutlivesHandlingDelegate<'tcx> for &'cx InferCtxt<'tcx> { fn push_sub_region_constraint( &mut self, origin: SubregionOrigin<'tcx>, diff --git a/compiler/rustc_infer/src/infer/outlives/verify.rs b/compiler/rustc_infer/src/infer/outlives/verify.rs deleted file mode 100644 index affeb01e6d052..0000000000000 --- a/compiler/rustc_infer/src/infer/outlives/verify.rs +++ /dev/null @@ -1,291 +0,0 @@ -use rustc_data_structures::assert_matches; -use rustc_middle::ty::outlives::{Component, compute_alias_components_recursive}; -use rustc_middle::ty::{self, OutlivesPredicate, Ty, TyCtxt}; -use smallvec::smallvec; -use tracing::{debug, instrument, trace}; - -use crate::infer::outlives::env::RegionBoundPairs; -use crate::infer::region_constraints::VerifyIfEq; -use crate::infer::{GenericKind, VerifyBound}; - -/// The `TypeOutlives` struct has the job of "lowering" a `T: 'a` -/// obligation into a series of `'a: 'b` constraints and "verifys", as -/// described on the module comment. The final constraints are emitted -/// via a "delegate" of type `D` -- this is usually the `infcx`, which -/// accrues them into the `region_obligations` code, but for NLL we -/// use something else. -pub(crate) struct VerifyBoundCx<'cx, 'tcx> { - tcx: TyCtxt<'tcx>, - region_bound_pairs: &'cx RegionBoundPairs<'tcx>, - /// During borrowck, if there are no outlives bounds on a generic - /// parameter `T`, we assume that `T: 'in_fn_body` holds. - /// - /// Outside of borrowck the only way to prove `T: '?0` is by - /// setting `'?0` to `'empty`. - implicit_region_bound: Option>, - caller_bounds: &'cx [ty::PolyTypeOutlivesPredicate<'tcx>], -} - -impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { - pub(crate) fn new( - tcx: TyCtxt<'tcx>, - region_bound_pairs: &'cx RegionBoundPairs<'tcx>, - implicit_region_bound: Option>, - caller_bounds: &'cx [ty::PolyTypeOutlivesPredicate<'tcx>], - ) -> Self { - Self { tcx, region_bound_pairs, implicit_region_bound, caller_bounds } - } - - #[instrument(level = "debug", skip(self))] - pub(crate) fn param_or_placeholder_bound(&self, ty: Ty<'tcx>) -> VerifyBound<'tcx> { - // Start with anything like `T: 'a` we can scrape from the - // environment. If the environment contains something like - // `for<'a> T: 'a`, then we know that `T` outlives everything. - let declared_bounds_from_env = self.declared_generic_bounds_from_env(ty); - debug!(?declared_bounds_from_env); - let mut param_bounds = vec![]; - for declared_bound in declared_bounds_from_env { - let bound_region = declared_bound.map_bound(|outlives| outlives.1); - if let Some(region) = bound_region.no_bound_vars() { - // This is `T: 'a` for some free region `'a`. - param_bounds.push(VerifyBound::OutlivedBy(region)); - } else { - // This is `for<'a> T: 'a`. This means that `T` outlives everything! All done here. - debug!("found that {ty:?} outlives any lifetime, returning empty vector"); - return VerifyBound::AllBounds(vec![]); - } - } - - // Add in the default bound of fn body that applies to all in - // scope type parameters: - if let Some(r) = self.implicit_region_bound { - debug!("adding implicit region bound of {r:?}"); - param_bounds.push(VerifyBound::OutlivedBy(r)); - } - - if param_bounds.is_empty() { - // We know that all types `T` outlive `'empty`, so if we - // can find no other bound, then check that the region - // being tested is `'empty`. - VerifyBound::IsEmpty - } else if param_bounds.len() == 1 { - // Micro-opt: no need to store the vector if it's just len 1 - param_bounds.pop().unwrap() - } else { - // If we can find any other bound `R` such that `T: R`, then - // we don't need to check for `'empty`, because `R: 'empty`. - VerifyBound::AnyBound(param_bounds) - } - } - - /// Given a projection like `T::Item`, searches the environment - /// for where-clauses like `T::Item: 'a`. Returns the set of - /// regions `'a` that it finds. - /// - /// This is an "approximate" check -- it may not find all - /// applicable bounds, and not all the bounds it returns can be - /// relied upon. In particular, this check ignores region - /// identity. So, for example, if we have `>::Item` where `'0` is a region variable, and the - /// user has `>::Item: 'b` in the environment, then - /// the clause from the environment only applies if `'0 = 'a`, - /// which we don't know yet. But we would still include `'b` in - /// this list. - pub(crate) fn approx_declared_bounds_from_env( - &self, - alias_ty: ty::AliasTy<'tcx>, - ) -> Vec> { - let erased_alias_ty = self.tcx.erase_and_anonymize_regions(alias_ty.to_ty(self.tcx)); - self.declared_generic_bounds_from_env_for_erased_ty(erased_alias_ty) - } - - #[instrument(level = "debug", skip(self))] - pub(crate) fn alias_bound(&self, alias_ty: ty::AliasTy<'tcx>) -> VerifyBound<'tcx> { - // Search the env for where clauses like `P: 'a`. - let env_bounds = self.approx_declared_bounds_from_env(alias_ty).into_iter().map(|binder| { - if let Some(ty::OutlivesPredicate(ty, r)) = binder.no_bound_vars() - && let ty::Alias(_, alias_ty_from_bound) = *ty.kind() - && alias_ty_from_bound == alias_ty - { - // Micro-optimize if this is an exact match (this - // occurs often when there are no region variables - // involved). - VerifyBound::OutlivedBy(r) - } else { - let verify_if_eq_b = - binder.map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound }); - VerifyBound::IfEq(verify_if_eq_b) - } - }); - - // Extend with bounds that we can find from the definition. - let definition_bounds = - self.declared_bounds_from_definition(alias_ty).map(|r| VerifyBound::OutlivedBy(r)); - - // see the extensive comment in projection_must_outlive - let recursive_bound = { - let mut components = smallvec![]; - let kind = alias_ty.kind(self.tcx); - compute_alias_components_recursive(self.tcx, kind, alias_ty, &mut components); - self.bound_from_components(&components) - }; - - VerifyBound::AnyBound(env_bounds.chain(definition_bounds).collect()).or(recursive_bound) - } - - fn bound_from_components(&self, components: &[Component>]) -> VerifyBound<'tcx> { - let mut bounds = components - .iter() - .map(|component| self.bound_from_single_component(component)) - // Remove bounds that must hold, since they are not interesting. - .filter(|bound| !bound.must_hold()); - - match (bounds.next(), bounds.next()) { - (Some(first), None) => first, - (first, second) => { - VerifyBound::AllBounds(first.into_iter().chain(second).chain(bounds).collect()) - } - } - } - - fn bound_from_single_component( - &self, - component: &Component>, - ) -> VerifyBound<'tcx> { - match *component { - Component::Region(lt) => VerifyBound::OutlivedBy(lt), - Component::Param(param_ty) => self.param_or_placeholder_bound(param_ty.to_ty(self.tcx)), - Component::Placeholder(placeholder_ty) => { - self.param_or_placeholder_bound(Ty::new_placeholder(self.tcx, placeholder_ty)) - } - Component::Alias(alias_ty) => self.alias_bound(alias_ty), - Component::EscapingAlias(ref components) => self.bound_from_components(components), - Component::UnresolvedInferenceVariable(v) => { - // Ignore this, we presume it will yield an error later, since - // if a type variable is not resolved by this point it never - // will be. - self.tcx - .dcx() - .delayed_bug(format!("unresolved inference variable in outlives: {v:?}")); - // Add a bound that never holds. - VerifyBound::AnyBound(vec![]) - } - } - } - - /// Searches the environment for where-clauses like `G: 'a` where - /// `G` is either some type parameter `T` or a projection like - /// `T::Item`. Returns a vector of the `'a` bounds it can find. - /// - /// This is a conservative check -- it may not find all applicable - /// bounds, but all the bounds it returns can be relied upon. - fn declared_generic_bounds_from_env( - &self, - generic_ty: Ty<'tcx>, - ) -> Vec> { - assert_matches!(generic_ty.kind(), ty::Param(_) | ty::Placeholder(_)); - self.declared_generic_bounds_from_env_for_erased_ty(generic_ty) - } - - /// Searches the environment to find all bounds that apply to `erased_ty`. - /// Obviously these must be approximate -- they are in fact both *over* and - /// and *under* approximated: - /// - /// * Over-approximated because we don't consider equality of regions. - /// * Under-approximated because we look for syntactic equality and so for complex types - /// like `>::Item` or whatever we may fail to figure out - /// all the subtleties. - /// - /// In some cases, such as when `erased_ty` represents a `ty::Param`, however, - /// the result is precise. - #[instrument(level = "debug", skip(self))] - fn declared_generic_bounds_from_env_for_erased_ty( - &self, - erased_ty: Ty<'tcx>, - ) -> Vec> { - let tcx = self.tcx; - let mut bounds = vec![]; - - // To start, collect bounds from user environment. Note that - // parameter environments are already elaborated, so we don't - // have to worry about that. - bounds.extend(self.caller_bounds.iter().copied().filter(move |outlives_predicate| { - super::test_type_match::can_match_erased_ty(tcx, *outlives_predicate, erased_ty) - })); - - // Next, collect regions we scraped from the well-formedness - // constraints in the fn signature. To do that, we walk the list - // of known relations from the fn ctxt. - // - // This is crucial because otherwise code like this fails: - // - // fn foo<'a, A>(x: &'a A) { x.bar() } - // - // The problem is that the type of `x` is `&'a A`. To be - // well-formed, then, A must outlive `'a`, but we don't know that - // this holds from first principles. - bounds.extend(self.region_bound_pairs.iter().filter_map(|&OutlivesPredicate(p, r)| { - debug!( - "declared_generic_bounds_from_env_for_erased_ty: region_bound_pair = {:?}", - (r, p) - ); - // Fast path for the common case. - match (&p, erased_ty.kind()) { - // In outlive routines, all types are expected to be fully normalized. - // And therefore we can safely use structural equality for alias types. - (GenericKind::Param(p1), ty::Param(p2)) if p1 == p2 => {} - (GenericKind::Placeholder(p1), ty::Placeholder(p2)) if p1 == p2 => {} - (GenericKind::Alias(a1), ty::Alias(_, a2)) if a1.def_id == a2.def_id => {} - _ => return None, - } - - let p_ty = p.to_ty(tcx); - let erased_p_ty = self.tcx.erase_and_anonymize_regions(p_ty); - (erased_p_ty == erased_ty).then_some(ty::Binder::dummy(ty::OutlivesPredicate(p_ty, r))) - })); - - bounds - } - - /// Given a projection like `>::Bar`, returns any bounds - /// declared in the trait definition. For example, if the trait were - /// - /// ```rust - /// trait Foo<'a> { - /// type Bar: 'a; - /// } - /// ``` - /// - /// If we were given the `DefId` of `Foo::Bar`, we would return - /// `'a`. You could then apply the instantiations from the - /// projection to convert this into your namespace. This also - /// works if the user writes `where >::Bar: 'a` on - /// the trait. In fact, it works by searching for just such a - /// where-clause. - /// - /// It will not, however, work for higher-ranked bounds like: - /// - /// ```ignore(this does compile today, previously was marked as `compile_fail,E0311`) - /// trait Foo<'a, 'b> - /// where for<'x> >::Bar: 'x - /// { - /// type Bar; - /// } - /// ``` - /// - /// This is for simplicity, and because we are not really smart - /// enough to cope with such bounds anywhere. - pub(crate) fn declared_bounds_from_definition( - &self, - alias_ty: ty::AliasTy<'tcx>, - ) -> impl Iterator> { - let tcx = self.tcx; - let bounds = tcx.item_self_bounds(alias_ty.def_id); - trace!("{:#?}", bounds.skip_binder()); - bounds - .iter_instantiated(tcx, alias_ty.args) - .filter_map(|p| p.as_type_outlives_clause()) - .filter_map(|p| p.no_bound_vars()) - .map(|OutlivesPredicate(_, r)| r) - } -} diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs index 47ff6190fa4f7..4ab4a37fc18f9 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs @@ -6,11 +6,11 @@ use rustc_infer::traits::query::OutlivesBound; use rustc_infer::traits::query::type_op::ImpliedOutlivesBounds; use rustc_middle::infer::canonical::CanonicalQueryResponse; use rustc_middle::traits::ObligationCause; -use rustc_middle::ty::outlives::{Component, push_outlives_components}; +use rustc_middle::ty::outlives::{Component, compute_outlives_components}; use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitable, TypeVisitor}; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::{DUMMY_SP, Span, sym}; -use smallvec::{SmallVec, smallvec}; +use smallvec::SmallVec; use crate::traits::query::NoSolution; use crate::traits::{ObligationCtxt, wf}; @@ -138,8 +138,7 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>( ty_a, r_b, ))) => { - let mut components = smallvec![]; - push_outlives_components(ocx.infcx.tcx, ty_a, &mut components); + let components = compute_outlives_components(ocx.infcx.tcx, ty_a); outlives_bounds.extend(implied_bounds_from_components(r_b, components)) } } @@ -156,8 +155,7 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>( for TypeOutlivesConstraint { sup_type, sub_region, .. } in ocx.infcx.clone_registered_region_obligations() { - let mut components = smallvec![]; - push_outlives_components(ocx.infcx.tcx, sup_type, &mut components); + let components = compute_outlives_components(ocx.infcx.tcx, sup_type); outlives_bounds.extend(implied_bounds_from_components(sub_region, components)); } } diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs index 2f7c78d497a4f..ead9d09365333 100644 --- a/compiler/rustc_type_ir/src/elaborate.rs +++ b/compiler/rustc_type_ir/src/elaborate.rs @@ -1,11 +1,9 @@ use std::marker::PhantomData; -use smallvec::smallvec; - use crate::data_structures::HashSet; use crate::inherent::*; use crate::lang_items::SolverTraitLangItem; -use crate::outlives::{Component, push_outlives_components}; +use crate::outlives::{Component, compute_outlives_components}; use crate::{self as ty, Interner, Upcast as _}; /// "Elaboration" is the process of identifying all the predicates that @@ -210,8 +208,7 @@ impl> Elaborator { return; } - let mut components = smallvec![]; - push_outlives_components(cx, ty_max, &mut components); + let components = compute_outlives_components(cx, ty_max); self.extend_deduped( components .into_iter() @@ -386,8 +383,7 @@ pub fn elaborate_outlives_assumptions( // Elaborate the components of an type, since we may have substituted a // generic coroutine with a more specific type. ty::GenericArgKind::Type(ty1) => { - let mut components = smallvec![]; - push_outlives_components(cx, ty1, &mut components); + let components = compute_outlives_components(cx, ty1); for c in components { match c { Component::Region(r1) => { diff --git a/compiler/rustc_type_ir/src/outlives.rs b/compiler/rustc_type_ir/src/outlives.rs index c7dccea6adc12..3aec4a4cbc471 100644 --- a/compiler/rustc_type_ir/src/outlives.rs +++ b/compiler/rustc_type_ir/src/outlives.rs @@ -3,7 +3,7 @@ //! RFC for reference. use derive_where::derive_where; -use smallvec::{SmallVec, smallvec}; +use smallvec::SmallVec; use crate::data_structures::SsoHashSet; use crate::inherent::*; @@ -51,23 +51,21 @@ pub enum Component { EscapingAlias(Vec>), } -/// Push onto `out` all the things that must outlive `'a` for the condition -/// `ty0: 'a` to hold. Note that `ty0` must be a **fully resolved type**. -pub fn push_outlives_components( - cx: I, - ty: I::Ty, - out: &mut SmallVec<[Component; 4]>, -) { - ty.visit_with(&mut OutlivesCollector { cx, out, visited: Default::default() }); +/// Returns all the components that must outlive some lifetime `'a` for the +/// condition `ty0: 'a` to hold. Note that `ty0` must be a **fully resolved type**. +pub fn compute_outlives_components(cx: I, ty: I::Ty) -> SmallVec<[Component; 4]> { + let mut collector = OutlivesCollector { cx, out: SmallVec::new(), visited: Default::default() }; + ty.visit_with(&mut collector); + collector.out } -struct OutlivesCollector<'a, I: Interner> { +struct OutlivesCollector { cx: I, - out: &'a mut SmallVec<[Component; 4]>, + out: SmallVec<[Component; 4]>, visited: SsoHashSet, } -impl TypeVisitor for OutlivesCollector<'_, I> { +impl TypeVisitor for OutlivesCollector { #[cfg(not(feature = "nightly"))] type Result = (); @@ -161,8 +159,7 @@ impl TypeVisitor for OutlivesCollector<'_, I> { // fallback case: hard code // OutlivesProjectionComponents. Continue walking // through and constrain Pi. - let mut subcomponents = smallvec![]; - compute_alias_components_recursive(self.cx, kind, alias_ty, &mut subcomponents); + let subcomponents = compute_alias_components_recursive(self.cx, kind, alias_ty); self.out.push(Component::EscapingAlias(subcomponents.into_iter().collect())); } } @@ -220,16 +217,15 @@ impl TypeVisitor for OutlivesCollector<'_, I> { /// Collect [Component]s for *all* the args of `alias_ty`. /// /// This should not be used to get the components of `alias_ty` itself. -/// Use [push_outlives_components] instead. +/// Use [compute_outlives_components] instead. pub fn compute_alias_components_recursive( cx: I, kind: ty::AliasTyKind, alias_ty: ty::AliasTy, - out: &mut SmallVec<[Component; 4]>, -) { +) -> SmallVec<[Component; 4]> { let opt_variances = cx.opt_alias_variances(kind, alias_ty.def_id); - let mut visitor = OutlivesCollector { cx, out, visited: Default::default() }; + let mut visitor = OutlivesCollector { cx, out: SmallVec::new(), visited: Default::default() }; for (index, child) in alias_ty.args.iter().enumerate() { if opt_variances.and_then(|variances| variances.get(index)) == Some(ty::Bivariant) { @@ -237,4 +233,6 @@ pub fn compute_alias_components_recursive( } child.visit_with(&mut visitor); } + + visitor.out }