| //! Print diagnostics to explain why values are borrowed. | |
| use std::assert_matches; | |
| use rustc_errors::{Applicability, Diag, EmissionGuarantee}; | |
| use rustc_hir as hir; | |
| use rustc_hir::intravisit::Visitor; | |
| use rustc_infer::infer::NllRegionVariableOrigin; | |
| use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault; | |
| use rustc_middle::mir::{ | |
| Body, CallSource, CastKind, ConstraintCategory, FakeReadCause, Local, LocalInfo, Location, | |
| Operand, Place, Rvalue, Statement, StatementKind, TerminatorKind, | |
| }; | |
| use rustc_middle::ty::adjustment::PointerCoercion; | |
| use rustc_middle::ty::{self, Ty, TyCtxt}; | |
| use rustc_span::{DesugaringKind, Span, kw, sym}; | |
| use rustc_trait_selection::error_reporting::traits::FindExprBySpan; | |
| use rustc_trait_selection::error_reporting::traits::call_kind::CallKind; | |
| use tracing::{debug, instrument}; | |
| use super::{RegionName, UseSpans, find_use}; | |
| use crate::borrow_set::BorrowData; | |
| use crate::constraints::OutlivesConstraint; | |
| use crate::nll::ConstraintDescription; | |
| use crate::region_infer::{BestBlame, Cause}; | |
| use crate::{MirBorrowckCtxt, WriteKind}; | |
| #[derive(Debug)] | |
| pub(crate) enum BorrowExplanation<'tcx> { | |
| UsedLater(Local, LaterUseKind, Span, Option<Span>), | |
| UsedLaterInLoop(LaterUseKind, Span, Option<Span>), | |
| UsedLaterWhenDropped { | |
| drop_loc: Location, | |
| dropped_local: Local, | |
| should_note_order: bool, | |
| }, | |
| MustBeValidFor { | |
| best_blame: BestBlame<'tcx>, | |
| region_name: RegionName, | |
| opt_place_desc: Option<String>, | |
| }, | |
| Unexplained, | |
| } | |
| #[derive(Clone, Copy, Debug)] | |
| pub(crate) enum LaterUseKind { | |
| TraitCapture, | |
| ClosureCapture, | |
| Call, | |
| FakeLetRead, | |
| Other, | |
| } | |
| impl<'tcx> BorrowExplanation<'tcx> { | |
| pub(crate) fn is_explained(&self) -> bool { | |
| !matches!(self, BorrowExplanation::Unexplained) | |
| } | |
| pub(crate) fn add_explanation_to_diagnostic<G: EmissionGuarantee>( | |
| &self, | |
| cx: &MirBorrowckCtxt<'_, '_, 'tcx>, | |
| err: &mut Diag<'_, G>, | |
| borrow_desc: &str, | |
| borrow_span: Option<Span>, | |
| multiple_borrow_span: Option<(Span, Span)>, | |
| ) { | |
| let tcx = cx.infcx.tcx; | |
| let body = cx.body; | |
| if let Some(span) = borrow_span { | |
| let def_id = body.source.def_id(); | |
| if let Some(node) = tcx.hir_get_if_local(def_id) | |
| && let Some(body_id) = node.body_id() | |
| { | |
| let body = tcx.hir_body(body_id); | |
| let mut expr_finder = FindExprBySpan::new(span, tcx); | |
| expr_finder.visit_expr(body.value); | |
| if let Some(mut expr) = expr_finder.result { | |
| while let hir::ExprKind::AddrOf(_, _, inner) | |
| | hir::ExprKind::Unary(hir::UnOp::Deref, inner) | |
| | hir::ExprKind::Field(inner, _) | |
| | hir::ExprKind::MethodCall(_, inner, _, _) | |
| | hir::ExprKind::Index(inner, _, _) = &expr.kind | |
| { | |
| expr = inner; | |
| } | |
| if let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind | |
| && let [hir::PathSegment { ident, args: None, .. }] = p.segments | |
| && let hir::def::Res::Local(hir_id) = p.res | |
| && let hir::Node::Pat(pat) = tcx.hir_node(hir_id) | |
| { | |
| if !ident.span.in_external_macro(tcx.sess.source_map()) { | |
| err.span_label(pat.span, format!("binding `{ident}` declared here")); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| match *self { | |
| BorrowExplanation::UsedLater( | |
| dropped_local, | |
| later_use_kind, | |
| var_or_use_span, | |
| path_span, | |
| ) => { | |
| let message = match later_use_kind { | |
| LaterUseKind::TraitCapture => "captured here by trait object", | |
| LaterUseKind::ClosureCapture => "captured here by closure", | |
| LaterUseKind::Call => "used by call", | |
| LaterUseKind::FakeLetRead => "stored here", | |
| LaterUseKind::Other => "used here", | |
| }; | |
| let local_decl = &body.local_decls[dropped_local]; | |
| if let &LocalInfo::IfThenRescopeTemp { if_then } = local_decl.local_info() | |
| && let Some((_, hir::Node::Expr(expr))) = tcx.hir_parent_iter(if_then).next() | |
| && let hir::ExprKind::If(cond, conseq, alt) = expr.kind | |
| && let hir::ExprKind::Let(&hir::LetExpr { | |
| span: _, | |
| pat, | |
| init, | |
| // FIXME(#101728): enable rewrite when type ascription is stabilized again | |
| ty: None, | |
| recovered: _, | |
| }) = cond.kind | |
| && pat.span.can_be_used_for_suggestions() | |
| && let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span) | |
| { | |
| suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err); | |
| } else if path_span.is_none_or(|path_span| path_span == var_or_use_span) { | |
| // We can use `var_or_use_span` if either `path_span` is not present, or both | |
| // spans are the same. | |
| if borrow_span.is_none_or(|sp| !sp.overlaps(var_or_use_span)) { | |
| err.span_label( | |
| var_or_use_span, | |
| format!("{borrow_desc}borrow later {message}"), | |
| ); | |
| } | |
| } else { | |
| // path_span must be `Some` as otherwise the if condition is true | |
| let path_span = path_span.unwrap(); | |
| // path_span is only present in the case of closure capture | |
| assert_matches!(later_use_kind, LaterUseKind::ClosureCapture); | |
| if !borrow_span.is_some_and(|sp| sp.overlaps(var_or_use_span)) { | |
| let path_label = "used here by closure"; | |
| let capture_kind_label = message; | |
| err.span_label( | |
| var_or_use_span, | |
| format!("{borrow_desc}borrow later {capture_kind_label}"), | |
| ); | |
| err.span_label(path_span, path_label); | |
| } | |
| } | |
| } | |
| BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span) => { | |
| let message = match later_use_kind { | |
| LaterUseKind::TraitCapture => { | |
| "borrow captured here by trait object, in later iteration of loop" | |
| } | |
| LaterUseKind::ClosureCapture => { | |
| "borrow captured here by closure, in later iteration of loop" | |
| } | |
| LaterUseKind::Call => "borrow used by call, in later iteration of loop", | |
| LaterUseKind::FakeLetRead => "borrow later stored here", | |
| LaterUseKind::Other => "borrow used here, in later iteration of loop", | |
| }; | |
| // We can use `var_or_use_span` if either `path_span` is not present, or both spans | |
| // are the same. | |
| if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) { | |
| err.span_label(var_or_use_span, format!("{borrow_desc}{message}")); | |
| } else { | |
| // path_span must be `Some` as otherwise the if condition is true | |
| let path_span = path_span.unwrap(); | |
| // path_span is only present in the case of closure capture | |
| assert_matches!(later_use_kind, LaterUseKind::ClosureCapture); | |
| if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) { | |
| let path_label = "used here by closure"; | |
| let capture_kind_label = message; | |
| err.span_label( | |
| var_or_use_span, | |
| format!("{borrow_desc}borrow later {capture_kind_label}"), | |
| ); | |
| err.span_label(path_span, path_label); | |
| } | |
| } | |
| } | |
| BorrowExplanation::UsedLaterWhenDropped { | |
| drop_loc, | |
| dropped_local, | |
| should_note_order, | |
| } => { | |
| let local_decl = &body.local_decls[dropped_local]; | |
| let mut ty = local_decl.ty; | |
| if local_decl.source_info.span.desugaring_kind() == Some(DesugaringKind::ForLoop) { | |
| if let ty::Adt(adt, args) = local_decl.ty.kind() { | |
| if tcx.is_diagnostic_item(sym::Option, adt.did()) { | |
| // in for loop desugaring, only look at the `Some(..)` inner type | |
| ty = args.type_at(0); | |
| } | |
| } | |
| } | |
| let (dtor_desc, type_desc) = match ty.kind() { | |
| // If type is an ADT that implements Drop, then | |
| // simplify output by reporting just the ADT name. | |
| ty::Adt(adt, _args) if adt.has_dtor(tcx) && !adt.is_box() => { | |
| ("`Drop` code", format!("type `{}`", tcx.def_path_str(adt.did()))) | |
| } | |
| // Otherwise, just report the whole type (and use | |
| // the intentionally fuzzy phrase "destructor") | |
| ty::Closure(..) => ("destructor", "closure".to_owned()), | |
| ty::Coroutine(..) => ("destructor", "coroutine".to_owned()), | |
| _ => ("destructor", format!("type `{}`", local_decl.ty)), | |
| }; | |
| match cx.local_name(dropped_local) { | |
| Some(local_name) if !local_decl.from_compiler_desugaring() => { | |
| let message = format!( | |
| "{borrow_desc}borrow might be used here, when `{local_name}` is dropped \ | |
| and runs the {dtor_desc} for {type_desc}", | |
| ); | |
| err.span_label(body.source_info(drop_loc).span, message); | |
| if should_note_order { | |
| err.note( | |
| "values in a scope are dropped \ | |
| in the opposite order they are defined", | |
| ); | |
| } | |
| } | |
| _ => { | |
| err.span_label( | |
| local_decl.source_info.span, | |
| format!( | |
| "a temporary with access to the {borrow_desc}borrow \ | |
| is created here ...", | |
| ), | |
| ); | |
| let message = format!( | |
| "... and the {borrow_desc}borrow might be used here, \ | |
| when that temporary is dropped \ | |
| and runs the {dtor_desc} for {type_desc}", | |
| ); | |
| err.span_label(body.source_info(drop_loc).span, message); | |
| struct FindLetExpr<'hir> { | |
| span: Span, | |
| result: Option<(Span, &'hir hir::Pat<'hir>, &'hir hir::Expr<'hir>)>, | |
| tcx: TyCtxt<'hir>, | |
| } | |
| impl<'hir> rustc_hir::intravisit::Visitor<'hir> for FindLetExpr<'hir> { | |
| type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies; | |
| fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { | |
| self.tcx | |
| } | |
| fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { | |
| if let hir::ExprKind::If(cond, _conseq, _alt) | |
| | hir::ExprKind::Loop( | |
| &hir::Block { | |
| expr: | |
| Some(&hir::Expr { | |
| kind: hir::ExprKind::If(cond, _conseq, _alt), | |
| .. | |
| }), | |
| .. | |
| }, | |
| _, | |
| hir::LoopSource::While, | |
| _, | |
| ) = expr.kind | |
| && let hir::ExprKind::Let(hir::LetExpr { | |
| init: let_expr_init, | |
| span: let_expr_span, | |
| pat: let_expr_pat, | |
| .. | |
| }) = cond.kind | |
| && let_expr_init.span.contains(self.span) | |
| { | |
| self.result = | |
| Some((*let_expr_span, let_expr_pat, let_expr_init)) | |
| } else { | |
| hir::intravisit::walk_expr(self, expr); | |
| } | |
| } | |
| } | |
| if let &LocalInfo::IfThenRescopeTemp { if_then } = local_decl.local_info() | |
| && let hir::Node::Expr(expr) = tcx.hir_node(if_then) | |
| && let hir::ExprKind::If(cond, conseq, alt) = expr.kind | |
| && let hir::ExprKind::Let(&hir::LetExpr { | |
| span: _, | |
| pat, | |
| init, | |
| // FIXME(#101728): enable rewrite when type ascription is | |
| // stabilized again. | |
| ty: None, | |
| recovered: _, | |
| }) = cond.kind | |
| && pat.span.can_be_used_for_suggestions() | |
| && let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span) | |
| { | |
| suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err); | |
| } else if let Some((old, new)) = multiple_borrow_span | |
| && let def_id = body.source.def_id() | |
| && let Some(node) = tcx.hir_get_if_local(def_id) | |
| && let Some(body_id) = node.body_id() | |
| && let hir_body = tcx.hir_body(body_id) | |
| && let mut expr_finder = (FindLetExpr { span: old, result: None, tcx }) | |
| && let Some((let_expr_span, let_expr_pat, let_expr_init)) = { | |
| expr_finder.visit_expr(hir_body.value); | |
| expr_finder.result | |
| } | |
| && !let_expr_span.contains(new) | |
| { | |
| // #133941: The `old` expression is at the conditional part of an | |
| // if/while let expression. Adding a semicolon won't work. | |
| // Instead, try suggesting the `matches!` macro or a temporary. | |
| if let_expr_pat | |
| .walk_short(|pat| !matches!(pat.kind, hir::PatKind::Binding(..))) | |
| { | |
| if let Ok(pat_snippet) = | |
| tcx.sess.source_map().span_to_snippet(let_expr_pat.span) | |
| && let Ok(init_snippet) = | |
| tcx.sess.source_map().span_to_snippet(let_expr_init.span) | |
| { | |
| err.span_suggestion_verbose( | |
| let_expr_span, | |
| "consider using the `matches!` macro", | |
| format!("matches!({init_snippet}, {pat_snippet})"), | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } else { | |
| err.note("consider using the `matches!` macro"); | |
| } | |
| } | |
| } else if let LocalInfo::BlockTailTemp(info) = local_decl.local_info() { | |
| let sp = info.span.find_ancestor_not_from_macro().unwrap_or(info.span); | |
| if info.tail_result_is_ignored { | |
| // #85581: If the first mutable borrow's scope contains | |
| // the second borrow, this suggestion isn't helpful. | |
| if !multiple_borrow_span.is_some_and(|(old, new)| { | |
| old.to(info.span.shrink_to_hi()).contains(new) | |
| }) { | |
| err.span_suggestion_verbose( | |
| sp.shrink_to_hi(), | |
| "consider adding semicolon after the expression so its \ | |
| temporaries are dropped sooner, before the local variables \ | |
| declared by the block are dropped", | |
| ";", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| } else { | |
| err.note( | |
| "the temporary is part of an expression at the end of a \ | |
| block;\nconsider forcing this temporary to be dropped sooner, \ | |
| before the block's local variables are dropped", | |
| ); | |
| err.multipart_suggestion( | |
| "for example, you could save the expression's value in a new \ | |
| local variable `x` and then make `x` be the expression at the \ | |
| end of the block", | |
| vec![ | |
| (sp.shrink_to_lo(), "let x = ".to_string()), | |
| (sp.shrink_to_hi(), "; x".to_string()), | |
| ], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| }; | |
| } | |
| } | |
| } | |
| } | |
| BorrowExplanation::MustBeValidFor { | |
| ref best_blame, | |
| ref region_name, | |
| ref opt_place_desc, | |
| } => { | |
| let OutlivesConstraint { category, span, .. } = *best_blame.constraint(); | |
| let path = best_blame.path(); | |
| region_name.highlight_region_name(err); | |
| if let Some(desc) = opt_place_desc { | |
| err.span_label( | |
| span, | |
| format!( | |
| "{}requires that `{desc}` is borrowed for `{region_name}`", | |
| category.description(), | |
| ), | |
| ); | |
| } else { | |
| err.span_label( | |
| span, | |
| format!( | |
| "{}requires that {borrow_desc}borrow lasts for `{region_name}`", | |
| category.description(), | |
| ), | |
| ); | |
| }; | |
| cx.add_placeholder_from_predicate_note(err, path); | |
| cx.add_sized_or_copy_bound_info(err, category, path); | |
| if let ConstraintCategory::Cast { | |
| is_raw_ptr_dyn_type_cast: _, | |
| is_implicit_coercion: true, | |
| unsize_to: Some(unsize_ty), | |
| } = category | |
| { | |
| self.add_object_lifetime_default_note(tcx, err, unsize_ty); | |
| } | |
| let mut preds = path | |
| .iter() | |
| .filter_map(|constraint| match constraint.category { | |
| ConstraintCategory::Predicate(pred) if !pred.is_dummy() => Some(pred), | |
| _ => None, | |
| }) | |
| .collect::<Vec<Span>>(); | |
| preds.sort(); | |
| preds.dedup(); | |
| if !preds.is_empty() { | |
| let s = if preds.len() == 1 { "" } else { "s" }; | |
| err.span_note( | |
| preds, | |
| format!( | |
| "requirement{s} that the value outlives `{region_name}` introduced here" | |
| ), | |
| ); | |
| } | |
| self.add_lifetime_bound_suggestion_to_diagnostic(err, &category, span, region_name); | |
| } | |
| _ => {} | |
| } | |
| } | |
| fn add_object_lifetime_default_note<G: EmissionGuarantee>( | |
| &self, | |
| tcx: TyCtxt<'tcx>, | |
| err: &mut Diag<'_, G>, | |
| unsize_ty: Ty<'tcx>, | |
| ) { | |
| if let ty::Adt(def, args) = unsize_ty.kind() { | |
| // We try to elaborate the object lifetime defaults and present those to the user. This | |
| // should make it clear where the region constraint is coming from. | |
| let generics = tcx.generics_of(def.did()); | |
| let mut has_dyn = false; | |
| let mut failed = false; | |
| let elaborated_args = | |
| std::iter::zip(*args, &generics.own_params).map(|(arg, param)| { | |
| if let Some(ty::Dynamic(obj, _)) = arg.as_type().map(Ty::kind) { | |
| let default = tcx.object_lifetime_default(param.def_id); | |
| let re_static = tcx.lifetimes.re_static; | |
| let implied_region = match default { | |
| // This is not entirely precise. | |
| ObjectLifetimeDefault::Empty => re_static, | |
| ObjectLifetimeDefault::Ambiguous => { | |
| failed = true; | |
| re_static | |
| } | |
| ObjectLifetimeDefault::Param(param_def_id) => { | |
| let index = generics.param_def_id_to_index[¶m_def_id] as usize; | |
| args.get(index).and_then(|arg| arg.as_region()).unwrap_or_else( | |
| || { | |
| failed = true; | |
| re_static | |
| }, | |
| ) | |
| } | |
| ObjectLifetimeDefault::Static => re_static, | |
| }; | |
| has_dyn = true; | |
| Ty::new_dynamic(tcx, obj, implied_region).into() | |
| } else { | |
| arg | |
| } | |
| }); | |
| let elaborated_ty = Ty::new_adt(tcx, *def, tcx.mk_args_from_iter(elaborated_args)); | |
| if has_dyn && !failed { | |
| err.note(format!( | |
| "due to object lifetime defaults, `{unsize_ty}` actually means `{elaborated_ty}`" | |
| )); | |
| } | |
| } | |
| } | |
| fn add_lifetime_bound_suggestion_to_diagnostic<G: EmissionGuarantee>( | |
| &self, | |
| err: &mut Diag<'_, G>, | |
| category: &ConstraintCategory<'tcx>, | |
| span: Span, | |
| region_name: &RegionName, | |
| ) { | |
| if !span.is_desugaring(DesugaringKind::OpaqueTy) { | |
| return; | |
| } | |
| if let ConstraintCategory::OpaqueType = category { | |
| let suggestable_name = | |
| if region_name.was_named() { region_name.name } else { kw::UnderscoreLifetime }; | |
| let msg = format!( | |
| "you can add a bound to the {}to make it last less than `'static` and match `{region_name}`", | |
| category.description(), | |
| ); | |
| err.span_suggestion_verbose( | |
| span.shrink_to_hi(), | |
| msg, | |
| format!(" + {suggestable_name}"), | |
| Applicability::Unspecified, | |
| ); | |
| } | |
| } | |
| } | |
| fn suggest_rewrite_if_let<G: EmissionGuarantee>( | |
| tcx: TyCtxt<'_>, | |
| expr: &hir::Expr<'_>, | |
| pat: &str, | |
| init: &hir::Expr<'_>, | |
| conseq: &hir::Expr<'_>, | |
| alt: Option<&hir::Expr<'_>>, | |
| err: &mut Diag<'_, G>, | |
| ) { | |
| let source_map = tcx.sess.source_map(); | |
| err.span_note( | |
| source_map.end_point(conseq.span), | |
| "lifetimes for temporaries generated in `if let`s have been shortened in Edition 2024 so that they are dropped here instead", | |
| ); | |
| if expr.span.can_be_used_for_suggestions() && conseq.span.can_be_used_for_suggestions() { | |
| let needs_block = if let Some(hir::Node::Expr(expr)) = | |
| alt.and_then(|alt| tcx.hir_parent_iter(alt.hir_id).next()).map(|(_, node)| node) | |
| { | |
| matches!(expr.kind, hir::ExprKind::If(..)) | |
| } else { | |
| false | |
| }; | |
| let mut sugg = vec![ | |
| ( | |
| expr.span.shrink_to_lo().between(init.span), | |
| if needs_block { "{ match ".into() } else { "match ".into() }, | |
| ), | |
| (conseq.span.shrink_to_lo(), format!(" {{ {pat} => ")), | |
| ]; | |
| let expr_end = expr.span.shrink_to_hi(); | |
| let mut expr_end_code; | |
| if let Some(alt) = alt { | |
| sugg.push((conseq.span.between(alt.span), " _ => ".into())); | |
| expr_end_code = "}".to_string(); | |
| } else { | |
| expr_end_code = " _ => {} }".into(); | |
| } | |
| expr_end_code.push('}'); | |
| sugg.push((expr_end, expr_end_code)); | |
| err.multipart_suggestion( | |
| "consider rewriting the `if` into `match` which preserves the extended lifetime", | |
| sugg, | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| } | |
| impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { | |
| /// Returns structured explanation for *why* the borrow contains the | |
| /// point from `location`. This is key for the "3-point errors" | |
| /// [described in the NLL RFC][d]. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// - `borrow`: the borrow in question | |
| /// - `location`: where the borrow occurs | |
| /// - `kind_place`: if Some, this describes the statement that triggered the error. | |
| /// - first half is the kind of write, if any, being performed | |
| /// - second half is the place being accessed | |
| /// | |
| /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points | |
| #[instrument(level = "debug", skip(self))] | |
| pub(crate) fn explain_why_borrow_contains_point( | |
| &self, | |
| location: Location, | |
| borrow: &BorrowData<'tcx>, | |
| kind_place: Option<(WriteKind, Place<'tcx>)>, | |
| ) -> BorrowExplanation<'tcx> { | |
| let regioncx = &self.regioncx; | |
| let body: &Body<'_> = self.body; | |
| let tcx = self.infcx.tcx; | |
| let borrow_region_vid = borrow.region; | |
| debug!(?borrow_region_vid); | |
| let mut region_sub = self.regioncx.find_sub_region_live_at(borrow_region_vid, location); | |
| debug!(?region_sub); | |
| let mut use_location = location; | |
| let mut use_in_later_iteration_of_loop = false; | |
| if region_sub == borrow_region_vid { | |
| // When `region_sub` is the same as `borrow_region_vid` (the location where the borrow | |
| // is issued is the same location that invalidates the reference), this is likely a | |
| // loop iteration. In this case, try using the loop terminator location in | |
| // `find_sub_region_live_at`. | |
| if let Some(loop_terminator_location) = | |
| regioncx.find_loop_terminator_location(borrow.region, body) | |
| { | |
| region_sub = self | |
| .regioncx | |
| .find_sub_region_live_at(borrow_region_vid, loop_terminator_location); | |
| debug!("explain_why_borrow_contains_point: region_sub in loop={:?}", region_sub); | |
| use_location = loop_terminator_location; | |
| use_in_later_iteration_of_loop = true; | |
| } | |
| } | |
| // NLL doesn't consider boring locals for liveness, and wouldn't encounter a | |
| // `Cause::LiveVar` for such a local. Polonius can't avoid computing liveness for boring | |
| // locals yet, and will encounter them when trying to explain why a borrow contains a given | |
| // point. | |
| // | |
| // We want to focus on relevant live locals in diagnostics, so when polonius is enabled, we | |
| // ensure that we don't emit live boring locals as explanations. | |
| let is_local_boring = |local| { | |
| if let Some(polonius_context) = self.polonius_context { | |
| polonius_context.boring_nll_locals.contains(&local) | |
| } else { | |
| assert!(!tcx.sess.opts.unstable_opts.polonius.is_next_enabled()); | |
| // Boring locals are never the cause of a borrow explanation in NLLs. | |
| false | |
| } | |
| }; | |
| match find_use::find(body, regioncx, tcx, region_sub, use_location) { | |
| Some(Cause::LiveVar(local, location)) if !is_local_boring(local) => { | |
| let span = body.source_info(location).span; | |
| let spans = self | |
| .move_spans(Place::from(local).as_ref(), location) | |
| .or_else(|| self.borrow_spans(span, location)); | |
| if use_in_later_iteration_of_loop { | |
| let (later_use_kind, var_or_use_span, path_span) = | |
| self.later_use_kind(borrow, spans, use_location); | |
| BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span) | |
| } else { | |
| // Check if the location represents a `FakeRead`, and adapt the error | |
| // message to the `FakeReadCause` it is from: in particular, | |
| // the ones inserted in optimized `let var = <expr>` patterns. | |
| let (later_use_kind, var_or_use_span, path_span) = | |
| self.later_use_kind(borrow, spans, location); | |
| BorrowExplanation::UsedLater( | |
| borrow.borrowed_place.local, | |
| later_use_kind, | |
| var_or_use_span, | |
| path_span, | |
| ) | |
| } | |
| } | |
| Some(Cause::DropVar(local, location)) if !is_local_boring(local) => { | |
| let mut should_note_order = false; | |
| if self.local_name(local).is_some() | |
| && let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place | |
| && let Some(borrowed_local) = place.as_local() | |
| && self.local_name(borrowed_local).is_some() | |
| && local != borrowed_local | |
| { | |
| should_note_order = true; | |
| } | |
| BorrowExplanation::UsedLaterWhenDropped { | |
| drop_loc: location, | |
| dropped_local: local, | |
| should_note_order, | |
| } | |
| } | |
| Some(Cause::LiveVar(..) | Cause::DropVar(..)) | None => { | |
| // Here, under NLL: no cause was found. Under polonius: no cause was found, or a | |
| // boring local was found, which we ignore like NLLs do to match its diagnostics. | |
| if let Some(region) = self.regioncx.to_error_region_vid(borrow_region_vid) { | |
| let best_blame = self.regioncx.best_blame_constraint( | |
| borrow_region_vid, | |
| NllRegionVariableOrigin::FreeRegion, | |
| region, | |
| ); | |
| if let Some(region_name) = self.give_region_a_name(region) { | |
| let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref()); | |
| BorrowExplanation::MustBeValidFor { | |
| best_blame, | |
| region_name, | |
| opt_place_desc, | |
| } | |
| } else { | |
| debug!("Could not generate a region name"); | |
| BorrowExplanation::Unexplained | |
| } | |
| } else { | |
| debug!("Could not generate an error region vid"); | |
| BorrowExplanation::Unexplained | |
| } | |
| } | |
| } | |
| } | |
| /// Determine how the borrow was later used. | |
| /// First span returned points to the location of the conflicting use | |
| /// Second span if `Some` is returned in the case of closures and points | |
| /// to the use of the path | |
| #[instrument(level = "debug", skip(self))] | |
| fn later_use_kind( | |
| &self, | |
| borrow: &BorrowData<'tcx>, | |
| use_spans: UseSpans<'tcx>, | |
| location: Location, | |
| ) -> (LaterUseKind, Span, Option<Span>) { | |
| match use_spans { | |
| UseSpans::ClosureUse { capture_kind_span, path_span, .. } => { | |
| // Used in a closure. | |
| (LaterUseKind::ClosureCapture, capture_kind_span, Some(path_span)) | |
| } | |
| // In the case that the borrowed value (probably a temporary) | |
| // overlaps with the method's receiver, then point at the method. | |
| UseSpans::FnSelfUse { | |
| var_span: span, | |
| kind: CallKind::Normal { desugaring: None, .. }, | |
| .. | |
| } if span | |
| .overlaps(self.body.local_decls[borrow.assigned_place.local].source_info.span) => | |
| { | |
| if let TerminatorKind::Call { func, call_source: CallSource::Normal, .. } = | |
| &self.body.basic_blocks[location.block].terminator().kind | |
| { | |
| // Just point to the function, to reduce the chance of overlapping spans. | |
| let function_span = match func { | |
| Operand::RuntimeChecks(_) => span, | |
| Operand::Constant(c) => c.span, | |
| Operand::Copy(place) | Operand::Move(place) => { | |
| if let Some(l) = place.as_local() { | |
| let local_decl = &self.body.local_decls[l]; | |
| if self.local_name(l).is_none() { | |
| local_decl.source_info.span | |
| } else { | |
| span | |
| } | |
| } else { | |
| span | |
| } | |
| } | |
| }; | |
| (LaterUseKind::Call, function_span, None) | |
| } else { | |
| (LaterUseKind::Other, span, None) | |
| } | |
| } | |
| UseSpans::PatUse(span) | |
| | UseSpans::OtherUse(span) | |
| | UseSpans::FnSelfUse { var_span: span, .. } => { | |
| let block = &self.body.basic_blocks[location.block]; | |
| let kind = if let Some(&Statement { | |
| kind: StatementKind::FakeRead((FakeReadCause::ForLet(_), place)), | |
| .. | |
| }) = block.statements.get(location.statement_index) | |
| { | |
| if let Some(l) = place.as_local() | |
| && let local_decl = &self.body.local_decls[l] | |
| && local_decl.ty.is_closure() | |
| { | |
| LaterUseKind::ClosureCapture | |
| } else { | |
| LaterUseKind::FakeLetRead | |
| } | |
| } else if self.was_captured_by_trait_object(borrow) { | |
| LaterUseKind::TraitCapture | |
| } else if location.statement_index == block.statements.len() { | |
| if let TerminatorKind::Call { func, call_source: CallSource::Normal, .. } = | |
| &block.terminator().kind | |
| { | |
| // Just point to the function, to reduce the chance of overlapping spans. | |
| let function_span = match func { | |
| Operand::RuntimeChecks(_) => span, | |
| Operand::Constant(c) => c.span, | |
| Operand::Copy(place) | Operand::Move(place) => { | |
| if let Some(l) = place.as_local() { | |
| let local_decl = &self.body.local_decls[l]; | |
| if self.local_name(l).is_none() { | |
| local_decl.source_info.span | |
| } else { | |
| span | |
| } | |
| } else { | |
| span | |
| } | |
| } | |
| }; | |
| return (LaterUseKind::Call, function_span, None); | |
| } else { | |
| LaterUseKind::Other | |
| } | |
| } else { | |
| LaterUseKind::Other | |
| }; | |
| (kind, span, None) | |
| } | |
| } | |
| } | |
| /// Checks if a borrowed value was captured by a trait object. We do this by | |
| /// looking forward in the MIR from the reserve location and checking if we see | |
| /// an unsized cast to a trait object on our data. | |
| fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool { | |
| // Start at the reserve location, find the place that we want to see cast to a trait object. | |
| let location = borrow.reserve_location; | |
| let block = &self.body[location.block]; | |
| let stmt = block.statements.get(location.statement_index); | |
| debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt); | |
| // We make a `queue` vector that has the locations we want to visit. As of writing, this | |
| // will only ever have one item at any given time, but by using a vector, we can pop from | |
| // it which simplifies the termination logic. | |
| let mut queue = vec![location]; | |
| let Some(Statement { kind: StatementKind::Assign((place, _)), .. }) = stmt else { | |
| return false; | |
| }; | |
| let Some(mut target) = place.as_local() else { return false }; | |
| debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue); | |
| while let Some(current_location) = queue.pop() { | |
| debug!("was_captured_by_trait: target={:?}", target); | |
| let block = &self.body[current_location.block]; | |
| // We need to check the current location to find out if it is a terminator. | |
| let is_terminator = current_location.statement_index == block.statements.len(); | |
| if !is_terminator { | |
| let stmt = &block.statements[current_location.statement_index]; | |
| debug!("was_captured_by_trait_object: stmt={:?}", stmt); | |
| // The only kind of statement that we care about is assignments... | |
| if let StatementKind::Assign((place, rvalue)) = &stmt.kind { | |
| let Some(into) = place.local_or_deref_local() else { | |
| // Continue at the next location. | |
| queue.push(current_location.successor_within_block()); | |
| continue; | |
| }; | |
| match rvalue { | |
| // If we see a use, we should check whether it is our data, and if so | |
| // update the place that we're looking for to that new place. | |
| Rvalue::Use(operand, _) => match operand { | |
| Operand::Copy(place) | Operand::Move(place) => { | |
| if let Some(from) = place.as_local() { | |
| if from == target { | |
| target = into; | |
| } | |
| } | |
| } | |
| _ => {} | |
| }, | |
| // If we see an unsized cast, then if it is our data we should check | |
| // whether it is being cast to a trait object. | |
| Rvalue::Cast( | |
| CastKind::PointerCoercion(PointerCoercion::Unsize, _), | |
| operand, | |
| ty, | |
| ) => { | |
| match operand { | |
| Operand::Copy(place) | Operand::Move(place) => { | |
| if let Some(from) = place.as_local() { | |
| if from == target { | |
| debug!("was_captured_by_trait_object: ty={:?}", ty); | |
| // Check the type for a trait object. | |
| return match ty.kind() { | |
| // `&dyn Trait` | |
| ty::Ref(_, ty, _) if ty.is_trait() => true, | |
| // `Box<dyn Trait>` | |
| _ if ty.boxed_ty().is_some_and(Ty::is_trait) => { | |
| true | |
| } | |
| // `dyn Trait` | |
| _ if ty.is_trait() => true, | |
| // Anything else. | |
| _ => false, | |
| }; | |
| } | |
| } | |
| return false; | |
| } | |
| _ => return false, | |
| } | |
| } | |
| _ => {} | |
| } | |
| } | |
| // Continue at the next location. | |
| queue.push(current_location.successor_within_block()); | |
| } else { | |
| // The only thing we need to do for terminators is progress to the next block. | |
| let terminator = block.terminator(); | |
| debug!("was_captured_by_trait_object: terminator={:?}", terminator); | |
| if let TerminatorKind::Call { destination, target: Some(block), args, .. } = | |
| &terminator.kind | |
| && let Some(dest) = destination.as_local() | |
| { | |
| debug!( | |
| "was_captured_by_trait_object: target={:?} dest={:?} args={:?}", | |
| target, dest, args | |
| ); | |
| // Check if one of the arguments to this function is the target place. | |
| let found_target = args.iter().any(|arg| { | |
| if let Operand::Move(place) = arg.node { | |
| if let Some(potential) = place.as_local() { | |
| potential == target | |
| } else { | |
| false | |
| } | |
| } else { | |
| false | |
| } | |
| }); | |
| // If it is, follow this to the next block and update the target. | |
| if found_target { | |
| target = dest; | |
| queue.push(block.start_location()); | |
| } | |
| } | |
| } | |
| debug!("was_captured_by_trait: queue={:?}", queue); | |
| } | |
| // We didn't find anything and ran out of locations to check. | |
| false | |
| } | |
| } |