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.
4
5use rustc_data_structures::frozen::Frozen;
6use rustc_data_structures::fx::FxIndexMap;
7use rustc_data_structures::graph::scc;
8use rustc_data_structures::graph::scc::Sccs;
9use rustc_index::IndexVec;
10use rustc_infer::infer::RegionVariableOrigin;
11use rustc_middle::mir::ConstraintCategory;
12use rustc_middle::ty::{RegionVid, UniverseIndex};
13use tracing::debug;
14
15use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet};
16use crate::consumers::OutlivesConstraint;
17use crate::diagnostics::UniverseInfo;
18use crate::region_infer::values::{LivenessValues, PlaceholderIndices};
19use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest};
20use crate::ty::VarianceDiagInfo;
21use crate::type_check::free_region_relations::UniversalRegionRelations;
22use crate::type_check::{Locations, MirTypeckRegionConstraints};
23use crate::universal_regions::UniversalRegions;
24use crate::{BorrowckInferCtxt, NllRegionVariableOrigin};
25
26/// A set of outlives constraints after rewriting to remove
27/// higher-kinded constraints.
28pub(crate) struct LoweredConstraints<'tcx> {
29    pub(crate) constraint_sccs: Sccs<RegionVid, ConstraintSccIndex>,
30    pub(crate) definitions: Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>,
31    pub(crate) scc_annotations: IndexVec<ConstraintSccIndex, RegionTracker>,
32    pub(crate) outlives_constraints: Frozen<OutlivesConstraintSet<'tcx>>,
33    pub(crate) type_tests: Vec<TypeTest<'tcx>>,
34    pub(crate) liveness_constraints: LivenessValues,
35    pub(crate) universe_causes: FxIndexMap<UniverseIndex, UniverseInfo<'tcx>>,
36    pub(crate) placeholder_indices: PlaceholderIndices,
37}
38
39impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> {
40    pub(crate) fn init(definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>) -> Self {
41        Self { scc_to_annotation: IndexVec::new(), definitions }
42    }
43}
44
45/// A Visitor for SCC annotation construction.
46pub(crate) struct SccAnnotations<'d, 'tcx, A: scc::Annotation> {
47    pub(crate) scc_to_annotation: IndexVec<ConstraintSccIndex, A>,
48    definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>,
49}
50
51impl scc::Annotations<RegionVid> for SccAnnotations<'_, '_, RegionTracker> {
52    fn new(&self, element: RegionVid) -> RegionTracker {
53        RegionTracker::new(element, &self.definitions[element])
54    }
55
56    fn annotate_scc(&mut self, scc: ConstraintSccIndex, annotation: RegionTracker) {
57        let idx = self.scc_to_annotation.push(annotation);
58        assert!(idx == scc);
59    }
60
61    type Ann = RegionTracker;
62    type SccIdx = ConstraintSccIndex;
63}
64
65/// An annotation for region graph SCCs that tracks
66/// the values of its elements. This annotates a single SCC.
67#[derive(Copy, Debug, Clone)]
68pub(crate) struct RegionTracker {
69    /// The largest universe of a placeholder reached from this SCC.
70    /// This includes placeholders within this SCC.
71    max_placeholder_universe_reached: UniverseIndex,
72
73    /// The largest universe nameable from this SCC.
74    /// It is the smallest nameable universes of all
75    /// existential regions reachable from it.
76    max_nameable_universe: UniverseIndex,
77
78    /// The representative Region Variable Id for this SCC.
79    pub(crate) representative: Representative,
80}
81
82impl RegionTracker {
83    pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self {
84        let placeholder_universe =
85            if matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)) {
86                definition.universe
87            } else {
88                UniverseIndex::ROOT
89            };
90
91        Self {
92            max_placeholder_universe_reached: placeholder_universe,
93            max_nameable_universe: definition.universe,
94            representative: Representative::new(rvid, definition),
95        }
96    }
97
98    /// The largest universe this SCC can name. It's the smallest
99    /// largest nameable uninverse of any reachable region.
100    pub(crate) fn max_nameable_universe(self) -> UniverseIndex {
101        self.max_nameable_universe
102    }
103
104    pub(crate) fn max_placeholder_universe_reached(self) -> UniverseIndex {
105        self.max_placeholder_universe_reached
106    }
107
108    fn merge_min_max_seen(&mut self, other: &Self) {
109        self.max_placeholder_universe_reached = std::cmp::max(
110            self.max_placeholder_universe_reached,
111            other.max_placeholder_universe_reached,
112        );
113
114        self.max_nameable_universe =
115            std::cmp::min(self.max_nameable_universe, other.max_nameable_universe);
116    }
117
118    /// Returns `true` if during the annotated SCC reaches a placeholder
119    /// with a universe larger than the smallest nameable universe of any
120    /// reachable existential region.
121    pub(crate) fn has_incompatible_universes(&self) -> bool {
122        self.max_nameable_universe().cannot_name(self.max_placeholder_universe_reached)
123    }
124
125    /// Determine if the tracked universes of the two SCCs are compatible.
126    pub(crate) fn universe_compatible_with(&self, other: Self) -> bool {
127        self.max_nameable_universe().can_name(other.max_nameable_universe())
128            || self.max_nameable_universe().can_name(other.max_placeholder_universe_reached)
129    }
130}
131
132impl scc::Annotation for RegionTracker {
133    fn merge_scc(mut self, other: Self) -> Self {
134        self.representative = self.representative.merge_scc(other.representative);
135        self.merge_min_max_seen(&other);
136        self
137    }
138
139    fn merge_reached(mut self, other: Self) -> Self {
140        // No update to in-component values, only add seen values.
141        self.merge_min_max_seen(&other);
142        self
143    }
144}
145
146/// Determines if the region variable definitions contain
147/// placeholders, and compute them for later use.
148// FIXME: This is also used by opaque type handling. Move it to a separate file.
149pub(super) fn region_definitions<'tcx>(
150    infcx: &BorrowckInferCtxt<'tcx>,
151    universal_regions: &UniversalRegions<'tcx>,
152) -> (Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>, bool) {
153    let var_infos = infcx.get_region_var_infos();
154    // Create a RegionDefinition for each inference variable. This happens here because
155    // it allows us to sneak in a cheap check for placeholders. Otherwise, its proper home
156    // is in `RegionInferenceContext::new()`, probably.
157    let mut definitions = IndexVec::with_capacity(var_infos.len());
158    let mut has_placeholders = false;
159
160    for info in var_infos.iter() {
161        let origin = match info.origin {
162            RegionVariableOrigin::Nll(origin) => origin,
163            _ => NllRegionVariableOrigin::Existential { name: None },
164        };
165
166        let definition = RegionDefinition { origin, universe: info.universe, external_name: None };
167
168        has_placeholders |= matches!(origin, NllRegionVariableOrigin::Placeholder(_));
169        definitions.push(definition);
170    }
171
172    // Add external names from universal regions in fun function definitions.
173    // FIXME: this two-step method is annoying, but I don't know how to avoid it.
174    for (external_name, variable) in universal_regions.named_universal_regions_iter() {
175        debug!("region {:?} has external name {:?}", variable, external_name);
176        definitions[variable].external_name = Some(external_name);
177    }
178    (Frozen::freeze(definitions), has_placeholders)
179}
180
181/// This method handles placeholders by rewriting the constraint
182/// graph. For each strongly connected component in the constraint
183/// graph such that there is a series of constraints
184///    A: B: C: ... : X  where
185/// A contains a placeholder whose universe cannot be named by X,
186/// add a constraint that A: 'static. This is a safe upper bound
187/// in the face of borrow checker/trait solver limitations that will
188/// eventually go away.
189///
190/// For a more precise definition, see the documentation for
191/// [`RegionTracker`] and its methods!
192///
193/// This edge case used to be handled during constraint propagation.
194/// It was rewritten as part of the Polonius project with the goal of moving
195/// higher-kindedness concerns out of the path of the borrow checker,
196/// for two reasons:
197///
198/// 1. Implementing Polonius is difficult enough without also
199///     handling them.
200/// 2. The long-term goal is to handle higher-kinded concerns
201///     in the trait solver, where they belong. This avoids
202///     logic duplication and allows future trait solvers
203///     to compute better bounds than for example our
204///     "must outlive 'static" here.
205///
206/// This code is a stop-gap measure in preparation for the future trait solver.
207///
208/// Every constraint added by this method is an internal `IllegalUniverse` constraint.
209pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
210    constraints: MirTypeckRegionConstraints<'tcx>,
211    universal_region_relations: &Frozen<UniversalRegionRelations<'tcx>>,
212    infcx: &BorrowckInferCtxt<'tcx>,
213) -> LoweredConstraints<'tcx> {
214    let universal_regions = &universal_region_relations.universal_regions;
215    let (definitions, has_placeholders) = region_definitions(infcx, universal_regions);
216
217    let MirTypeckRegionConstraints {
218        placeholder_indices,
219        placeholder_index_to_region: _,
220        liveness_constraints,
221        mut outlives_constraints,
222        universe_causes,
223        type_tests,
224    } = constraints;
225
226    let fr_static = universal_regions.fr_static;
227    let compute_sccs =
228        |constraints: &OutlivesConstraintSet<'tcx>,
229         annotations: &mut SccAnnotations<'_, 'tcx, RegionTracker>| {
230            ConstraintSccs::new_with_annotation(
231                &constraints.graph(definitions.len()).region_graph(constraints, fr_static),
232                annotations,
233            )
234        };
235
236    let mut scc_annotations = SccAnnotations::init(&definitions);
237    let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations);
238
239    // This code structure is a bit convoluted because it allows for a planned
240    // future change where the early return here has a different type of annotation
241    // that does much less work.
242    if !has_placeholders {
243        debug!("No placeholder regions found; skipping rewriting logic!");
244
245        return LoweredConstraints {
246            type_tests,
247            constraint_sccs,
248            scc_annotations: scc_annotations.scc_to_annotation,
249            definitions,
250            outlives_constraints: Frozen::freeze(outlives_constraints),
251            liveness_constraints,
252            universe_causes,
253            placeholder_indices,
254        };
255    }
256    debug!("Placeholders present; activating placeholder handling logic!");
257
258    let added_constraints = rewrite_placeholder_outlives(
259        &constraint_sccs,
260        &scc_annotations,
261        fr_static,
262        &mut outlives_constraints,
263    );
264
265    let (constraint_sccs, scc_annotations) = if added_constraints {
266        let mut annotations = SccAnnotations::init(&definitions);
267
268        // We changed the constraint set and so must recompute SCCs.
269        // Optimisation opportunity: if we can add them incrementally (and that's
270        // possible because edges to 'static always only merge SCCs into 'static),
271        // we would potentially save a lot of work here.
272        (compute_sccs(&outlives_constraints, &mut annotations), annotations.scc_to_annotation)
273    } else {
274        // If we didn't add any back-edges; no more work needs doing
275        debug!("No constraints rewritten!");
276        (constraint_sccs, scc_annotations.scc_to_annotation)
277    };
278
279    LoweredConstraints {
280        constraint_sccs,
281        definitions,
282        scc_annotations,
283        outlives_constraints: Frozen::freeze(outlives_constraints),
284        type_tests,
285        liveness_constraints,
286        universe_causes,
287        placeholder_indices,
288    }
289}
290
291fn rewrite_placeholder_outlives<'tcx>(
292    sccs: &Sccs<RegionVid, ConstraintSccIndex>,
293    annotations: &SccAnnotations<'_, '_, RegionTracker>,
294    fr_static: RegionVid,
295    outlives_constraints: &mut OutlivesConstraintSet<'tcx>,
296) -> bool {
297    // Changed to `true` if we added any constraints and need to
298    // recompute SCCs.
299    let mut added_constraints = false;
300
301    let annotations = &annotations.scc_to_annotation;
302
303    for scc in sccs.all_sccs() {
304        // No point in adding 'static: 'static!
305        // This micro-optimisation makes somewhat sense
306        // because static outlives *everything*.
307        if scc == sccs.scc(fr_static) {
308            continue;
309        }
310
311        let annotation = annotations[scc];
312
313        // If this SCC participates in a universe violation,
314        // e.g. if it reaches a region with a universe smaller than
315        // the largest region reached, add a requirement that it must
316        // outlive `'static`.
317        if annotation.has_incompatible_universes() {
318            // Optimisation opportunity: this will add more constraints than
319            // needed for correctness, since an SCC upstream of another with
320            // a universe violation will "infect" its downstream SCCs to also
321            // outlive static.
322            let scc_representative_outlives_static = OutlivesConstraint {
323                sup: annotation.representative.rvid(),
324                sub: fr_static,
325                category: ConstraintCategory::IllegalUniverse,
326                locations: Locations::All(rustc_span::DUMMY_SP),
327                span: rustc_span::DUMMY_SP,
328                variance_info: VarianceDiagInfo::None,
329                from_closure: false,
330            };
331            outlives_constraints.push(scc_representative_outlives_static);
332            added_constraints = true;
333            debug!("Added {:?}: 'static!", annotation.representative.rvid());
334        }
335    }
336    added_constraints
337}