rustc_borrowck/type_check/liveness/
trace.rs

1use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
2use rustc_index::bit_set::DenseBitSet;
3use rustc_index::interval::IntervalSet;
4use rustc_infer::infer::canonical::QueryRegionConstraints;
5use rustc_infer::infer::outlives::for_liveness;
6use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, HasLocalDecls, Local, Location};
7use rustc_middle::traits::query::DropckOutlivesResult;
8use rustc_middle::ty::relate::Relate;
9use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt};
10use rustc_mir_dataflow::ResultsCursor;
11use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
12use rustc_mir_dataflow::move_paths::{HasMoveData, MoveData, MovePathIndex};
13use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex};
14use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
15use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
16use rustc_trait_selection::traits::ObligationCtxt;
17use rustc_trait_selection::traits::query::dropck_outlives;
18use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOp, TypeOpOutput};
19use tracing::debug;
20
21use crate::polonius;
22use crate::region_infer::values;
23use crate::type_check::liveness::local_use_map::LocalUseMap;
24use crate::type_check::{NormalizeLocation, TypeChecker};
25
26/// This is the heart of the liveness computation. For each variable X
27/// that requires a liveness computation, it walks over all the uses
28/// of X and does a reverse depth-first search ("trace") through the
29/// MIR. This search stops when we find a definition of that variable.
30/// The points visited in this search is the USE-LIVE set for the variable;
31/// of those points is added to all the regions that appear in the variable's
32/// type.
33///
34/// We then also walks through each *drop* of those variables and does
35/// another search, stopping when we reach a use or definition. This
36/// is the DROP-LIVE set of points. Each of the points in the
37/// DROP-LIVE set are to the liveness sets for regions found in the
38/// `dropck_outlives` result of the variable's type (in particular,
39/// this respects `#[may_dangle]` annotations).
40pub(super) fn trace<'a, 'tcx>(
41    typeck: &mut TypeChecker<'_, 'tcx>,
42    location_map: &DenseLocationMap,
43    flow_inits: ResultsCursor<'a, 'tcx, MaybeInitializedPlaces<'a, 'tcx>>,
44    move_data: &MoveData<'tcx>,
45    relevant_live_locals: Vec<Local>,
46    boring_locals: Vec<Local>,
47) {
48    let local_use_map = &LocalUseMap::build(&relevant_live_locals, location_map, typeck.body);
49    let cx = LivenessContext {
50        typeck,
51        flow_inits,
52        location_map,
53        local_use_map,
54        move_data,
55        drop_data: FxIndexMap::default(),
56    };
57
58    let mut results = LivenessResults::new(cx);
59
60    results.add_extra_drop_facts(&relevant_live_locals);
61
62    results.compute_for_all_locals(relevant_live_locals);
63
64    results.dropck_boring_locals(boring_locals);
65}
66
67/// Contextual state for the type-liveness coroutine.
68struct LivenessContext<'a, 'typeck, 'b, 'tcx> {
69    /// Current type-checker, giving us our inference context etc.
70    ///
71    /// This also stores the body we're currently analyzing.
72    typeck: &'a mut TypeChecker<'typeck, 'tcx>,
73
74    /// Defines the `PointIndex` mapping
75    location_map: &'a DenseLocationMap,
76
77    /// Mapping to/from the various indices used for initialization tracking.
78    move_data: &'a MoveData<'tcx>,
79
80    /// Cache for the results of `dropck_outlives` query.
81    drop_data: FxIndexMap<Ty<'tcx>, DropData<'tcx>>,
82
83    /// Results of dataflow tracking which variables (and paths) have been
84    /// initialized.
85    flow_inits: ResultsCursor<'b, 'tcx, MaybeInitializedPlaces<'b, 'tcx>>,
86
87    /// Index indicating where each variable is assigned, used, or
88    /// dropped.
89    local_use_map: &'a LocalUseMap,
90}
91
92struct DropData<'tcx> {
93    dropck_result: DropckOutlivesResult<'tcx>,
94    region_constraint_data: Option<&'tcx QueryRegionConstraints<'tcx>>,
95}
96
97struct LivenessResults<'a, 'typeck, 'b, 'tcx> {
98    cx: LivenessContext<'a, 'typeck, 'b, 'tcx>,
99
100    /// Set of points that define the current local.
101    defs: DenseBitSet<PointIndex>,
102
103    /// Points where the current variable is "use live" -- meaning
104    /// that there is a future "full use" that may use its value.
105    use_live_at: IntervalSet<PointIndex>,
106
107    /// Points where the current variable is "drop live" -- meaning
108    /// that there is no future "full use" that may use its value, but
109    /// there is a future drop.
110    drop_live_at: IntervalSet<PointIndex>,
111
112    /// Locations where drops may occur.
113    drop_locations: Vec<Location>,
114
115    /// Stack used when doing (reverse) DFS.
116    stack: Vec<PointIndex>,
117}
118
119impl<'a, 'typeck, 'b, 'tcx> LivenessResults<'a, 'typeck, 'b, 'tcx> {
120    fn new(cx: LivenessContext<'a, 'typeck, 'b, 'tcx>) -> Self {
121        let num_points = cx.location_map.num_points();
122        LivenessResults {
123            cx,
124            defs: DenseBitSet::new_empty(num_points),
125            use_live_at: IntervalSet::new(num_points),
126            drop_live_at: IntervalSet::new(num_points),
127            drop_locations: vec![],
128            stack: vec![],
129        }
130    }
131
132    fn compute_for_all_locals(&mut self, relevant_live_locals: Vec<Local>) {
133        for local in relevant_live_locals {
134            self.reset_local_state();
135            self.add_defs_for(local);
136            self.compute_use_live_points_for(local);
137            self.compute_drop_live_points_for(local);
138
139            let local_ty = self.cx.body().local_decls[local].ty;
140
141            if !self.use_live_at.is_empty() {
142                self.cx.add_use_live_facts_for(local_ty, &self.use_live_at);
143            }
144
145            if !self.drop_live_at.is_empty() {
146                self.cx.add_drop_live_facts_for(
147                    local,
148                    local_ty,
149                    &self.drop_locations,
150                    &self.drop_live_at,
151                );
152            }
153        }
154    }
155
156    /// Runs dropck for locals whose liveness isn't relevant. This is
157    /// necessary to eagerly detect unbound recursion during drop glue computation.
158    ///
159    /// These are all the locals which do not potentially reference a region local
160    /// to this body. Locals which only reference free regions are always drop-live
161    /// and can therefore safely be dropped.
162    fn dropck_boring_locals(&mut self, boring_locals: Vec<Local>) {
163        for local in boring_locals {
164            let local_ty = self.cx.body().local_decls[local].ty;
165            let local_span = self.cx.body().local_decls[local].source_info.span;
166            let drop_data = self.cx.drop_data.entry(local_ty).or_insert_with({
167                let typeck = &self.cx.typeck;
168                move || LivenessContext::compute_drop_data(typeck, local_ty, local_span)
169            });
170
171            drop_data.dropck_result.report_overflows(
172                self.cx.typeck.infcx.tcx,
173                self.cx.typeck.body.local_decls[local].source_info.span,
174                local_ty,
175            );
176        }
177    }
178
179    /// Add extra drop facts needed for Polonius.
180    ///
181    /// Add facts for all locals with free regions, since regions may outlive
182    /// the function body only at certain nodes in the CFG.
183    fn add_extra_drop_facts(&mut self, relevant_live_locals: &[Local]) {
184        // This collect is more necessary than immediately apparent
185        // because these facts go into `add_drop_live_facts_for()`,
186        // which also writes to `polonius_facts`, and so this is genuinely
187        // a simultaneous overlapping mutable borrow.
188        // FIXME for future hackers: investigate whether this is
189        // actually necessary; these facts come from Polonius
190        // and probably maybe plausibly does not need to go back in.
191        // It may be necessary to just pick out the parts of
192        // `add_drop_live_facts_for()` that make sense.
193        let Some(facts) = self.cx.typeck.polonius_facts.as_ref() else { return };
194        let facts_to_add: Vec<_> = {
195            let relevant_live_locals: FxIndexSet<_> =
196                relevant_live_locals.iter().copied().collect();
197
198            facts
199                .var_dropped_at
200                .iter()
201                .filter_map(|&(local, location_index)| {
202                    let local_ty = self.cx.body().local_decls[local].ty;
203                    if relevant_live_locals.contains(&local) || !local_ty.has_free_regions() {
204                        return None;
205                    }
206
207                    let location = self.cx.typeck.location_table.to_location(location_index);
208                    Some((local, local_ty, location))
209                })
210                .collect()
211        };
212
213        let live_at = IntervalSet::new(self.cx.location_map.num_points());
214        for (local, local_ty, location) in facts_to_add {
215            self.cx.add_drop_live_facts_for(local, local_ty, &[location], &live_at);
216        }
217    }
218
219    /// Clear the value of fields that are "per local variable".
220    fn reset_local_state(&mut self) {
221        self.defs.clear();
222        self.use_live_at.clear();
223        self.drop_live_at.clear();
224        self.drop_locations.clear();
225        assert!(self.stack.is_empty());
226    }
227
228    /// Adds the definitions of `local` into `self.defs`.
229    fn add_defs_for(&mut self, local: Local) {
230        for def in self.cx.local_use_map.defs(local) {
231            debug!("- defined at {:?}", def);
232            self.defs.insert(def);
233        }
234    }
235
236    /// Computes all points where local is "use live" -- meaning its
237    /// current value may be used later (except by a drop). This is
238    /// done by walking backwards from each use of `local` until we
239    /// find a `def` of local.
240    ///
241    /// Requires `add_defs_for(local)` to have been executed.
242    fn compute_use_live_points_for(&mut self, local: Local) {
243        debug!("compute_use_live_points_for(local={:?})", local);
244
245        self.stack.extend(self.cx.local_use_map.uses(local));
246        while let Some(p) = self.stack.pop() {
247            // We are live in this block from the closest to us of:
248            //
249            //  * Inclusively, the block start
250            //  * Exclusively, the previous definition (if it's in this block)
251            //  * Exclusively, the previous live_at setting (an optimization)
252            let block_start = self.cx.location_map.to_block_start(p);
253            let previous_defs = self.defs.last_set_in(block_start..=p);
254            let previous_live_at = self.use_live_at.last_set_in(block_start..=p);
255
256            let exclusive_start = match (previous_defs, previous_live_at) {
257                (Some(a), Some(b)) => Some(std::cmp::max(a, b)),
258                (Some(a), None) | (None, Some(a)) => Some(a),
259                (None, None) => None,
260            };
261
262            if let Some(exclusive) = exclusive_start {
263                self.use_live_at.insert_range(exclusive + 1..=p);
264
265                // If we have a bound after the start of the block, we should
266                // not add the predecessors for this block.
267                continue;
268            } else {
269                // Add all the elements of this block.
270                self.use_live_at.insert_range(block_start..=p);
271
272                // Then add the predecessors for this block, which are the
273                // terminators of predecessor basic blocks. Push those onto the
274                // stack so that the next iteration(s) will process them.
275
276                let block = self.cx.location_map.to_location(block_start).block;
277                self.stack.extend(
278                    self.cx.body().basic_blocks.predecessors()[block]
279                        .iter()
280                        .map(|&pred_bb| self.cx.body().terminator_loc(pred_bb))
281                        .map(|pred_loc| self.cx.location_map.point_from_location(pred_loc)),
282                );
283            }
284        }
285    }
286
287    /// Computes all points where local is "drop live" -- meaning its
288    /// current value may be dropped later (but not used). This is
289    /// done by iterating over the drops of `local` where `local` (or
290    /// some subpart of `local`) is initialized. For each such drop,
291    /// we walk backwards until we find a point where `local` is
292    /// either defined or use-live.
293    ///
294    /// Requires `compute_use_live_points_for` and `add_defs_for` to
295    /// have been executed.
296    fn compute_drop_live_points_for(&mut self, local: Local) {
297        debug!("compute_drop_live_points_for(local={:?})", local);
298
299        let Some(mpi) = self.cx.move_data.rev_lookup.find_local(local) else { return };
300        debug!("compute_drop_live_points_for: mpi = {:?}", mpi);
301
302        // Find the drops where `local` is initialized.
303        for drop_point in self.cx.local_use_map.drops(local) {
304            let location = self.cx.location_map.to_location(drop_point);
305            debug_assert_eq!(self.cx.body().terminator_loc(location.block), location,);
306
307            if self.cx.initialized_at_terminator(location.block, mpi)
308                && self.drop_live_at.insert(drop_point)
309            {
310                self.drop_locations.push(location);
311                self.stack.push(drop_point);
312            }
313        }
314
315        debug!("compute_drop_live_points_for: drop_locations={:?}", self.drop_locations);
316
317        // Reverse DFS. But for drops, we do it a bit differently.
318        // The stack only ever stores *terminators of blocks*. Within
319        // a block, we walk back the statements in an inner loop.
320        while let Some(term_point) = self.stack.pop() {
321            self.compute_drop_live_points_for_block(mpi, term_point);
322        }
323    }
324
325    /// Executes one iteration of the drop-live analysis loop.
326    ///
327    /// The parameter `mpi` is the `MovePathIndex` of the local variable
328    /// we are currently analyzing.
329    ///
330    /// The point `term_point` represents some terminator in the MIR,
331    /// where the local `mpi` is drop-live on entry to that terminator.
332    ///
333    /// This method adds all drop-live points within the block and --
334    /// where applicable -- pushes the terminators of preceding blocks
335    /// onto `self.stack`.
336    fn compute_drop_live_points_for_block(&mut self, mpi: MovePathIndex, term_point: PointIndex) {
337        debug!(
338            "compute_drop_live_points_for_block(mpi={:?}, term_point={:?})",
339            self.cx.move_data.move_paths[mpi].place,
340            self.cx.location_map.to_location(term_point),
341        );
342
343        // We are only invoked with terminators where `mpi` is
344        // drop-live on entry.
345        debug_assert!(self.drop_live_at.contains(term_point));
346
347        // Otherwise, scan backwards through the statements in the
348        // block. One of them may be either a definition or use
349        // live point.
350        let term_location = self.cx.location_map.to_location(term_point);
351        debug_assert_eq!(self.cx.body().terminator_loc(term_location.block), term_location,);
352        let block = term_location.block;
353        let entry_point = self.cx.location_map.entry_point(term_location.block);
354        for p in (entry_point..term_point).rev() {
355            debug!(
356                "compute_drop_live_points_for_block: p = {:?}",
357                self.cx.location_map.to_location(p)
358            );
359
360            if self.defs.contains(p) {
361                debug!("compute_drop_live_points_for_block: def site");
362                return;
363            }
364
365            if self.use_live_at.contains(p) {
366                debug!("compute_drop_live_points_for_block: use-live at {:?}", p);
367                return;
368            }
369
370            if !self.drop_live_at.insert(p) {
371                debug!("compute_drop_live_points_for_block: already drop-live");
372                return;
373            }
374        }
375
376        let body = self.cx.typeck.body;
377        for &pred_block in body.basic_blocks.predecessors()[block].iter() {
378            debug!("compute_drop_live_points_for_block: pred_block = {:?}", pred_block,);
379
380            // Check whether the variable is (at least partially)
381            // initialized at the exit of this predecessor. If so, we
382            // want to enqueue it on our list. If not, go check the
383            // next block.
384            //
385            // Note that we only need to check whether `live_local`
386            // became de-initialized at basic block boundaries. If it
387            // were to become de-initialized within the block, that
388            // would have been a "use-live" transition in the earlier
389            // loop, and we'd have returned already.
390            //
391            // NB. It's possible that the pred-block ends in a call
392            // which stores to the variable; in that case, the
393            // variable may be uninitialized "at exit" because this
394            // call only considers the *unconditional effects* of the
395            // terminator. *But*, in that case, the terminator is also
396            // a *definition* of the variable, in which case we want
397            // to stop the search anyhow. (But see Note 1 below.)
398            if !self.cx.initialized_at_exit(pred_block, mpi) {
399                debug!("compute_drop_live_points_for_block: not initialized");
400                continue;
401            }
402
403            let pred_term_loc = self.cx.body().terminator_loc(pred_block);
404            let pred_term_point = self.cx.location_map.point_from_location(pred_term_loc);
405
406            // If the terminator of this predecessor either *assigns*
407            // our value or is a "normal use", then stop.
408            if self.defs.contains(pred_term_point) {
409                debug!("compute_drop_live_points_for_block: defined at {:?}", pred_term_loc);
410                continue;
411            }
412
413            if self.use_live_at.contains(pred_term_point) {
414                debug!("compute_drop_live_points_for_block: use-live at {:?}", pred_term_loc);
415                continue;
416            }
417
418            // Otherwise, we are drop-live on entry to the terminator,
419            // so walk it.
420            if self.drop_live_at.insert(pred_term_point) {
421                debug!("compute_drop_live_points_for_block: pushed to stack");
422                self.stack.push(pred_term_point);
423            }
424        }
425
426        // Note 1. There is a weird scenario that you might imagine
427        // being problematic here, but which actually cannot happen.
428        // The problem would be if we had a variable that *is* initialized
429        // (but dead) on entry to the terminator, and where the current value
430        // will be dropped in the case of unwind. In that case, we ought to
431        // consider `X` to be drop-live in between the last use and call.
432        // Here is the example:
433        //
434        // ```
435        // BB0 {
436        //   X = ...
437        //   use(X); // last use
438        //   ...     // <-- X ought to be drop-live here
439        //   X = call() goto BB1 unwind BB2
440        // }
441        //
442        // BB1 {
443        //   DROP(X)
444        // }
445        //
446        // BB2 {
447        //   DROP(X)
448        // }
449        // ```
450        //
451        // However, the current code would, when walking back from BB2,
452        // simply stop and never explore BB0. This seems bad! But it turns
453        // out this code is flawed anyway -- note that the existing value of
454        // `X` would leak in the case where unwinding did *not* occur.
455        //
456        // What we *actually* generate is a store to a temporary
457        // for the call (`TMP = call()...`) and then a
458        // `Drop(X)` followed by `X = TMP`  to swap that with `X`.
459    }
460}
461
462impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> {
463    fn body(&self) -> &Body<'tcx> {
464        self.typeck.body
465    }
466    /// Returns `true` if the local variable (or some part of it) is initialized at the current
467    /// cursor position. Callers should call one of the `seek` methods immediately before to point
468    /// the cursor to the desired location.
469    fn initialized_at_curr_loc(&self, mpi: MovePathIndex) -> bool {
470        let state = self.flow_inits.get();
471        if state.contains(mpi) {
472            return true;
473        }
474
475        let move_paths = &self.flow_inits.analysis().move_data().move_paths;
476        move_paths[mpi].find_descendant(move_paths, |mpi| state.contains(mpi)).is_some()
477    }
478
479    /// Returns `true` if the local variable (or some part of it) is initialized in
480    /// the terminator of `block`. We need to check this to determine if a
481    /// DROP of some local variable will have an effect -- note that
482    /// drops, as they may unwind, are always terminators.
483    fn initialized_at_terminator(&mut self, block: BasicBlock, mpi: MovePathIndex) -> bool {
484        self.flow_inits.seek_before_primary_effect(self.body().terminator_loc(block));
485        self.initialized_at_curr_loc(mpi)
486    }
487
488    /// Returns `true` if the path `mpi` (or some part of it) is initialized at
489    /// the exit of `block`.
490    ///
491    /// **Warning:** Does not account for the result of `Call`
492    /// instructions.
493    fn initialized_at_exit(&mut self, block: BasicBlock, mpi: MovePathIndex) -> bool {
494        self.flow_inits.seek_after_primary_effect(self.body().terminator_loc(block));
495        self.initialized_at_curr_loc(mpi)
496    }
497
498    /// Stores the result that all regions in `value` are live for the
499    /// points `live_at`.
500    fn add_use_live_facts_for(&mut self, value: Ty<'tcx>, live_at: &IntervalSet<PointIndex>) {
501        debug!("add_use_live_facts_for(value={:?})", value);
502        Self::make_all_regions_live(self.location_map, self.typeck, value, live_at);
503    }
504
505    /// Some variable with type `live_ty` is "drop live" at `location`
506    /// -- i.e., it may be dropped later. This means that *some* of
507    /// the regions in its type must be live at `location`. The
508    /// precise set will depend on the dropck constraints, and in
509    /// particular this takes `#[may_dangle]` into account.
510    fn add_drop_live_facts_for(
511        &mut self,
512        dropped_local: Local,
513        dropped_ty: Ty<'tcx>,
514        drop_locations: &[Location],
515        live_at: &IntervalSet<PointIndex>,
516    ) {
517        debug!(
518            "add_drop_live_constraint(\
519             dropped_local={:?}, \
520             dropped_ty={:?}, \
521             drop_locations={:?}, \
522             live_at={:?})",
523            dropped_local,
524            dropped_ty,
525            drop_locations,
526            values::pretty_print_points(self.location_map, live_at.iter()),
527        );
528
529        let local_span = self.body().local_decls()[dropped_local].source_info.span;
530        let drop_data = self.drop_data.entry(dropped_ty).or_insert_with({
531            let typeck = &self.typeck;
532            move || Self::compute_drop_data(typeck, dropped_ty, local_span)
533        });
534
535        if let Some(data) = &drop_data.region_constraint_data {
536            for &drop_location in drop_locations {
537                self.typeck.push_region_constraints(
538                    drop_location.to_locations(),
539                    ConstraintCategory::Boring,
540                    data,
541                );
542            }
543        }
544
545        drop_data.dropck_result.report_overflows(
546            self.typeck.infcx.tcx,
547            self.typeck.body.source_info(*drop_locations.first().unwrap()).span,
548            dropped_ty,
549        );
550
551        // All things in the `outlives` array may be touched by
552        // the destructor and must be live at this point.
553        for &kind in &drop_data.dropck_result.kinds {
554            Self::make_all_regions_live(self.location_map, self.typeck, kind, live_at);
555            polonius::legacy::emit_drop_facts(
556                self.typeck.tcx(),
557                dropped_local,
558                &kind,
559                self.typeck.universal_regions,
560                self.typeck.polonius_facts,
561            );
562        }
563    }
564
565    fn make_all_regions_live(
566        location_map: &DenseLocationMap,
567        typeck: &mut TypeChecker<'_, 'tcx>,
568        value: impl TypeVisitable<TyCtxt<'tcx>> + Relate<TyCtxt<'tcx>>,
569        live_at: &IntervalSet<PointIndex>,
570    ) {
571        debug!("make_all_regions_live(value={:?})", value);
572        debug!(
573            "make_all_regions_live: live_at={}",
574            values::pretty_print_points(location_map, live_at.iter()),
575        );
576
577        value.visit_with(&mut for_liveness::FreeRegionsVisitor {
578            tcx: typeck.tcx(),
579            param_env: typeck.infcx.param_env,
580            op: |r| {
581                let live_region_vid = typeck.universal_regions.to_region_vid(r);
582
583                typeck.constraints.liveness_constraints.add_points(live_region_vid, live_at);
584            },
585        });
586
587        // When using `-Zpolonius=next`, we record the variance of each live region.
588        if let Some(polonius_liveness) = typeck.polonius_liveness.as_mut() {
589            polonius_liveness.record_live_region_variance(
590                typeck.infcx.tcx,
591                typeck.universal_regions,
592                value,
593            );
594        }
595    }
596
597    fn compute_drop_data(
598        typeck: &TypeChecker<'_, 'tcx>,
599        dropped_ty: Ty<'tcx>,
600        span: Span,
601    ) -> DropData<'tcx> {
602        debug!("compute_drop_data(dropped_ty={:?})", dropped_ty);
603
604        let op = typeck.infcx.param_env.and(DropckOutlives { dropped_ty });
605
606        match op.fully_perform(typeck.infcx, DUMMY_SP) {
607            Ok(TypeOpOutput { output, constraints, .. }) => {
608                DropData { dropck_result: output, region_constraint_data: constraints }
609            }
610            Err(ErrorGuaranteed { .. }) => {
611                // We don't run dropck on HIR, and dropck looks inside fields of
612                // types, so there's no guarantee that it succeeds. We also
613                // can't rely on the `ErrorGuaranteed` from `fully_perform` here
614                // because it comes from delay_span_bug.
615                //
616                // Do this inside of a probe because we don't particularly care (or want)
617                // any region side-effects of this operation in our infcx.
618                typeck.infcx.probe(|_| {
619                    let ocx = ObligationCtxt::new_with_diagnostics(&typeck.infcx);
620                    let errors = match dropck_outlives::compute_dropck_outlives_with_errors(
621                        &ocx, op, span,
622                    ) {
623                        Ok(_) => ocx.select_all_or_error(),
624                        Err(e) => {
625                            if e.is_empty() {
626                                ocx.select_all_or_error()
627                            } else {
628                                e
629                            }
630                        }
631                    };
632
633                    // Could have no errors if a type lowering error, say, caused the query
634                    // to fail.
635                    if !errors.is_empty() {
636                        typeck.infcx.err_ctxt().report_fulfillment_errors(errors);
637                    }
638                });
639                DropData { dropck_result: Default::default(), region_constraint_data: None }
640            }
641        }
642    }
643}