//! Print diagnostics to explain why values are borrowed. #![allow(rustc::diagnostic_outside_of_impl)] #![allow(rustc::untranslatable_diagnostic)] use std::assert_matches::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, RegionVid, 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::{BlameConstraint, Cause}; use crate::{MirBorrowckCtxt, WriteKind}; #[derive(Debug)] pub(crate) enum BorrowExplanation<'tcx> { UsedLater(Local, LaterUseKind, Span, Option), UsedLaterInLoop(LaterUseKind, Span, Option), UsedLaterWhenDropped { drop_loc: Location, dropped_local: Local, should_note_order: bool, }, MustBeValidFor { category: ConstraintCategory<'tcx>, from_closure: bool, span: Span, region_name: RegionName, opt_place_desc: Option, path: Vec>, }, 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( &self, cx: &MirBorrowckCtxt<'_, '_, 'tcx>, err: &mut Diag<'_, G>, borrow_desc: &str, borrow_span: Option, 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 { category, span, ref region_name, ref opt_place_desc, from_closure: _, ref 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_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::>(); 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( &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( &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( 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> { fn free_region_constraint_info( &self, borrow_region: RegionVid, outlived_region: RegionVid, ) -> (ConstraintCategory<'tcx>, bool, Span, Option, Vec>) { let (blame_constraint, path) = self.regioncx.best_blame_constraint( borrow_region, NllRegionVariableOrigin::FreeRegion, outlived_region, ); let BlameConstraint { category, from_closure, cause, .. } = blame_constraint; let outlived_fr_name = self.give_region_a_name(outlived_region); (category, from_closure, cause.span, outlived_fr_name, path) } /// 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_diagnostics) = self.polonius_diagnostics { polonius_diagnostics.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 = ` 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)) => { 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(..)) | 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.to_error_region_vid(borrow_region_vid) { let (category, from_closure, span, region_name, path) = self.free_region_constraint_info(borrow_region_vid, region); if let Some(region_name) = region_name { let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref()); BorrowExplanation::MustBeValidFor { category, from_closure, span, region_name, opt_place_desc, path, } } 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) { 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::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(box (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::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 mut target = if let Some(Statement { kind: StatementKind::Assign(box (place, _)), .. }) = stmt { if let Some(local) = place.as_local() { local } else { return false; } } 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(box (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` _ 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 } }