1 //! Print diagnostics to explain why values are borrowed.
3 use rustc_errors::{Applicability, Diagnostic};
5 use rustc_hir::intravisit::Visitor;
6 use rustc_index::vec::IndexVec;
7 use rustc_infer::infer::NllRegionVariableOrigin;
8 use rustc_middle::mir::{
9 Body, CastKind, ConstraintCategory, FakeReadCause, Local, Location, Operand, Place, Rvalue,
10 Statement, StatementKind, TerminatorKind,
12 use rustc_middle::ty::adjustment::PointerCast;
13 use rustc_middle::ty::{self, RegionVid, TyCtxt};
14 use rustc_span::symbol::{kw, Symbol};
15 use rustc_span::{sym, DesugaringKind, Span};
16 use rustc_trait_selection::traits::error_reporting::FindExprBySpan;
18 use crate::region_infer::{BlameConstraint, ExtraConstraintInfo};
20 borrow_set::BorrowData, nll::ConstraintDescription, region_infer::Cause, MirBorrowckCtxt,
24 use super::{find_use, RegionName, UseSpans};
27 pub(crate) enum BorrowExplanation<'tcx> {
28 UsedLater(LaterUseKind, Span, Option<Span>),
29 UsedLaterInLoop(LaterUseKind, Span, Option<Span>),
30 UsedLaterWhenDropped {
33 should_note_order: bool,
36 category: ConstraintCategory<'tcx>,
39 region_name: RegionName,
40 opt_place_desc: Option<String>,
41 extra_info: Vec<ExtraConstraintInfo>,
46 #[derive(Clone, Copy, Debug)]
47 pub(crate) enum LaterUseKind {
55 impl<'tcx> BorrowExplanation<'tcx> {
56 pub(crate) fn is_explained(&self) -> bool {
57 !matches!(self, BorrowExplanation::Unexplained)
59 pub(crate) fn add_explanation_to_diagnostic(
63 local_names: &IndexVec<Local, Option<Symbol>>,
66 borrow_span: Option<Span>,
67 multiple_borrow_span: Option<(Span, Span)>,
69 if let Some(span) = borrow_span {
70 let def_id = body.source.def_id();
71 if let Some(node) = tcx.hir().get_if_local(def_id)
72 && let Some(body_id) = node.body_id()
74 let body = tcx.hir().body(body_id);
75 let mut expr_finder = FindExprBySpan::new(span);
76 expr_finder.visit_expr(body.value);
77 if let Some(mut expr) = expr_finder.result {
78 while let hir::ExprKind::AddrOf(_, _, inner)
79 | hir::ExprKind::Unary(hir::UnOp::Deref, inner)
80 | hir::ExprKind::Field(inner, _)
81 | hir::ExprKind::MethodCall(_, inner, _, _)
82 | hir::ExprKind::Index(inner, _) = &expr.kind
86 if let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind
87 && let [hir::PathSegment { ident, args: None, .. }] = p.segments
88 && let hir::def::Res::Local(hir_id) = p.res
89 && let Some(hir::Node::Pat(pat)) = tcx.hir().find(hir_id)
93 &format!("binding `{ident}` declared here"),
100 BorrowExplanation::UsedLater(later_use_kind, var_or_use_span, path_span) => {
101 let message = match later_use_kind {
102 LaterUseKind::TraitCapture => "captured here by trait object",
103 LaterUseKind::ClosureCapture => "captured here by closure",
104 LaterUseKind::Call => "used by call",
105 LaterUseKind::FakeLetRead => "stored here",
106 LaterUseKind::Other => "used here",
108 // We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
109 if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) {
110 if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) {
113 format!("{borrow_desc}borrow later {message}"),
117 // path_span must be `Some` as otherwise the if condition is true
118 let path_span = path_span.unwrap();
119 // path_span is only present in the case of closure capture
120 assert!(matches!(later_use_kind, LaterUseKind::ClosureCapture));
121 if !borrow_span.map_or(false, |sp| sp.overlaps(var_or_use_span)) {
122 let path_label = "used here by closure";
123 let capture_kind_label = message;
126 format!("{borrow_desc}borrow later {capture_kind_label}"),
128 err.span_label(path_span, path_label);
132 BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span) => {
133 let message = match later_use_kind {
134 LaterUseKind::TraitCapture => {
135 "borrow captured here by trait object, in later iteration of loop"
137 LaterUseKind::ClosureCapture => {
138 "borrow captured here by closure, in later iteration of loop"
140 LaterUseKind::Call => "borrow used by call, in later iteration of loop",
141 LaterUseKind::FakeLetRead => "borrow later stored here",
142 LaterUseKind::Other => "borrow used here, in later iteration of loop",
144 // We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
145 if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) {
146 err.span_label(var_or_use_span, format!("{borrow_desc}{message}"));
148 // path_span must be `Some` as otherwise the if condition is true
149 let path_span = path_span.unwrap();
150 // path_span is only present in the case of closure capture
151 assert!(matches!(later_use_kind, LaterUseKind::ClosureCapture));
152 if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) {
153 let path_label = "used here by closure";
154 let capture_kind_label = message;
157 format!("{borrow_desc}borrow later {capture_kind_label}"),
159 err.span_label(path_span, path_label);
163 BorrowExplanation::UsedLaterWhenDropped {
168 let local_decl = &body.local_decls[dropped_local];
169 let mut ty = local_decl.ty;
170 if local_decl.source_info.span.desugaring_kind() == Some(DesugaringKind::ForLoop) {
171 if let ty::Adt(adt, substs) = local_decl.ty.kind() {
172 if tcx.is_diagnostic_item(sym::Option, adt.did()) {
173 // in for loop desugaring, only look at the `Some(..)` inner type
174 ty = substs.type_at(0);
178 let (dtor_desc, type_desc) = match ty.kind() {
179 // If type is an ADT that implements Drop, then
180 // simplify output by reporting just the ADT name.
181 ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => {
182 ("`Drop` code", format!("type `{}`", tcx.def_path_str(adt.did())))
185 // Otherwise, just report the whole type (and use
186 // the intentionally fuzzy phrase "destructor")
187 ty::Closure(..) => ("destructor", "closure".to_owned()),
188 ty::Generator(..) => ("destructor", "generator".to_owned()),
190 _ => ("destructor", format!("type `{}`", local_decl.ty)),
193 match local_names[dropped_local] {
194 Some(local_name) if !local_decl.from_compiler_desugaring() => {
195 let message = format!(
196 "{borrow_desc}borrow might be used here, when `{local_name}` is dropped \
197 and runs the {dtor_desc} for {type_desc}",
199 err.span_label(body.source_info(drop_loc).span, message);
201 if should_note_order {
203 "values in a scope are dropped \
204 in the opposite order they are defined",
210 local_decl.source_info.span,
212 "a temporary with access to the {borrow_desc}borrow \
213 is created here ...",
216 let message = format!(
217 "... and the {borrow_desc}borrow might be used here, \
218 when that temporary is dropped \
219 and runs the {dtor_desc} for {type_desc}",
221 err.span_label(body.source_info(drop_loc).span, message);
223 if let Some(info) = &local_decl.is_block_tail {
224 if info.tail_result_is_ignored {
225 // #85581: If the first mutable borrow's scope contains
226 // the second borrow, this suggestion isn't helpful.
227 if !multiple_borrow_span
229 old.to(info.span.shrink_to_hi()).contains(new)
233 err.span_suggestion_verbose(
234 info.span.shrink_to_hi(),
235 "consider adding semicolon after the expression so its \
236 temporaries are dropped sooner, before the local variables \
237 declared by the block are dropped",
239 Applicability::MaybeIncorrect,
244 "the temporary is part of an expression at the end of a \
245 block;\nconsider forcing this temporary to be dropped sooner, \
246 before the block's local variables are dropped",
248 err.multipart_suggestion(
249 "for example, you could save the expression's value in a new \
250 local variable `x` and then make `x` be the expression at the \
253 (info.span.shrink_to_lo(), "let x = ".to_string()),
254 (info.span.shrink_to_hi(), "; x".to_string()),
256 Applicability::MaybeIncorrect,
263 BorrowExplanation::MustBeValidFor {
271 region_name.highlight_region_name(err);
273 if let Some(desc) = opt_place_desc {
277 "{}requires that `{desc}` is borrowed for `{region_name}`",
278 category.description(),
285 "{}requires that {borrow_desc}borrow lasts for `{region_name}`",
286 category.description(),
291 for extra in extra_info {
293 ExtraConstraintInfo::PlaceholderFromPredicate(span) => {
294 err.span_note(*span, "due to current limitations in the borrow checker, this implies a `'static` lifetime");
299 self.add_lifetime_bound_suggestion_to_diagnostic(err, &category, span, region_name);
305 fn add_lifetime_bound_suggestion_to_diagnostic(
307 err: &mut Diagnostic,
308 category: &ConstraintCategory<'tcx>,
310 region_name: &RegionName,
312 if !span.is_desugaring(DesugaringKind::OpaqueTy) {
315 if let ConstraintCategory::OpaqueType = category {
316 let suggestable_name =
317 if region_name.was_named() { region_name.name } else { kw::UnderscoreLifetime };
320 "you can add a bound to the {}to make it last less than `'static` and match `{region_name}`",
321 category.description(),
324 err.span_suggestion_verbose(
327 format!(" + {suggestable_name}"),
328 Applicability::Unspecified,
334 impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
335 fn free_region_constraint_info(
337 borrow_region: RegionVid,
338 outlived_region: RegionVid,
339 ) -> (ConstraintCategory<'tcx>, bool, Span, Option<RegionName>, Vec<ExtraConstraintInfo>) {
340 let (blame_constraint, extra_info) = self.regioncx.best_blame_constraint(
342 NllRegionVariableOrigin::FreeRegion,
343 |r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
345 let BlameConstraint { category, from_closure, cause, .. } = blame_constraint;
347 let outlived_fr_name = self.give_region_a_name(outlived_region);
349 (category, from_closure, cause.span, outlived_fr_name, extra_info)
352 /// Returns structured explanation for *why* the borrow contains the
353 /// point from `location`. This is key for the "3-point errors"
354 /// [described in the NLL RFC][d].
358 /// - `borrow`: the borrow in question
359 /// - `location`: where the borrow occurs
360 /// - `kind_place`: if Some, this describes the statement that triggered the error.
361 /// - first half is the kind of write, if any, being performed
362 /// - second half is the place being accessed
364 /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
365 #[instrument(level = "debug", skip(self))]
366 pub(crate) fn explain_why_borrow_contains_point(
369 borrow: &BorrowData<'tcx>,
370 kind_place: Option<(WriteKind, Place<'tcx>)>,
371 ) -> BorrowExplanation<'tcx> {
372 let regioncx = &self.regioncx;
373 let body: &Body<'_> = &self.body;
374 let tcx = self.infcx.tcx;
376 let borrow_region_vid = borrow.region;
377 debug!(?borrow_region_vid);
379 let mut region_sub = self.regioncx.find_sub_region_live_at(borrow_region_vid, location);
382 let mut use_location = location;
383 let mut use_in_later_iteration_of_loop = false;
385 if region_sub == borrow_region_vid {
386 // When `region_sub` is the same as `borrow_region_vid` (the location where the borrow is
387 // issued is the same location that invalidates the reference), this is likely a loop iteration
388 // - in this case, try using the loop terminator location in `find_sub_region_live_at`.
389 if let Some(loop_terminator_location) =
390 regioncx.find_loop_terminator_location(borrow.region, body)
394 .find_sub_region_live_at(borrow_region_vid, loop_terminator_location);
395 debug!("explain_why_borrow_contains_point: region_sub in loop={:?}", region_sub);
396 use_location = loop_terminator_location;
397 use_in_later_iteration_of_loop = true;
401 match find_use::find(body, regioncx, tcx, region_sub, use_location) {
402 Some(Cause::LiveVar(local, location)) => {
403 let span = body.source_info(location).span;
405 .move_spans(Place::from(local).as_ref(), location)
406 .or_else(|| self.borrow_spans(span, location));
408 if use_in_later_iteration_of_loop {
409 let later_use = self.later_use_kind(borrow, spans, use_location);
410 BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1, later_use.2)
412 // Check if the location represents a `FakeRead`, and adapt the error
413 // message to the `FakeReadCause` it is from: in particular,
414 // the ones inserted in optimized `let var = <expr>` patterns.
415 let later_use = self.later_use_kind(borrow, spans, location);
416 BorrowExplanation::UsedLater(later_use.0, later_use.1, later_use.2)
420 Some(Cause::DropVar(local, location)) => {
421 let mut should_note_order = false;
422 if self.local_names[local].is_some()
423 && let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place
424 && let Some(borrowed_local) = place.as_local()
425 && self.local_names[borrowed_local].is_some() && local != borrowed_local
427 should_note_order = true;
430 BorrowExplanation::UsedLaterWhenDropped {
432 dropped_local: local,
438 if let Some(region) = self.to_error_region_vid(borrow_region_vid) {
439 let (category, from_closure, span, region_name, extra_info) =
440 self.free_region_constraint_info(borrow_region_vid, region);
441 if let Some(region_name) = region_name {
442 let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref());
443 BorrowExplanation::MustBeValidFor {
452 debug!("Could not generate a region name");
453 BorrowExplanation::Unexplained
456 debug!("Could not generate an error region vid");
457 BorrowExplanation::Unexplained
463 /// Determine how the borrow was later used.
464 /// First span returned points to the location of the conflicting use
465 /// Second span if `Some` is returned in the case of closures and points
466 /// to the use of the path
467 #[instrument(level = "debug", skip(self))]
470 borrow: &BorrowData<'tcx>,
471 use_spans: UseSpans<'tcx>,
473 ) -> (LaterUseKind, Span, Option<Span>) {
475 UseSpans::ClosureUse { capture_kind_span, path_span, .. } => {
476 // Used in a closure.
477 (LaterUseKind::ClosureCapture, capture_kind_span, Some(path_span))
479 UseSpans::PatUse(span)
480 | UseSpans::OtherUse(span)
481 | UseSpans::FnSelfUse { var_span: span, .. } => {
482 let block = &self.body.basic_blocks[location.block];
484 let kind = if let Some(&Statement {
485 kind: StatementKind::FakeRead(box (FakeReadCause::ForLet(_), place)),
487 }) = block.statements.get(location.statement_index)
489 if let Some(l) = place.as_local()
490 && let local_decl = &self.body.local_decls[l]
491 && local_decl.ty.is_closure()
493 LaterUseKind::ClosureCapture
495 LaterUseKind::FakeLetRead
497 } else if self.was_captured_by_trait_object(borrow) {
498 LaterUseKind::TraitCapture
499 } else if location.statement_index == block.statements.len() {
500 if let TerminatorKind::Call { func, from_hir_call: true, .. } =
501 &block.terminator().kind
503 // Just point to the function, to reduce the chance of overlapping spans.
504 let function_span = match func {
505 Operand::Constant(c) => c.span,
506 Operand::Copy(place) | Operand::Move(place) => {
507 if let Some(l) = place.as_local() {
508 let local_decl = &self.body.local_decls[l];
509 if self.local_names[l].is_none() {
510 local_decl.source_info.span
519 return (LaterUseKind::Call, function_span, None);
532 /// Checks if a borrowed value was captured by a trait object. We do this by
533 /// looking forward in the MIR from the reserve location and checking if we see
534 /// an unsized cast to a trait object on our data.
535 fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
536 // Start at the reserve location, find the place that we want to see cast to a trait object.
537 let location = borrow.reserve_location;
538 let block = &self.body[location.block];
539 let stmt = block.statements.get(location.statement_index);
540 debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
542 // We make a `queue` vector that has the locations we want to visit. As of writing, this
543 // will only ever have one item at any given time, but by using a vector, we can pop from
544 // it which simplifies the termination logic.
545 let mut queue = vec![location];
547 if let Some(Statement { kind: StatementKind::Assign(box (place, _)), .. }) = stmt {
548 if let Some(local) = place.as_local() {
557 debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
558 while let Some(current_location) = queue.pop() {
559 debug!("was_captured_by_trait: target={:?}", target);
560 let block = &self.body[current_location.block];
561 // We need to check the current location to find out if it is a terminator.
562 let is_terminator = current_location.statement_index == block.statements.len();
564 let stmt = &block.statements[current_location.statement_index];
565 debug!("was_captured_by_trait_object: stmt={:?}", stmt);
567 // The only kind of statement that we care about is assignments...
568 if let StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
569 let Some(into) = place.local_or_deref_local() else {
570 // Continue at the next location.
571 queue.push(current_location.successor_within_block());
576 // If we see a use, we should check whether it is our data, and if so
577 // update the place that we're looking for to that new place.
578 Rvalue::Use(operand) => match operand {
579 Operand::Copy(place) | Operand::Move(place) => {
580 if let Some(from) = place.as_local() {
588 // If we see an unsized cast, then if it is our data we should check
589 // whether it is being cast to a trait object.
590 Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), operand, ty) => {
592 Operand::Copy(place) | Operand::Move(place) => {
593 if let Some(from) = place.as_local() {
595 debug!("was_captured_by_trait_object: ty={:?}", ty);
596 // Check the type for a trait object.
597 return match ty.kind() {
599 ty::Ref(_, ty, _) if ty.is_trait() => true,
601 _ if ty.is_box() && ty.boxed_ty().is_trait() => {
605 _ if ty.is_trait() => true,
620 // Continue at the next location.
621 queue.push(current_location.successor_within_block());
623 // The only thing we need to do for terminators is progress to the next block.
624 let terminator = block.terminator();
625 debug!("was_captured_by_trait_object: terminator={:?}", terminator);
627 if let TerminatorKind::Call { destination, target: Some(block), args, .. } =
630 if let Some(dest) = destination.as_local() {
632 "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
635 // Check if one of the arguments to this function is the target place.
636 let found_target = args.iter().any(|arg| {
637 if let Operand::Move(place) = arg {
638 if let Some(potential) = place.as_local() {
648 // If it is, follow this to the next block and update the target.
651 queue.push(block.start_location());
657 debug!("was_captured_by_trait: queue={:?}", queue);
660 // We didn't find anything and ran out of locations to check.