rustc_borrowck/
handle_placeholders.rs

1//! Logic for lowering higher-kinded outlives constraints
2//! (with placeholders and universes) and turn them into regular
3//! outlives constraints.
4use rustc_data_structures::frozen::Frozen;
5use rustc_data_structures::fx::FxIndexMap;
6use rustc_data_structures::graph::scc;
7use rustc_data_structures::graph::scc::Sccs;
8use rustc_index::IndexVec;
9use rustc_infer::infer::RegionVariableOrigin;
10use rustc_middle::mir::ConstraintCategory;
11use rustc_middle::ty::{RegionVid, UniverseIndex};
12use tracing::{debug, trace};
13
14use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet};
15use crate::consumers::OutlivesConstraint;
16use crate::diagnostics::UniverseInfo;
17use crate::region_infer::values::{LivenessValues, PlaceholderIndices};
18use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest};
19use crate::ty::VarianceDiagInfo;
20use crate::type_check::free_region_relations::UniversalRegionRelations;
21use crate::type_check::{Locations, MirTypeckRegionConstraints};
22use crate::universal_regions::UniversalRegions;
23use crate::{BorrowckInferCtxt, NllRegionVariableOrigin};
24
25/// A set of outlives constraints after rewriting to remove
26/// higher-kinded constraints.
27pub(crate) struct LoweredConstraints<'tcx> {
28    pub(crate) constraint_sccs: Sccs<RegionVid, ConstraintSccIndex>,
29    pub(crate) definitions: Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>,
30    pub(crate) scc_annotations: IndexVec<ConstraintSccIndex, RegionTracker>,
31    pub(crate) outlives_constraints: Frozen<OutlivesConstraintSet<'tcx>>,
32    pub(crate) type_tests: Vec<TypeTest<'tcx>>,
33    pub(crate) liveness_constraints: LivenessValues,
34    pub(crate) universe_causes: FxIndexMap<UniverseIndex, UniverseInfo<'tcx>>,
35    pub(crate) placeholder_indices: PlaceholderIndices,
36}
37
38impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> {
39    pub(crate) fn init(definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>) -> Self {
40        Self { scc_to_annotation: IndexVec::new(), definitions }
41    }
42}
43
44/// A Visitor for SCC annotation construction.
45pub(crate) struct SccAnnotations<'d, 'tcx, A: scc::Annotation> {
46    pub(crate) scc_to_annotation: IndexVec<ConstraintSccIndex, A>,
47    definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>,
48}
49
50impl scc::Annotations<RegionVid> for SccAnnotations<'_, '_, RegionTracker> {
51    fn new(&self, element: RegionVid) -> RegionTracker {
52        RegionTracker::new(element, &self.definitions[element])
53    }
54
55    fn annotate_scc(&mut self, scc: ConstraintSccIndex, annotation: RegionTracker) {
56        let idx = self.scc_to_annotation.push(annotation);
57        assert!(idx == scc);
58    }
59
60    type Ann = RegionTracker;
61    type SccIdx = ConstraintSccIndex;
62}
63
64#[derive(Copy, Debug, Clone, PartialEq, Eq)]
65enum PlaceholderReachability {
66    /// This SCC reaches no placeholders.
67    NoPlaceholders,
68    /// This SCC reaches at least one placeholder.
69    Placeholders {
70        /// The largest-universed placeholder we can reach
71        max_universe: (UniverseIndex, RegionVid),
72
73        /// The placeholder with the smallest ID
74        min_placeholder: RegionVid,
75
76        /// The placeholder with the largest ID
77        max_placeholder: RegionVid,
78    },
79}
80
81impl PlaceholderReachability {
82    /// Merge the reachable placeholders of two graph components.
83    fn merge(self, other: PlaceholderReachability) -> PlaceholderReachability {
84        use PlaceholderReachability::*;
85        match (self, other) {
86            (NoPlaceholders, NoPlaceholders) => NoPlaceholders,
87            (NoPlaceholders, p @ Placeholders { .. })
88            | (p @ Placeholders { .. }, NoPlaceholders) => p,
89            (
90                Placeholders {
91                    min_placeholder: min_pl,
92                    max_placeholder: max_pl,
93                    max_universe: max_u,
94                },
95                Placeholders { min_placeholder, max_placeholder, max_universe },
96            ) => Placeholders {
97                min_placeholder: min_pl.min(min_placeholder),
98                max_placeholder: max_pl.max(max_placeholder),
99                max_universe: max_u.max(max_universe),
100            },
101        }
102    }
103
104    fn max_universe(&self) -> Option<(UniverseIndex, RegionVid)> {
105        match self {
106            Self::NoPlaceholders => None,
107            Self::Placeholders { max_universe, .. } => Some(*max_universe),
108        }
109    }
110
111    /// If we have reached placeholders, determine if they can
112    /// be named from this universe.
113    fn can_be_named_by(&self, from: UniverseIndex) -> bool {
114        self.max_universe()
115            .is_none_or(|(max_placeholder_universe, _)| from.can_name(max_placeholder_universe))
116    }
117}
118
119/// An annotation for region graph SCCs that tracks
120/// the values of its elements. This annotates a single SCC.
121#[derive(Copy, Debug, Clone)]
122pub(crate) struct RegionTracker {
123    reachable_placeholders: PlaceholderReachability,
124
125    /// The largest universe nameable from this SCC.
126    /// It is the smallest nameable universes of all
127    /// existential regions reachable from it. Small Rvids are preferred.
128    max_nameable_universe: (UniverseIndex, RegionVid),
129
130    /// The representative Region Variable Id for this SCC.
131    pub(crate) representative: Representative,
132}
133
134impl RegionTracker {
135    pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self {
136        let reachable_placeholders =
137            if matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)) {
138                PlaceholderReachability::Placeholders {
139                    max_universe: (definition.universe, rvid),
140                    min_placeholder: rvid,
141                    max_placeholder: rvid,
142                }
143            } else {
144                PlaceholderReachability::NoPlaceholders
145            };
146
147        Self {
148            reachable_placeholders,
149            max_nameable_universe: (definition.universe, rvid),
150            representative: Representative::new(rvid, definition),
151        }
152    }
153
154    /// The largest universe this SCC can name. It's the smallest
155    /// largest nameable universe of any reachable region, or
156    /// `max_nameable(r) = min (max_nameable(r') for r' reachable from r)`
157    pub(crate) fn max_nameable_universe(self) -> UniverseIndex {
158        self.max_nameable_universe.0
159    }
160
161    pub(crate) fn max_placeholder_universe_reached(self) -> UniverseIndex {
162        if let Some((universe, _)) = self.reachable_placeholders.max_universe() {
163            universe
164        } else {
165            UniverseIndex::ROOT
166        }
167    }
168
169    /// Determine if we can name all the placeholders in `other`.
170    pub(crate) fn can_name_all_placeholders(&self, other: Self) -> bool {
171        other.reachable_placeholders.can_be_named_by(self.max_nameable_universe.0)
172    }
173
174    /// If this SCC reaches a placeholder it can't name, return it.
175    fn unnameable_placeholder(&self) -> Option<(UniverseIndex, RegionVid)> {
176        self.reachable_placeholders.max_universe().filter(|&(placeholder_universe, _)| {
177            !self.max_nameable_universe().can_name(placeholder_universe)
178        })
179    }
180}
181
182impl scc::Annotation for RegionTracker {
183    fn merge_scc(self, other: Self) -> Self {
184        trace!("{:?} << {:?}", self.representative, other.representative);
185
186        Self {
187            representative: self.representative.min(other.representative),
188            max_nameable_universe: self.max_nameable_universe.min(other.max_nameable_universe),
189            reachable_placeholders: self.reachable_placeholders.merge(other.reachable_placeholders),
190        }
191    }
192
193    fn merge_reached(self, other: Self) -> Self {
194        Self {
195            max_nameable_universe: self.max_nameable_universe.min(other.max_nameable_universe),
196            reachable_placeholders: self.reachable_placeholders.merge(other.reachable_placeholders),
197            representative: self.representative,
198        }
199    }
200}
201
202/// Determines if the region variable definitions contain
203/// placeholders, and compute them for later use.
204// FIXME: This is also used by opaque type handling. Move it to a separate file.
205pub(super) fn region_definitions<'tcx>(
206    infcx: &BorrowckInferCtxt<'tcx>,
207    universal_regions: &UniversalRegions<'tcx>,
208) -> (Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>, bool) {
209    let var_infos = infcx.get_region_var_infos();
210    // Create a RegionDefinition for each inference variable. This happens here because
211    // it allows us to sneak in a cheap check for placeholders. Otherwise, its proper home
212    // is in `RegionInferenceContext::new()`, probably.
213    let mut definitions = IndexVec::with_capacity(var_infos.len());
214    let mut has_placeholders = false;
215
216    for info in var_infos.iter() {
217        let origin = match info.origin {
218            RegionVariableOrigin::Nll(origin) => origin,
219            _ => NllRegionVariableOrigin::Existential { name: None },
220        };
221
222        let definition = RegionDefinition { origin, universe: info.universe, external_name: None };
223
224        has_placeholders |= matches!(origin, NllRegionVariableOrigin::Placeholder(_));
225        definitions.push(definition);
226    }
227
228    // Add external names from universal regions in fun function definitions.
229    // FIXME: this two-step method is annoying, but I don't know how to avoid it.
230    for (external_name, variable) in universal_regions.named_universal_regions_iter() {
231        debug!("region {:?} has external name {:?}", variable, external_name);
232        definitions[variable].external_name = Some(external_name);
233    }
234    (Frozen::freeze(definitions), has_placeholders)
235}
236
237/// This method handles placeholders by rewriting the constraint
238/// graph. For each strongly connected component in the constraint
239/// graph such that there is a series of constraints
240///    A: B: C: ... : X  where
241/// A contains a placeholder whose universe cannot be named by X,
242/// add a constraint that A: 'static. This is a safe upper bound
243/// in the face of borrow checker/trait solver limitations that will
244/// eventually go away.
245///
246/// For a more precise definition, see the documentation for
247/// [`RegionTracker`] and its methods!
248///
249/// This edge case used to be handled during constraint propagation.
250/// It was rewritten as part of the Polonius project with the goal of moving
251/// higher-kindedness concerns out of the path of the borrow checker,
252/// for two reasons:
253///
254/// 1. Implementing Polonius is difficult enough without also
255///     handling them.
256/// 2. The long-term goal is to handle higher-kinded concerns
257///     in the trait solver, where they belong. This avoids
258///     logic duplication and allows future trait solvers
259///     to compute better bounds than for example our
260///     "must outlive 'static" here.
261///
262/// This code is a stop-gap measure in preparation for the future trait solver.
263///
264/// Every constraint added by this method is an internal `IllegalUniverse` constraint.
265pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
266    constraints: MirTypeckRegionConstraints<'tcx>,
267    universal_region_relations: &Frozen<UniversalRegionRelations<'tcx>>,
268    infcx: &BorrowckInferCtxt<'tcx>,
269) -> LoweredConstraints<'tcx> {
270    let universal_regions = &universal_region_relations.universal_regions;
271    let (definitions, has_placeholders) = region_definitions(infcx, universal_regions);
272
273    let MirTypeckRegionConstraints {
274        placeholder_indices,
275        placeholder_index_to_region: _,
276        liveness_constraints,
277        mut outlives_constraints,
278        universe_causes,
279        type_tests,
280    } = constraints;
281
282    let fr_static = universal_regions.fr_static;
283    let compute_sccs =
284        |constraints: &OutlivesConstraintSet<'tcx>,
285         annotations: &mut SccAnnotations<'_, 'tcx, RegionTracker>| {
286            ConstraintSccs::new_with_annotation(
287                &constraints.graph(definitions.len()).region_graph(constraints, fr_static),
288                annotations,
289            )
290        };
291
292    let mut scc_annotations = SccAnnotations::init(&definitions);
293    let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations);
294
295    // This code structure is a bit convoluted because it allows for a planned
296    // future change where the early return here has a different type of annotation
297    // that does much less work.
298    if !has_placeholders {
299        debug!("No placeholder regions found; skipping rewriting logic!");
300
301        return LoweredConstraints {
302            type_tests,
303            constraint_sccs,
304            scc_annotations: scc_annotations.scc_to_annotation,
305            definitions,
306            outlives_constraints: Frozen::freeze(outlives_constraints),
307            liveness_constraints,
308            universe_causes,
309            placeholder_indices,
310        };
311    }
312    debug!("Placeholders present; activating placeholder handling logic!");
313
314    let added_constraints = rewrite_placeholder_outlives(
315        &constraint_sccs,
316        &scc_annotations,
317        fr_static,
318        &mut outlives_constraints,
319    );
320
321    let (constraint_sccs, scc_annotations) = if added_constraints {
322        let mut annotations = SccAnnotations::init(&definitions);
323
324        // We changed the constraint set and so must recompute SCCs.
325        // Optimisation opportunity: if we can add them incrementally (and that's
326        // possible because edges to 'static always only merge SCCs into 'static),
327        // we would potentially save a lot of work here.
328        (compute_sccs(&outlives_constraints, &mut annotations), annotations.scc_to_annotation)
329    } else {
330        // If we didn't add any back-edges; no more work needs doing
331        debug!("No constraints rewritten!");
332        (constraint_sccs, scc_annotations.scc_to_annotation)
333    };
334
335    LoweredConstraints {
336        constraint_sccs,
337        definitions,
338        scc_annotations,
339        outlives_constraints: Frozen::freeze(outlives_constraints),
340        type_tests,
341        liveness_constraints,
342        universe_causes,
343        placeholder_indices,
344    }
345}
346
347fn rewrite_placeholder_outlives<'tcx>(
348    sccs: &Sccs<RegionVid, ConstraintSccIndex>,
349    annotations: &SccAnnotations<'_, '_, RegionTracker>,
350    fr_static: RegionVid,
351    outlives_constraints: &mut OutlivesConstraintSet<'tcx>,
352) -> bool {
353    // Changed to `true` if we added any constraints and need to
354    // recompute SCCs.
355    let mut added_constraints = false;
356
357    let annotations = &annotations.scc_to_annotation;
358
359    for scc in sccs.all_sccs() {
360        // No point in adding 'static: 'static!
361        // This micro-optimisation makes somewhat sense
362        // because static outlives *everything*.
363        if scc == sccs.scc(fr_static) {
364            continue;
365        }
366
367        let annotation = annotations[scc];
368
369        let Some((max_u, max_u_rvid)) = annotation.unnameable_placeholder() else {
370            continue;
371        };
372
373        debug!(
374            "Placeholder universe {max_u:?} is too large for its SCC, represented by {:?}",
375            annotation.representative
376        );
377
378        // We only add one `r: 'static` constraint per SCC, where `r` is the SCC representative.
379        // That constraint is annotated with some placeholder `unnameable` where
380        // `unnameable` is unnameable from `r` and there is a path in the constraint graph
381        // between them.
382        //
383        // There is one exception; if some other region in this SCC can't name `'r`, then
384        // we pick the region with the smallest universe in the SCC, so that a path can
385        // always start in `'r` to find a motivation that isn't cyclic.
386        let blame_to = if annotation.representative.rvid() == max_u_rvid {
387            // Assertion: the region that lowered our universe is an existential one and we are a placeholder!
388
389            // The SCC's representative is not nameable from some region
390            // that ends up in the SCC.
391            let small_universed_rvid = annotation.max_nameable_universe.1;
392            debug!(
393                "{small_universed_rvid:?} lowered our universe to {:?}",
394                annotation.max_nameable_universe()
395            );
396            small_universed_rvid
397        } else {
398            // `max_u_rvid` is not nameable by the SCC's representative.
399            max_u_rvid
400        };
401
402        // FIXME: if we can extract a useful blame span here, future error
403        // reporting and constraint search can be simplified.
404
405        added_constraints = true;
406        outlives_constraints.push(OutlivesConstraint {
407            sup: annotation.representative.rvid(),
408            sub: fr_static,
409            category: ConstraintCategory::OutlivesUnnameablePlaceholder(blame_to),
410            locations: Locations::All(rustc_span::DUMMY_SP),
411            span: rustc_span::DUMMY_SP,
412            variance_info: VarianceDiagInfo::None,
413            from_closure: false,
414        });
415    }
416    added_constraints
417}