blob: 3ef37bf3466f16e6d0b6e42e9869ba78e0b0faaf [file] [log] [blame]
use crate::infer::outlives::components::{compute_alias_components_recursive, Component};
use crate::infer::outlives::env::RegionBoundPairs;
use crate::infer::region_constraints::VerifyIfEq;
use crate::infer::{GenericKind, VerifyBound};
use rustc_data_structures::sso::SsoHashSet;
use rustc_middle::ty::GenericArg;
use rustc_middle::ty::{self, OutlivesPredicate, Ty, TyCtxt};
use smallvec::smallvec;
/// 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 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<ty::Region<'tcx>>,
caller_bounds: &'cx [ty::PolyTypeOutlivesPredicate<'tcx>],
}
impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
pub fn new(
tcx: TyCtxt<'tcx>,
region_bound_pairs: &'cx RegionBoundPairs<'tcx>,
implicit_region_bound: Option<ty::Region<'tcx>>,
caller_bounds: &'cx [ty::PolyTypeOutlivesPredicate<'tcx>],
) -> Self {
Self { tcx, region_bound_pairs, implicit_region_bound, caller_bounds }
}
#[instrument(level = "debug", skip(self))]
pub 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 `<T as
/// Trait<'0>>::Item` where `'0` is a region variable, and the
/// user has `<T as Trait<'a>>::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 fn approx_declared_bounds_from_env(
&self,
alias_ty: ty::AliasTy<'tcx>,
) -> Vec<ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>> {
let erased_alias_ty = self.tcx.erase_regions(alias_ty.to_ty(self.tcx));
self.declared_generic_bounds_from_env_for_erased_ty(erased_alias_ty)
}
#[instrument(level = "debug", skip(self, visited))]
pub fn alias_bound(
&self,
alias_ty: ty::AliasTy<'tcx>,
visited: &mut SsoHashSet<GenericArg<'tcx>>,
) -> VerifyBound<'tcx> {
let alias_ty_as_ty = alias_ty.to_ty(self.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()
&& ty == alias_ty_as_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![];
compute_alias_components_recursive(self.tcx, alias_ty_as_ty, &mut components, visited);
self.bound_from_components(&components, visited)
};
VerifyBound::AnyBound(env_bounds.chain(definition_bounds).collect()).or(recursive_bound)
}
fn bound_from_components(
&self,
components: &[Component<'tcx>],
visited: &mut SsoHashSet<GenericArg<'tcx>>,
) -> VerifyBound<'tcx> {
let mut bounds = components
.iter()
.map(|component| self.bound_from_single_component(component, visited))
// 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<'tcx>,
visited: &mut SsoHashSet<GenericArg<'tcx>>,
) -> 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, visited),
Component::EscapingAlias(ref components) => {
self.bound_from_components(components, visited)
}
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<ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>> {
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 erase regions, so
/// * Under-approximated because we look for syntactic equality and so for complex types
/// like `<T as Foo<fn(&u32, &u32)>>::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<ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>> {
let tcx = self.tcx;
// To start, collect bounds from user environment. Note that
// parameter environments are already elaborated, so we don't
// have to worry about that.
let param_bounds = 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.
let from_region_bound_pairs =
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_regions(p_ty);
(erased_p_ty == erased_ty)
.then_some(ty::Binder::dummy(ty::OutlivesPredicate(p_ty, r)))
});
param_bounds
.chain(from_region_bound_pairs)
.inspect(|bound| {
debug!(
"declared_generic_bounds_from_env_for_erased_ty: result predicate = {:?}",
bound
)
})
.collect()
}
/// Given a projection like `<T as Foo<'x>>::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 <Self as Foo<'a>>::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> <Self as Foo<'x, 'b>>::Bar: 'x
/// {
/// type Bar;
/// }
/// ```
///
/// This is for simplicity, and because we are not really smart
/// enough to cope with such bounds anywhere.
pub fn declared_bounds_from_definition(
&self,
alias_ty: ty::AliasTy<'tcx>,
) -> impl Iterator<Item = ty::Region<'tcx>> {
let tcx = self.tcx;
let bounds = tcx.item_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)
}
}