rustc_borrowck/diagnostics/
explain_borrow.rs

1//! Print diagnostics to explain why values are borrowed.
2
3#![allow(rustc::diagnostic_outside_of_impl)]
4#![allow(rustc::untranslatable_diagnostic)]
5
6use std::assert_matches::assert_matches;
7
8use rustc_errors::{Applicability, Diag, EmissionGuarantee};
9use rustc_hir as hir;
10use rustc_hir::intravisit::Visitor;
11use rustc_infer::infer::NllRegionVariableOrigin;
12use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault;
13use rustc_middle::mir::{
14    Body, CallSource, CastKind, ConstraintCategory, FakeReadCause, Local, LocalInfo, Location,
15    Operand, Place, Rvalue, Statement, StatementKind, TerminatorKind,
16};
17use rustc_middle::ty::adjustment::PointerCoercion;
18use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt};
19use rustc_span::{DesugaringKind, Span, kw, sym};
20use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
21use rustc_trait_selection::error_reporting::traits::call_kind::CallKind;
22use tracing::{debug, instrument};
23
24use super::{RegionName, UseSpans, find_use};
25use crate::borrow_set::BorrowData;
26use crate::constraints::OutlivesConstraint;
27use crate::nll::ConstraintDescription;
28use crate::region_infer::{BlameConstraint, Cause};
29use crate::{MirBorrowckCtxt, WriteKind};
30
31#[derive(Debug)]
32pub(crate) enum BorrowExplanation<'tcx> {
33    UsedLater(Local, LaterUseKind, Span, Option<Span>),
34    UsedLaterInLoop(LaterUseKind, Span, Option<Span>),
35    UsedLaterWhenDropped {
36        drop_loc: Location,
37        dropped_local: Local,
38        should_note_order: bool,
39    },
40    MustBeValidFor {
41        category: ConstraintCategory<'tcx>,
42        from_closure: bool,
43        span: Span,
44        region_name: RegionName,
45        opt_place_desc: Option<String>,
46        path: Vec<OutlivesConstraint<'tcx>>,
47    },
48    Unexplained,
49}
50
51#[derive(Clone, Copy, Debug)]
52pub(crate) enum LaterUseKind {
53    TraitCapture,
54    ClosureCapture,
55    Call,
56    FakeLetRead,
57    Other,
58}
59
60impl<'tcx> BorrowExplanation<'tcx> {
61    pub(crate) fn is_explained(&self) -> bool {
62        !matches!(self, BorrowExplanation::Unexplained)
63    }
64    pub(crate) fn add_explanation_to_diagnostic<G: EmissionGuarantee>(
65        &self,
66        cx: &MirBorrowckCtxt<'_, '_, 'tcx>,
67        err: &mut Diag<'_, G>,
68        borrow_desc: &str,
69        borrow_span: Option<Span>,
70        multiple_borrow_span: Option<(Span, Span)>,
71    ) {
72        let tcx = cx.infcx.tcx;
73        let body = cx.body;
74        let local_names = &cx.local_names;
75
76        if let Some(span) = borrow_span {
77            let def_id = body.source.def_id();
78            if let Some(node) = tcx.hir_get_if_local(def_id)
79                && let Some(body_id) = node.body_id()
80            {
81                let body = tcx.hir_body(body_id);
82                let mut expr_finder = FindExprBySpan::new(span, tcx);
83                expr_finder.visit_expr(body.value);
84                if let Some(mut expr) = expr_finder.result {
85                    while let hir::ExprKind::AddrOf(_, _, inner)
86                    | hir::ExprKind::Unary(hir::UnOp::Deref, inner)
87                    | hir::ExprKind::Field(inner, _)
88                    | hir::ExprKind::MethodCall(_, inner, _, _)
89                    | hir::ExprKind::Index(inner, _, _) = &expr.kind
90                    {
91                        expr = inner;
92                    }
93                    if let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind
94                        && let [hir::PathSegment { ident, args: None, .. }] = p.segments
95                        && let hir::def::Res::Local(hir_id) = p.res
96                        && let hir::Node::Pat(pat) = tcx.hir_node(hir_id)
97                    {
98                        if !ident.span.in_external_macro(tcx.sess.source_map()) {
99                            err.span_label(pat.span, format!("binding `{ident}` declared here"));
100                        }
101                    }
102                }
103            }
104        }
105        match *self {
106            BorrowExplanation::UsedLater(
107                dropped_local,
108                later_use_kind,
109                var_or_use_span,
110                path_span,
111            ) => {
112                let message = match later_use_kind {
113                    LaterUseKind::TraitCapture => "captured here by trait object",
114                    LaterUseKind::ClosureCapture => "captured here by closure",
115                    LaterUseKind::Call => "used by call",
116                    LaterUseKind::FakeLetRead => "stored here",
117                    LaterUseKind::Other => "used here",
118                };
119                let local_decl = &body.local_decls[dropped_local];
120
121                if let &LocalInfo::IfThenRescopeTemp { if_then } = local_decl.local_info()
122                    && let Some((_, hir::Node::Expr(expr))) = tcx.hir_parent_iter(if_then).next()
123                    && let hir::ExprKind::If(cond, conseq, alt) = expr.kind
124                    && let hir::ExprKind::Let(&hir::LetExpr {
125                        span: _,
126                        pat,
127                        init,
128                        // FIXME(#101728): enable rewrite when type ascription is stabilized again
129                        ty: None,
130                        recovered: _,
131                    }) = cond.kind
132                    && pat.span.can_be_used_for_suggestions()
133                    && let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
134                {
135                    suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
136                } else if path_span.is_none_or(|path_span| path_span == var_or_use_span) {
137                    // We can use `var_or_use_span` if either `path_span` is not present, or both
138                    // spans are the same.
139                    if borrow_span.is_none_or(|sp| !sp.overlaps(var_or_use_span)) {
140                        err.span_label(
141                            var_or_use_span,
142                            format!("{borrow_desc}borrow later {message}"),
143                        );
144                    }
145                } else {
146                    // path_span must be `Some` as otherwise the if condition is true
147                    let path_span = path_span.unwrap();
148                    // path_span is only present in the case of closure capture
149                    assert_matches!(later_use_kind, LaterUseKind::ClosureCapture);
150                    if !borrow_span.is_some_and(|sp| sp.overlaps(var_or_use_span)) {
151                        let path_label = "used here by closure";
152                        let capture_kind_label = message;
153                        err.span_label(
154                            var_or_use_span,
155                            format!("{borrow_desc}borrow later {capture_kind_label}"),
156                        );
157                        err.span_label(path_span, path_label);
158                    }
159                }
160            }
161            BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span) => {
162                let message = match later_use_kind {
163                    LaterUseKind::TraitCapture => {
164                        "borrow captured here by trait object, in later iteration of loop"
165                    }
166                    LaterUseKind::ClosureCapture => {
167                        "borrow captured here by closure, in later iteration of loop"
168                    }
169                    LaterUseKind::Call => "borrow used by call, in later iteration of loop",
170                    LaterUseKind::FakeLetRead => "borrow later stored here",
171                    LaterUseKind::Other => "borrow used here, in later iteration of loop",
172                };
173                // We can use `var_or_use_span` if either `path_span` is not present, or both spans
174                // are the same.
175                if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) {
176                    err.span_label(var_or_use_span, format!("{borrow_desc}{message}"));
177                } else {
178                    // path_span must be `Some` as otherwise the if condition is true
179                    let path_span = path_span.unwrap();
180                    // path_span is only present in the case of closure capture
181                    assert_matches!(later_use_kind, LaterUseKind::ClosureCapture);
182                    if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) {
183                        let path_label = "used here by closure";
184                        let capture_kind_label = message;
185                        err.span_label(
186                            var_or_use_span,
187                            format!("{borrow_desc}borrow later {capture_kind_label}"),
188                        );
189                        err.span_label(path_span, path_label);
190                    }
191                }
192            }
193            BorrowExplanation::UsedLaterWhenDropped {
194                drop_loc,
195                dropped_local,
196                should_note_order,
197            } => {
198                let local_decl = &body.local_decls[dropped_local];
199                let mut ty = local_decl.ty;
200                if local_decl.source_info.span.desugaring_kind() == Some(DesugaringKind::ForLoop) {
201                    if let ty::Adt(adt, args) = local_decl.ty.kind() {
202                        if tcx.is_diagnostic_item(sym::Option, adt.did()) {
203                            // in for loop desugaring, only look at the `Some(..)` inner type
204                            ty = args.type_at(0);
205                        }
206                    }
207                }
208                let (dtor_desc, type_desc) = match ty.kind() {
209                    // If type is an ADT that implements Drop, then
210                    // simplify output by reporting just the ADT name.
211                    ty::Adt(adt, _args) if adt.has_dtor(tcx) && !adt.is_box() => {
212                        ("`Drop` code", format!("type `{}`", tcx.def_path_str(adt.did())))
213                    }
214
215                    // Otherwise, just report the whole type (and use
216                    // the intentionally fuzzy phrase "destructor")
217                    ty::Closure(..) => ("destructor", "closure".to_owned()),
218                    ty::Coroutine(..) => ("destructor", "coroutine".to_owned()),
219
220                    _ => ("destructor", format!("type `{}`", local_decl.ty)),
221                };
222
223                match local_names[dropped_local] {
224                    Some(local_name) if !local_decl.from_compiler_desugaring() => {
225                        let message = format!(
226                            "{borrow_desc}borrow might be used here, when `{local_name}` is dropped \
227                             and runs the {dtor_desc} for {type_desc}",
228                        );
229                        err.span_label(body.source_info(drop_loc).span, message);
230
231                        if should_note_order {
232                            err.note(
233                                "values in a scope are dropped \
234                                 in the opposite order they are defined",
235                            );
236                        }
237                    }
238                    _ => {
239                        err.span_label(
240                            local_decl.source_info.span,
241                            format!(
242                                "a temporary with access to the {borrow_desc}borrow \
243                                 is created here ...",
244                            ),
245                        );
246                        let message = format!(
247                            "... and the {borrow_desc}borrow might be used here, \
248                             when that temporary is dropped \
249                             and runs the {dtor_desc} for {type_desc}",
250                        );
251                        err.span_label(body.source_info(drop_loc).span, message);
252
253                        struct FindLetExpr<'hir> {
254                            span: Span,
255                            result: Option<(Span, &'hir hir::Pat<'hir>, &'hir hir::Expr<'hir>)>,
256                            tcx: TyCtxt<'hir>,
257                        }
258
259                        impl<'hir> rustc_hir::intravisit::Visitor<'hir> for FindLetExpr<'hir> {
260                            type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;
261                            fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
262                                self.tcx
263                            }
264                            fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
265                                if let hir::ExprKind::If(cond, _conseq, _alt)
266                                | hir::ExprKind::Loop(
267                                    &hir::Block {
268                                        expr:
269                                            Some(&hir::Expr {
270                                                kind: hir::ExprKind::If(cond, _conseq, _alt),
271                                                ..
272                                            }),
273                                        ..
274                                    },
275                                    _,
276                                    hir::LoopSource::While,
277                                    _,
278                                ) = expr.kind
279                                    && let hir::ExprKind::Let(hir::LetExpr {
280                                        init: let_expr_init,
281                                        span: let_expr_span,
282                                        pat: let_expr_pat,
283                                        ..
284                                    }) = cond.kind
285                                    && let_expr_init.span.contains(self.span)
286                                {
287                                    self.result =
288                                        Some((*let_expr_span, let_expr_pat, let_expr_init))
289                                } else {
290                                    hir::intravisit::walk_expr(self, expr);
291                                }
292                            }
293                        }
294
295                        if let &LocalInfo::IfThenRescopeTemp { if_then } = local_decl.local_info()
296                            && let hir::Node::Expr(expr) = tcx.hir_node(if_then)
297                            && let hir::ExprKind::If(cond, conseq, alt) = expr.kind
298                            && let hir::ExprKind::Let(&hir::LetExpr {
299                                span: _,
300                                pat,
301                                init,
302                                // FIXME(#101728): enable rewrite when type ascription is
303                                // stabilized again.
304                                ty: None,
305                                recovered: _,
306                            }) = cond.kind
307                            && pat.span.can_be_used_for_suggestions()
308                            && let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
309                        {
310                            suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
311                        } else if let Some((old, new)) = multiple_borrow_span
312                            && let def_id = body.source.def_id()
313                            && let Some(node) = tcx.hir_get_if_local(def_id)
314                            && let Some(body_id) = node.body_id()
315                            && let hir_body = tcx.hir_body(body_id)
316                            && let mut expr_finder = (FindLetExpr { span: old, result: None, tcx })
317                            && let Some((let_expr_span, let_expr_pat, let_expr_init)) = {
318                                expr_finder.visit_expr(hir_body.value);
319                                expr_finder.result
320                            }
321                            && !let_expr_span.contains(new)
322                        {
323                            // #133941: The `old` expression is at the conditional part of an
324                            // if/while let expression. Adding a semicolon won't work.
325                            // Instead, try suggesting the `matches!` macro or a temporary.
326                            if let_expr_pat
327                                .walk_short(|pat| !matches!(pat.kind, hir::PatKind::Binding(..)))
328                            {
329                                if let Ok(pat_snippet) =
330                                    tcx.sess.source_map().span_to_snippet(let_expr_pat.span)
331                                    && let Ok(init_snippet) =
332                                        tcx.sess.source_map().span_to_snippet(let_expr_init.span)
333                                {
334                                    err.span_suggestion_verbose(
335                                        let_expr_span,
336                                        "consider using the `matches!` macro",
337                                        format!("matches!({init_snippet}, {pat_snippet})"),
338                                        Applicability::MaybeIncorrect,
339                                    );
340                                } else {
341                                    err.note("consider using the `matches!` macro");
342                                }
343                            }
344                        } else if let LocalInfo::BlockTailTemp(info) = local_decl.local_info() {
345                            let sp = info.span.find_oldest_ancestor_in_same_ctxt();
346                            if info.tail_result_is_ignored {
347                                // #85581: If the first mutable borrow's scope contains
348                                // the second borrow, this suggestion isn't helpful.
349                                if !multiple_borrow_span.is_some_and(|(old, new)| {
350                                    old.to(info.span.shrink_to_hi()).contains(new)
351                                }) {
352                                    err.span_suggestion_verbose(
353                                        sp.shrink_to_hi(),
354                                        "consider adding semicolon after the expression so its \
355                                        temporaries are dropped sooner, before the local variables \
356                                        declared by the block are dropped",
357                                        ";",
358                                        Applicability::MaybeIncorrect,
359                                    );
360                                }
361                            } else {
362                                err.note(
363                                    "the temporary is part of an expression at the end of a \
364                                     block;\nconsider forcing this temporary to be dropped sooner, \
365                                     before the block's local variables are dropped",
366                                );
367                                err.multipart_suggestion(
368                                    "for example, you could save the expression's value in a new \
369                                     local variable `x` and then make `x` be the expression at the \
370                                     end of the block",
371                                    vec![
372                                        (sp.shrink_to_lo(), "let x = ".to_string()),
373                                        (sp.shrink_to_hi(), "; x".to_string()),
374                                    ],
375                                    Applicability::MaybeIncorrect,
376                                );
377                            };
378                        }
379                    }
380                }
381            }
382            BorrowExplanation::MustBeValidFor {
383                category,
384                span,
385                ref region_name,
386                ref opt_place_desc,
387                from_closure: _,
388                ref path,
389            } => {
390                region_name.highlight_region_name(err);
391
392                if let Some(desc) = opt_place_desc {
393                    err.span_label(
394                        span,
395                        format!(
396                            "{}requires that `{desc}` is borrowed for `{region_name}`",
397                            category.description(),
398                        ),
399                    );
400                } else {
401                    err.span_label(
402                        span,
403                        format!(
404                            "{}requires that {borrow_desc}borrow lasts for `{region_name}`",
405                            category.description(),
406                        ),
407                    );
408                };
409
410                cx.add_placeholder_from_predicate_note(err, &path);
411                cx.add_sized_or_copy_bound_info(err, category, &path);
412
413                if let ConstraintCategory::Cast {
414                    is_implicit_coercion: true,
415                    unsize_to: Some(unsize_ty),
416                } = category
417                {
418                    self.add_object_lifetime_default_note(tcx, err, unsize_ty);
419                }
420                self.add_lifetime_bound_suggestion_to_diagnostic(err, &category, span, region_name);
421            }
422            _ => {}
423        }
424    }
425
426    fn add_object_lifetime_default_note<G: EmissionGuarantee>(
427        &self,
428        tcx: TyCtxt<'tcx>,
429        err: &mut Diag<'_, G>,
430        unsize_ty: Ty<'tcx>,
431    ) {
432        if let ty::Adt(def, args) = unsize_ty.kind() {
433            // We try to elaborate the object lifetime defaults and present those to the user. This
434            // should make it clear where the region constraint is coming from.
435            let generics = tcx.generics_of(def.did());
436
437            let mut has_dyn = false;
438            let mut failed = false;
439
440            let elaborated_args =
441                std::iter::zip(*args, &generics.own_params).map(|(arg, param)| {
442                    if let Some(ty::Dynamic(obj, _, ty::Dyn)) = arg.as_type().map(Ty::kind) {
443                        let default = tcx.object_lifetime_default(param.def_id);
444
445                        let re_static = tcx.lifetimes.re_static;
446
447                        let implied_region = match default {
448                            // This is not entirely precise.
449                            ObjectLifetimeDefault::Empty => re_static,
450                            ObjectLifetimeDefault::Ambiguous => {
451                                failed = true;
452                                re_static
453                            }
454                            ObjectLifetimeDefault::Param(param_def_id) => {
455                                let index = generics.param_def_id_to_index[&param_def_id] as usize;
456                                args.get(index).and_then(|arg| arg.as_region()).unwrap_or_else(
457                                    || {
458                                        failed = true;
459                                        re_static
460                                    },
461                                )
462                            }
463                            ObjectLifetimeDefault::Static => re_static,
464                        };
465
466                        has_dyn = true;
467
468                        Ty::new_dynamic(tcx, obj, implied_region, ty::Dyn).into()
469                    } else {
470                        arg
471                    }
472                });
473            let elaborated_ty = Ty::new_adt(tcx, *def, tcx.mk_args_from_iter(elaborated_args));
474
475            if has_dyn && !failed {
476                err.note(format!(
477                    "due to object lifetime defaults, `{unsize_ty}` actually means `{elaborated_ty}`"
478                ));
479            }
480        }
481    }
482
483    fn add_lifetime_bound_suggestion_to_diagnostic<G: EmissionGuarantee>(
484        &self,
485        err: &mut Diag<'_, G>,
486        category: &ConstraintCategory<'tcx>,
487        span: Span,
488        region_name: &RegionName,
489    ) {
490        if !span.is_desugaring(DesugaringKind::OpaqueTy) {
491            return;
492        }
493        if let ConstraintCategory::OpaqueType = category {
494            let suggestable_name =
495                if region_name.was_named() { region_name.name } else { kw::UnderscoreLifetime };
496
497            let msg = format!(
498                "you can add a bound to the {}to make it last less than `'static` and match `{region_name}`",
499                category.description(),
500            );
501
502            err.span_suggestion_verbose(
503                span.shrink_to_hi(),
504                msg,
505                format!(" + {suggestable_name}"),
506                Applicability::Unspecified,
507            );
508        }
509    }
510}
511
512fn suggest_rewrite_if_let<G: EmissionGuarantee>(
513    tcx: TyCtxt<'_>,
514    expr: &hir::Expr<'_>,
515    pat: &str,
516    init: &hir::Expr<'_>,
517    conseq: &hir::Expr<'_>,
518    alt: Option<&hir::Expr<'_>>,
519    err: &mut Diag<'_, G>,
520) {
521    let source_map = tcx.sess.source_map();
522    err.span_note(
523        source_map.end_point(conseq.span),
524        "lifetimes for temporaries generated in `if let`s have been shortened in Edition 2024 so that they are dropped here instead",
525    );
526    if expr.span.can_be_used_for_suggestions() && conseq.span.can_be_used_for_suggestions() {
527        let needs_block = if let Some(hir::Node::Expr(expr)) =
528            alt.and_then(|alt| tcx.hir_parent_iter(alt.hir_id).next()).map(|(_, node)| node)
529        {
530            matches!(expr.kind, hir::ExprKind::If(..))
531        } else {
532            false
533        };
534        let mut sugg = vec![
535            (
536                expr.span.shrink_to_lo().between(init.span),
537                if needs_block { "{ match ".into() } else { "match ".into() },
538            ),
539            (conseq.span.shrink_to_lo(), format!(" {{ {pat} => ")),
540        ];
541        let expr_end = expr.span.shrink_to_hi();
542        let mut expr_end_code;
543        if let Some(alt) = alt {
544            sugg.push((conseq.span.between(alt.span), " _ => ".into()));
545            expr_end_code = "}".to_string();
546        } else {
547            expr_end_code = " _ => {} }".into();
548        }
549        expr_end_code.push('}');
550        sugg.push((expr_end, expr_end_code));
551        err.multipart_suggestion(
552            "consider rewriting the `if` into `match` which preserves the extended lifetime",
553            sugg,
554            Applicability::MaybeIncorrect,
555        );
556    }
557}
558
559impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
560    fn free_region_constraint_info(
561        &self,
562        borrow_region: RegionVid,
563        outlived_region: RegionVid,
564    ) -> (ConstraintCategory<'tcx>, bool, Span, Option<RegionName>, Vec<OutlivesConstraint<'tcx>>)
565    {
566        let (blame_constraint, path) = self.regioncx.best_blame_constraint(
567            borrow_region,
568            NllRegionVariableOrigin::FreeRegion,
569            |r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
570        );
571        let BlameConstraint { category, from_closure, cause, .. } = blame_constraint;
572
573        let outlived_fr_name = self.give_region_a_name(outlived_region);
574
575        (category, from_closure, cause.span, outlived_fr_name, path)
576    }
577
578    /// Returns structured explanation for *why* the borrow contains the
579    /// point from `location`. This is key for the "3-point errors"
580    /// [described in the NLL RFC][d].
581    ///
582    /// # Parameters
583    ///
584    /// - `borrow`: the borrow in question
585    /// - `location`: where the borrow occurs
586    /// - `kind_place`: if Some, this describes the statement that triggered the error.
587    ///   - first half is the kind of write, if any, being performed
588    ///   - second half is the place being accessed
589    ///
590    /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
591    #[instrument(level = "debug", skip(self))]
592    pub(crate) fn explain_why_borrow_contains_point(
593        &self,
594        location: Location,
595        borrow: &BorrowData<'tcx>,
596        kind_place: Option<(WriteKind, Place<'tcx>)>,
597    ) -> BorrowExplanation<'tcx> {
598        let regioncx = &self.regioncx;
599        let body: &Body<'_> = self.body;
600        let tcx = self.infcx.tcx;
601
602        let borrow_region_vid = borrow.region;
603        debug!(?borrow_region_vid);
604
605        let mut region_sub = self.regioncx.find_sub_region_live_at(borrow_region_vid, location);
606        debug!(?region_sub);
607
608        let mut use_location = location;
609        let mut use_in_later_iteration_of_loop = false;
610
611        if region_sub == borrow_region_vid {
612            // When `region_sub` is the same as `borrow_region_vid` (the location where the borrow
613            // is issued is the same location that invalidates the reference), this is likely a
614            // loop iteration. In this case, try using the loop terminator location in
615            // `find_sub_region_live_at`.
616            if let Some(loop_terminator_location) =
617                regioncx.find_loop_terminator_location(borrow.region, body)
618            {
619                region_sub = self
620                    .regioncx
621                    .find_sub_region_live_at(borrow_region_vid, loop_terminator_location);
622                debug!("explain_why_borrow_contains_point: region_sub in loop={:?}", region_sub);
623                use_location = loop_terminator_location;
624                use_in_later_iteration_of_loop = true;
625            }
626        }
627
628        // NLL doesn't consider boring locals for liveness, and wouldn't encounter a
629        // `Cause::LiveVar` for such a local. Polonius can't avoid computing liveness for boring
630        // locals yet, and will encounter them when trying to explain why a borrow contains a given
631        // point.
632        //
633        // We want to focus on relevant live locals in diagnostics, so when polonius is enabled, we
634        // ensure that we don't emit live boring locals as explanations.
635        let is_local_boring = |local| {
636            if let Some(polonius_diagnostics) = self.polonius_diagnostics {
637                polonius_diagnostics.boring_nll_locals.contains(&local)
638            } else {
639                assert!(!tcx.sess.opts.unstable_opts.polonius.is_next_enabled());
640
641                // Boring locals are never the cause of a borrow explanation in NLLs.
642                false
643            }
644        };
645        match find_use::find(body, regioncx, tcx, region_sub, use_location) {
646            Some(Cause::LiveVar(local, location)) if !is_local_boring(local) => {
647                let span = body.source_info(location).span;
648                let spans = self
649                    .move_spans(Place::from(local).as_ref(), location)
650                    .or_else(|| self.borrow_spans(span, location));
651
652                if use_in_later_iteration_of_loop {
653                    let (later_use_kind, var_or_use_span, path_span) =
654                        self.later_use_kind(borrow, spans, use_location);
655                    BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span)
656                } else {
657                    // Check if the location represents a `FakeRead`, and adapt the error
658                    // message to the `FakeReadCause` it is from: in particular,
659                    // the ones inserted in optimized `let var = <expr>` patterns.
660                    let (later_use_kind, var_or_use_span, path_span) =
661                        self.later_use_kind(borrow, spans, location);
662                    BorrowExplanation::UsedLater(
663                        borrow.borrowed_place.local,
664                        later_use_kind,
665                        var_or_use_span,
666                        path_span,
667                    )
668                }
669            }
670
671            Some(Cause::DropVar(local, location)) => {
672                let mut should_note_order = false;
673                if self.local_names[local].is_some()
674                    && let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place
675                    && let Some(borrowed_local) = place.as_local()
676                    && self.local_names[borrowed_local].is_some()
677                    && local != borrowed_local
678                {
679                    should_note_order = true;
680                }
681
682                BorrowExplanation::UsedLaterWhenDropped {
683                    drop_loc: location,
684                    dropped_local: local,
685                    should_note_order,
686                }
687            }
688
689            Some(Cause::LiveVar(..)) | None => {
690                // Here, under NLL: no cause was found. Under polonius: no cause was found, or a
691                // boring local was found, which we ignore like NLLs do to match its diagnostics.
692                if let Some(region) = self.to_error_region_vid(borrow_region_vid) {
693                    let (category, from_closure, span, region_name, path) =
694                        self.free_region_constraint_info(borrow_region_vid, region);
695                    if let Some(region_name) = region_name {
696                        let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref());
697                        BorrowExplanation::MustBeValidFor {
698                            category,
699                            from_closure,
700                            span,
701                            region_name,
702                            opt_place_desc,
703                            path,
704                        }
705                    } else {
706                        debug!("Could not generate a region name");
707                        BorrowExplanation::Unexplained
708                    }
709                } else {
710                    debug!("Could not generate an error region vid");
711                    BorrowExplanation::Unexplained
712                }
713            }
714        }
715    }
716
717    /// Determine how the borrow was later used.
718    /// First span returned points to the location of the conflicting use
719    /// Second span if `Some` is returned in the case of closures and points
720    /// to the use of the path
721    #[instrument(level = "debug", skip(self))]
722    fn later_use_kind(
723        &self,
724        borrow: &BorrowData<'tcx>,
725        use_spans: UseSpans<'tcx>,
726        location: Location,
727    ) -> (LaterUseKind, Span, Option<Span>) {
728        match use_spans {
729            UseSpans::ClosureUse { capture_kind_span, path_span, .. } => {
730                // Used in a closure.
731                (LaterUseKind::ClosureCapture, capture_kind_span, Some(path_span))
732            }
733            // In the case that the borrowed value (probably a temporary)
734            // overlaps with the method's receiver, then point at the method.
735            UseSpans::FnSelfUse {
736                var_span: span,
737                kind: CallKind::Normal { desugaring: None, .. },
738                ..
739            } if span
740                .overlaps(self.body.local_decls[borrow.assigned_place.local].source_info.span) =>
741            {
742                if let TerminatorKind::Call { func, call_source: CallSource::Normal, .. } =
743                    &self.body.basic_blocks[location.block].terminator().kind
744                {
745                    // Just point to the function, to reduce the chance of overlapping spans.
746                    let function_span = match func {
747                        Operand::Constant(c) => c.span,
748                        Operand::Copy(place) | Operand::Move(place) => {
749                            if let Some(l) = place.as_local() {
750                                let local_decl = &self.body.local_decls[l];
751                                if self.local_names[l].is_none() {
752                                    local_decl.source_info.span
753                                } else {
754                                    span
755                                }
756                            } else {
757                                span
758                            }
759                        }
760                    };
761                    (LaterUseKind::Call, function_span, None)
762                } else {
763                    (LaterUseKind::Other, span, None)
764                }
765            }
766            UseSpans::PatUse(span)
767            | UseSpans::OtherUse(span)
768            | UseSpans::FnSelfUse { var_span: span, .. } => {
769                let block = &self.body.basic_blocks[location.block];
770
771                let kind = if let Some(&Statement {
772                    kind: StatementKind::FakeRead(box (FakeReadCause::ForLet(_), place)),
773                    ..
774                }) = block.statements.get(location.statement_index)
775                {
776                    if let Some(l) = place.as_local()
777                        && let local_decl = &self.body.local_decls[l]
778                        && local_decl.ty.is_closure()
779                    {
780                        LaterUseKind::ClosureCapture
781                    } else {
782                        LaterUseKind::FakeLetRead
783                    }
784                } else if self.was_captured_by_trait_object(borrow) {
785                    LaterUseKind::TraitCapture
786                } else if location.statement_index == block.statements.len() {
787                    if let TerminatorKind::Call { func, call_source: CallSource::Normal, .. } =
788                        &block.terminator().kind
789                    {
790                        // Just point to the function, to reduce the chance of overlapping spans.
791                        let function_span = match func {
792                            Operand::Constant(c) => c.span,
793                            Operand::Copy(place) | Operand::Move(place) => {
794                                if let Some(l) = place.as_local() {
795                                    let local_decl = &self.body.local_decls[l];
796                                    if self.local_names[l].is_none() {
797                                        local_decl.source_info.span
798                                    } else {
799                                        span
800                                    }
801                                } else {
802                                    span
803                                }
804                            }
805                        };
806                        return (LaterUseKind::Call, function_span, None);
807                    } else {
808                        LaterUseKind::Other
809                    }
810                } else {
811                    LaterUseKind::Other
812                };
813
814                (kind, span, None)
815            }
816        }
817    }
818
819    /// Checks if a borrowed value was captured by a trait object. We do this by
820    /// looking forward in the MIR from the reserve location and checking if we see
821    /// an unsized cast to a trait object on our data.
822    fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
823        // Start at the reserve location, find the place that we want to see cast to a trait object.
824        let location = borrow.reserve_location;
825        let block = &self.body[location.block];
826        let stmt = block.statements.get(location.statement_index);
827        debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
828
829        // We make a `queue` vector that has the locations we want to visit. As of writing, this
830        // will only ever have one item at any given time, but by using a vector, we can pop from
831        // it which simplifies the termination logic.
832        let mut queue = vec![location];
833        let mut target =
834            if let Some(Statement { kind: StatementKind::Assign(box (place, _)), .. }) = stmt {
835                if let Some(local) = place.as_local() {
836                    local
837                } else {
838                    return false;
839                }
840            } else {
841                return false;
842            };
843
844        debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
845        while let Some(current_location) = queue.pop() {
846            debug!("was_captured_by_trait: target={:?}", target);
847            let block = &self.body[current_location.block];
848            // We need to check the current location to find out if it is a terminator.
849            let is_terminator = current_location.statement_index == block.statements.len();
850            if !is_terminator {
851                let stmt = &block.statements[current_location.statement_index];
852                debug!("was_captured_by_trait_object: stmt={:?}", stmt);
853
854                // The only kind of statement that we care about is assignments...
855                if let StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
856                    let Some(into) = place.local_or_deref_local() else {
857                        // Continue at the next location.
858                        queue.push(current_location.successor_within_block());
859                        continue;
860                    };
861
862                    match rvalue {
863                        // If we see a use, we should check whether it is our data, and if so
864                        // update the place that we're looking for to that new place.
865                        Rvalue::Use(operand) => match operand {
866                            Operand::Copy(place) | Operand::Move(place) => {
867                                if let Some(from) = place.as_local() {
868                                    if from == target {
869                                        target = into;
870                                    }
871                                }
872                            }
873                            _ => {}
874                        },
875                        // If we see an unsized cast, then if it is our data we should check
876                        // whether it is being cast to a trait object.
877                        Rvalue::Cast(
878                            CastKind::PointerCoercion(PointerCoercion::Unsize, _),
879                            operand,
880                            ty,
881                        ) => {
882                            match operand {
883                                Operand::Copy(place) | Operand::Move(place) => {
884                                    if let Some(from) = place.as_local() {
885                                        if from == target {
886                                            debug!("was_captured_by_trait_object: ty={:?}", ty);
887                                            // Check the type for a trait object.
888                                            return match ty.kind() {
889                                                // `&dyn Trait`
890                                                ty::Ref(_, ty, _) if ty.is_trait() => true,
891                                                // `Box<dyn Trait>`
892                                                _ if ty.boxed_ty().is_some_and(Ty::is_trait) => {
893                                                    true
894                                                }
895
896                                                // `dyn Trait`
897                                                _ if ty.is_trait() => true,
898                                                // Anything else.
899                                                _ => false,
900                                            };
901                                        }
902                                    }
903                                    return false;
904                                }
905                                _ => return false,
906                            }
907                        }
908                        _ => {}
909                    }
910                }
911
912                // Continue at the next location.
913                queue.push(current_location.successor_within_block());
914            } else {
915                // The only thing we need to do for terminators is progress to the next block.
916                let terminator = block.terminator();
917                debug!("was_captured_by_trait_object: terminator={:?}", terminator);
918
919                if let TerminatorKind::Call { destination, target: Some(block), args, .. } =
920                    &terminator.kind
921                {
922                    if let Some(dest) = destination.as_local() {
923                        debug!(
924                            "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
925                            target, dest, args
926                        );
927                        // Check if one of the arguments to this function is the target place.
928                        let found_target = args.iter().any(|arg| {
929                            if let Operand::Move(place) = arg.node {
930                                if let Some(potential) = place.as_local() {
931                                    potential == target
932                                } else {
933                                    false
934                                }
935                            } else {
936                                false
937                            }
938                        });
939
940                        // If it is, follow this to the next block and update the target.
941                        if found_target {
942                            target = dest;
943                            queue.push(block.start_location());
944                        }
945                    }
946                }
947            }
948
949            debug!("was_captured_by_trait: queue={:?}", queue);
950        }
951
952        // We didn't find anything and ran out of locations to check.
953        false
954    }
955}