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 the tracked universes of the two SCCs are compatible.
170    pub(crate) fn universe_compatible_with(&self, other: Self) -> bool {
171        // HACK: We first check whether we can name the highest existential universe
172        // of `other`. This only exists to avoid errors in case that scc already
173        // depends on a placeholder it cannot name itself.
174        self.max_nameable_universe().can_name(other.max_nameable_universe())
175            || other.reachable_placeholders.can_be_named_by(self.max_nameable_universe())
176    }
177
178    /// If this SCC reaches a placeholder it can't name, return it.
179    fn unnameable_placeholder(&self) -> Option<(UniverseIndex, RegionVid)> {
180        self.reachable_placeholders.max_universe().filter(|&(placeholder_universe, _)| {
181            !self.max_nameable_universe().can_name(placeholder_universe)
182        })
183    }
184}
185
186impl scc::Annotation for RegionTracker {
187    fn merge_scc(self, other: Self) -> Self {
188        trace!("{:?} << {:?}", self.representative, other.representative);
189
190        Self {
191            representative: self.representative.min(other.representative),
192            max_nameable_universe: self.max_nameable_universe.min(other.max_nameable_universe),
193            reachable_placeholders: self.reachable_placeholders.merge(other.reachable_placeholders),
194        }
195    }
196
197    fn merge_reached(self, other: Self) -> Self {
198        Self {
199            max_nameable_universe: self.max_nameable_universe.min(other.max_nameable_universe),
200            reachable_placeholders: self.reachable_placeholders.merge(other.reachable_placeholders),
201            representative: self.representative,
202        }
203    }
204}
205
206/// Determines if the region variable definitions contain
207/// placeholders, and compute them for later use.
208// FIXME: This is also used by opaque type handling. Move it to a separate file.
209pub(super) fn region_definitions<'tcx>(
210    infcx: &BorrowckInferCtxt<'tcx>,
211    universal_regions: &UniversalRegions<'tcx>,
212) -> (Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>, bool) {
213    let var_infos = infcx.get_region_var_infos();
214    // Create a RegionDefinition for each inference variable. This happens here because
215    // it allows us to sneak in a cheap check for placeholders. Otherwise, its proper home
216    // is in `RegionInferenceContext::new()`, probably.
217    let mut definitions = IndexVec::with_capacity(var_infos.len());
218    let mut has_placeholders = false;
219
220    for info in var_infos.iter() {
221        let origin = match info.origin {
222            RegionVariableOrigin::Nll(origin) => origin,
223            _ => NllRegionVariableOrigin::Existential { name: None },
224        };
225
226        let definition = RegionDefinition { origin, universe: info.universe, external_name: None };
227
228        has_placeholders |= matches!(origin, NllRegionVariableOrigin::Placeholder(_));
229        definitions.push(definition);
230    }
231
232    // Add external names from universal regions in fun function definitions.
233    // FIXME: this two-step method is annoying, but I don't know how to avoid it.
234    for (external_name, variable) in universal_regions.named_universal_regions_iter() {
235        debug!("region {:?} has external name {:?}", variable, external_name);
236        definitions[variable].external_name = Some(external_name);
237    }
238    (Frozen::freeze(definitions), has_placeholders)
239}
240
241/// This method handles placeholders by rewriting the constraint
242/// graph. For each strongly connected component in the constraint
243/// graph such that there is a series of constraints
244///    A: B: C: ... : X  where
245/// A contains a placeholder whose universe cannot be named by X,
246/// add a constraint that A: 'static. This is a safe upper bound
247/// in the face of borrow checker/trait solver limitations that will
248/// eventually go away.
249///
250/// For a more precise definition, see the documentation for
251/// [`RegionTracker`] and its methods!
252///
253/// This edge case used to be handled during constraint propagation.
254/// It was rewritten as part of the Polonius project with the goal of moving
255/// higher-kindedness concerns out of the path of the borrow checker,
256/// for two reasons:
257///
258/// 1. Implementing Polonius is difficult enough without also
259///     handling them.
260/// 2. The long-term goal is to handle higher-kinded concerns
261///     in the trait solver, where they belong. This avoids
262///     logic duplication and allows future trait solvers
263///     to compute better bounds than for example our
264///     "must outlive 'static" here.
265///
266/// This code is a stop-gap measure in preparation for the future trait solver.
267///
268/// Every constraint added by this method is an internal `IllegalUniverse` constraint.
269pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
270    constraints: MirTypeckRegionConstraints<'tcx>,
271    universal_region_relations: &Frozen<UniversalRegionRelations<'tcx>>,
272    infcx: &BorrowckInferCtxt<'tcx>,
273) -> LoweredConstraints<'tcx> {
274    let universal_regions = &universal_region_relations.universal_regions;
275    let (definitions, has_placeholders) = region_definitions(infcx, universal_regions);
276
277    let MirTypeckRegionConstraints {
278        placeholder_indices,
279        placeholder_index_to_region: _,
280        liveness_constraints,
281        mut outlives_constraints,
282        universe_causes,
283        type_tests,
284    } = constraints;
285
286    let fr_static = universal_regions.fr_static;
287    let compute_sccs =
288        |constraints: &OutlivesConstraintSet<'tcx>,
289         annotations: &mut SccAnnotations<'_, 'tcx, RegionTracker>| {
290            ConstraintSccs::new_with_annotation(
291                &constraints.graph(definitions.len()).region_graph(constraints, fr_static),
292                annotations,
293            )
294        };
295
296    let mut scc_annotations = SccAnnotations::init(&definitions);
297    let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations);
298
299    // This code structure is a bit convoluted because it allows for a planned
300    // future change where the early return here has a different type of annotation
301    // that does much less work.
302    if !has_placeholders {
303        debug!("No placeholder regions found; skipping rewriting logic!");
304
305        return LoweredConstraints {
306            type_tests,
307            constraint_sccs,
308            scc_annotations: scc_annotations.scc_to_annotation,
309            definitions,
310            outlives_constraints: Frozen::freeze(outlives_constraints),
311            liveness_constraints,
312            universe_causes,
313            placeholder_indices,
314        };
315    }
316    debug!("Placeholders present; activating placeholder handling logic!");
317
318    let added_constraints = rewrite_placeholder_outlives(
319        &constraint_sccs,
320        &scc_annotations,
321        fr_static,
322        &mut outlives_constraints,
323    );
324
325    let (constraint_sccs, scc_annotations) = if added_constraints {
326        let mut annotations = SccAnnotations::init(&definitions);
327
328        // We changed the constraint set and so must recompute SCCs.
329        // Optimisation opportunity: if we can add them incrementally (and that's
330        // possible because edges to 'static always only merge SCCs into 'static),
331        // we would potentially save a lot of work here.
332        (compute_sccs(&outlives_constraints, &mut annotations), annotations.scc_to_annotation)
333    } else {
334        // If we didn't add any back-edges; no more work needs doing
335        debug!("No constraints rewritten!");
336        (constraint_sccs, scc_annotations.scc_to_annotation)
337    };
338
339    LoweredConstraints {
340        constraint_sccs,
341        definitions,
342        scc_annotations,
343        outlives_constraints: Frozen::freeze(outlives_constraints),
344        type_tests,
345        liveness_constraints,
346        universe_causes,
347        placeholder_indices,
348    }
349}
350
351fn rewrite_placeholder_outlives<'tcx>(
352    sccs: &Sccs<RegionVid, ConstraintSccIndex>,
353    annotations: &SccAnnotations<'_, '_, RegionTracker>,
354    fr_static: RegionVid,
355    outlives_constraints: &mut OutlivesConstraintSet<'tcx>,
356) -> bool {
357    // Changed to `true` if we added any constraints and need to
358    // recompute SCCs.
359    let mut added_constraints = false;
360
361    let annotations = &annotations.scc_to_annotation;
362
363    for scc in sccs.all_sccs() {
364        // No point in adding 'static: 'static!
365        // This micro-optimisation makes somewhat sense
366        // because static outlives *everything*.
367        if scc == sccs.scc(fr_static) {
368            continue;
369        }
370
371        let annotation = annotations[scc];
372
373        let Some((max_u, max_u_rvid)) = annotation.unnameable_placeholder() else {
374            continue;
375        };
376
377        debug!(
378            "Placeholder universe {max_u:?} is too large for its SCC, represented by {:?}",
379            annotation.representative
380        );
381
382        // We only add one `r: 'static` constraint per SCC, where `r` is the SCC representative.
383        // That constraint is annotated with some placeholder `unnameable` where
384        // `unnameable` is unnameable from `r` and there is a path in the constraint graph
385        // between them.
386        //
387        // There is one exception; if some other region in this SCC can't name `'r`, then
388        // we pick the region with the smallest universe in the SCC, so that a path can
389        // always start in `'r` to find a motivation that isn't cyclic.
390        let blame_to = if annotation.representative.rvid() == max_u_rvid {
391            // Assertion: the region that lowered our universe is an existential one and we are a placeholder!
392
393            // The SCC's representative is not nameable from some region
394            // that ends up in the SCC.
395            let small_universed_rvid = annotation.max_nameable_universe.1;
396            debug!(
397                "{small_universed_rvid:?} lowered our universe to {:?}",
398                annotation.max_nameable_universe()
399            );
400            small_universed_rvid
401        } else {
402            // `max_u_rvid` is not nameable by the SCC's representative.
403            max_u_rvid
404        };
405
406        // FIXME: if we can extract a useful blame span here, future error
407        // reporting and constraint search can be simplified.
408
409        added_constraints = true;
410        outlives_constraints.push(OutlivesConstraint {
411            sup: annotation.representative.rvid(),
412            sub: fr_static,
413            category: ConstraintCategory::OutlivesUnnameablePlaceholder(blame_to),
414            locations: Locations::All(rustc_span::DUMMY_SP),
415            span: rustc_span::DUMMY_SP,
416            variance_info: VarianceDiagInfo::None,
417            from_closure: false,
418        });
419    }
420    added_constraints
421}