rustc_trait_selection/traits/query/type_op/
implied_outlives_bounds.rs

1use std::ops::ControlFlow;
2
3use rustc_infer::infer::TypeOutlivesConstraint;
4use rustc_infer::infer::canonical::CanonicalQueryInput;
5use rustc_infer::traits::query::OutlivesBound;
6use rustc_infer::traits::query::type_op::ImpliedOutlivesBounds;
7use rustc_middle::infer::canonical::CanonicalQueryResponse;
8use rustc_middle::traits::ObligationCause;
9use rustc_middle::ty::outlives::{Component, push_outlives_components};
10use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitable, TypeVisitor};
11use rustc_span::def_id::CRATE_DEF_ID;
12use rustc_span::{DUMMY_SP, Span, sym};
13use smallvec::{SmallVec, smallvec};
14
15use crate::traits::query::NoSolution;
16use crate::traits::{ObligationCtxt, wf};
17
18impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
19    type QueryResponse = Vec<OutlivesBound<'tcx>>;
20
21    fn try_fast_path(
22        _tcx: TyCtxt<'tcx>,
23        key: &ParamEnvAnd<'tcx, Self>,
24    ) -> Option<Self::QueryResponse> {
25        // Don't go into the query for things that can't possibly have lifetimes.
26        match key.value.ty.kind() {
27            ty::Tuple(elems) if elems.is_empty() => Some(vec![]),
28            ty::Never | ty::Str | ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) => {
29                Some(vec![])
30            }
31            _ => None,
32        }
33    }
34
35    fn perform_query(
36        tcx: TyCtxt<'tcx>,
37        canonicalized: CanonicalQueryInput<'tcx, ParamEnvAnd<'tcx, Self>>,
38    ) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution> {
39        tcx.implied_outlives_bounds((canonicalized, false))
40    }
41
42    fn perform_locally_with_next_solver(
43        ocx: &ObligationCtxt<'_, 'tcx>,
44        key: ParamEnvAnd<'tcx, Self>,
45        span: Span,
46    ) -> Result<Self::QueryResponse, NoSolution> {
47        compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty, span, false)
48    }
49}
50
51pub fn compute_implied_outlives_bounds_inner<'tcx>(
52    ocx: &ObligationCtxt<'_, 'tcx>,
53    param_env: ty::ParamEnv<'tcx>,
54    ty: Ty<'tcx>,
55    span: Span,
56    disable_implied_bounds_hack: bool,
57) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
58    // Inside mir borrowck, each computation starts with an empty list.
59    assert!(
60        ocx.infcx.inner.borrow().region_obligations().is_empty(),
61        "compute_implied_outlives_bounds assumes region obligations are empty before starting"
62    );
63
64    let normalize_ty = |ty| -> Result<_, NoSolution> {
65        // We must normalize the type so we can compute the right outlives components.
66        // for example, if we have some constrained param type like `T: Trait<Out = U>`,
67        // and we know that `&'a T::Out` is WF, then we want to imply `U: 'a`.
68        let ty = ocx
69            .deeply_normalize(&ObligationCause::dummy_with_span(span), param_env, ty)
70            .map_err(|_| NoSolution)?;
71        Ok(ty)
72    };
73
74    // Sometimes when we ask what it takes for T: WF, we get back that
75    // U: WF is required; in that case, we push U onto this stack and
76    // process it next. Because the resulting predicates aren't always
77    // guaranteed to be a subset of the original type, so we need to store the
78    // WF args we've computed in a set.
79    let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
80    let mut wf_args = vec![ty.into(), normalize_ty(ty)?.into()];
81
82    let mut outlives_bounds: Vec<OutlivesBound<'tcx>> = vec![];
83
84    while let Some(arg) = wf_args.pop() {
85        if !checked_wf_args.insert(arg) {
86            continue;
87        }
88
89        // From the full set of obligations, just filter down to the region relationships.
90        for obligation in
91            wf::unnormalized_obligations(ocx.infcx, param_env, arg, DUMMY_SP, CRATE_DEF_ID)
92                .into_iter()
93                .flatten()
94        {
95            let pred = ocx
96                .deeply_normalize(
97                    &ObligationCause::dummy_with_span(span),
98                    param_env,
99                    obligation.predicate,
100                )
101                .map_err(|_| NoSolution)?;
102            let Some(pred) = pred.kind().no_bound_vars() else {
103                continue;
104            };
105            match pred {
106                // FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound
107                // if we ever support that
108                ty::PredicateKind::Clause(ty::ClauseKind::Trait(..))
109                | ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(..))
110                | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..))
111                | ty::PredicateKind::Subtype(..)
112                | ty::PredicateKind::Coerce(..)
113                | ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
114                | ty::PredicateKind::DynCompatible(..)
115                | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..))
116                | ty::PredicateKind::ConstEquate(..)
117                | ty::PredicateKind::Ambiguous
118                | ty::PredicateKind::NormalizesTo(..)
119                | ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_))
120                | ty::PredicateKind::AliasRelate(..) => {}
121
122                // We need to search through *all* WellFormed predicates
123                ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(term)) => {
124                    wf_args.push(term);
125                }
126
127                // We need to register region relationships
128                ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(
129                    ty::OutlivesPredicate(r_a, r_b),
130                )) => outlives_bounds.push(OutlivesBound::RegionSubRegion(r_b, r_a)),
131
132                ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(
133                    ty_a,
134                    r_b,
135                ))) => {
136                    let mut components = smallvec![];
137                    push_outlives_components(ocx.infcx.tcx, ty_a, &mut components);
138                    outlives_bounds.extend(implied_bounds_from_components(r_b, components))
139                }
140            }
141        }
142    }
143
144    // If we detect `bevy_ecs::*::ParamSet` in the WF args list (and `disable_implied_bounds_hack`
145    // or `-Zno-implied-bounds-compat` are not set), then use the registered outlives obligations
146    // as implied bounds.
147    if !disable_implied_bounds_hack
148        && !ocx.infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat
149        && ty.visit_with(&mut ContainsBevyParamSet { tcx: ocx.infcx.tcx }).is_break()
150    {
151        for TypeOutlivesConstraint { sup_type, sub_region, .. } in
152            ocx.infcx.clone_registered_region_obligations()
153        {
154            let mut components = smallvec![];
155            push_outlives_components(ocx.infcx.tcx, sup_type, &mut components);
156            outlives_bounds.extend(implied_bounds_from_components(sub_region, components));
157        }
158    }
159
160    Ok(outlives_bounds)
161}
162
163struct ContainsBevyParamSet<'tcx> {
164    tcx: TyCtxt<'tcx>,
165}
166
167impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsBevyParamSet<'tcx> {
168    type Result = ControlFlow<()>;
169
170    fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
171        // We only care to match `ParamSet<T>` or `&ParamSet<T>`.
172        match t.kind() {
173            ty::Adt(def, _) => {
174                if self.tcx.item_name(def.did()) == sym::ParamSet
175                    && self.tcx.crate_name(def.did().krate) == sym::bevy_ecs
176                {
177                    return ControlFlow::Break(());
178                }
179            }
180            ty::Ref(_, ty, _) => ty.visit_with(self)?,
181            _ => {}
182        }
183
184        ControlFlow::Continue(())
185    }
186}
187
188/// When we have an implied bound that `T: 'a`, we can further break
189/// this down to determine what relationships would have to hold for
190/// `T: 'a` to hold. We get to assume that the caller has validated
191/// those relationships.
192fn implied_bounds_from_components<'tcx>(
193    sub_region: ty::Region<'tcx>,
194    sup_components: SmallVec<[Component<TyCtxt<'tcx>>; 4]>,
195) -> Vec<OutlivesBound<'tcx>> {
196    sup_components
197        .into_iter()
198        .filter_map(|component| {
199            match component {
200                Component::Region(r) => Some(OutlivesBound::RegionSubRegion(sub_region, r)),
201                Component::Param(p) => Some(OutlivesBound::RegionSubParam(sub_region, p)),
202                Component::Alias(p) => Some(OutlivesBound::RegionSubAlias(sub_region, p)),
203                Component::Placeholder(_p) => {
204                    // FIXME(non_lifetime_binders): Placeholders don't currently
205                    // imply anything for outlives, though they could easily.
206                    None
207                }
208                Component::EscapingAlias(_) =>
209                // If the projection has escaping regions, don't
210                // try to infer any implied bounds even for its
211                // free components. This is conservative, because
212                // the caller will still have to prove that those
213                // free components outlive `sub_region`. But the
214                // idea is that the WAY that the caller proves
215                // that may change in the future and we want to
216                // give ourselves room to get smarter here.
217                {
218                    None
219                }
220                Component::UnresolvedInferenceVariable(..) => None,
221            }
222        })
223        .collect()
224}