1 //! Print diagnostics to explain why values are borrowed.
3 use std::collections::VecDeque;
5 use rustc_data_structures::fx::FxHashSet;
6 use rustc_errors::{Applicability, DiagnosticBuilder};
7 use rustc_index::vec::IndexVec;
8 use rustc_infer::infer::NLLRegionVariableOrigin;
9 use rustc_middle::mir::{
10 Body, CastKind, ConstraintCategory, FakeReadCause, Local, Location, Operand, Place, Rvalue,
11 Statement, StatementKind, TerminatorKind,
13 use rustc_middle::ty::adjustment::PointerCast;
14 use rustc_middle::ty::{self, RegionVid, TyCtxt};
15 use rustc_span::symbol::Symbol;
18 use crate::borrow_check::{
19 borrow_set::BorrowData, nll::ConstraintDescription, region_infer::Cause, MirBorrowckCtxt,
23 use super::{find_use, RegionName, UseSpans};
26 pub(in crate::borrow_check) enum BorrowExplanation {
27 UsedLater(LaterUseKind, Span),
28 UsedLaterInLoop(LaterUseKind, Span),
29 UsedLaterWhenDropped {
32 should_note_order: bool,
35 category: ConstraintCategory,
38 region_name: RegionName,
39 opt_place_desc: Option<String>,
44 #[derive(Clone, Copy, Debug)]
45 pub(in crate::borrow_check) enum LaterUseKind {
53 impl BorrowExplanation {
54 pub(in crate::borrow_check) fn is_explained(&self) -> bool {
56 BorrowExplanation::Unexplained => false,
60 pub(in crate::borrow_check) fn add_explanation_to_diagnostic<'tcx>(
64 local_names: &IndexVec<Local, Option<Symbol>>,
65 err: &mut DiagnosticBuilder<'_>,
67 borrow_span: Option<Span>,
70 BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => {
71 let message = match later_use_kind {
72 LaterUseKind::TraitCapture => "captured here by trait object",
73 LaterUseKind::ClosureCapture => "captured here by closure",
74 LaterUseKind::Call => "used by call",
75 LaterUseKind::FakeLetRead => "stored here",
76 LaterUseKind::Other => "used here",
78 if !borrow_span.map(|sp| sp.overlaps(var_or_use_span)).unwrap_or(false) {
81 format!("{}borrow later {}", borrow_desc, message),
85 BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => {
86 let message = match later_use_kind {
87 LaterUseKind::TraitCapture => {
88 "borrow captured here by trait object, in later iteration of loop"
90 LaterUseKind::ClosureCapture => {
91 "borrow captured here by closure, in later iteration of loop"
93 LaterUseKind::Call => "borrow used by call, in later iteration of loop",
94 LaterUseKind::FakeLetRead => "borrow later stored here",
95 LaterUseKind::Other => "borrow used here, in later iteration of loop",
97 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
99 BorrowExplanation::UsedLaterWhenDropped {
104 let local_decl = &body.local_decls[dropped_local];
105 let (dtor_desc, type_desc) = match local_decl.ty.kind() {
106 // If type is an ADT that implements Drop, then
107 // simplify output by reporting just the ADT name.
108 ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => {
109 ("`Drop` code", format!("type `{}`", tcx.def_path_str(adt.did)))
112 // Otherwise, just report the whole type (and use
113 // the intentionally fuzzy phrase "destructor")
114 ty::Closure(..) => ("destructor", "closure".to_owned()),
115 ty::Generator(..) => ("destructor", "generator".to_owned()),
117 _ => ("destructor", format!("type `{}`", local_decl.ty)),
120 match local_names[dropped_local] {
121 Some(local_name) if !local_decl.from_compiler_desugaring() => {
122 let message = format!(
123 "{B}borrow might be used here, when `{LOC}` is dropped \
124 and runs the {DTOR} for {TYPE}",
130 err.span_label(body.source_info(drop_loc).span, message);
132 if should_note_order {
134 "values in a scope are dropped \
135 in the opposite order they are defined",
141 local_decl.source_info.span,
143 "a temporary with access to the {B}borrow \
144 is created here ...",
148 let message = format!(
149 "... and the {B}borrow might be used here, \
150 when that temporary is dropped \
151 and runs the {DTOR} for {TYPE}",
156 err.span_label(body.source_info(drop_loc).span, message);
158 if let Some(info) = &local_decl.is_block_tail {
159 if info.tail_result_is_ignored {
160 err.span_suggestion_verbose(
161 info.span.shrink_to_hi(),
162 "consider adding semicolon after the expression so its \
163 temporaries are dropped sooner, before the local variables \
164 declared by the block are dropped",
166 Applicability::MaybeIncorrect,
170 "the temporary is part of an expression at the end of a \
171 block;\nconsider forcing this temporary to be dropped sooner, \
172 before the block's local variables are dropped",
174 err.multipart_suggestion(
175 "for example, you could save the expression's value in a new \
176 local variable `x` and then make `x` be the expression at the \
179 (info.span.shrink_to_lo(), "let x = ".to_string()),
180 (info.span.shrink_to_hi(), "; x".to_string()),
182 Applicability::MaybeIncorrect,
189 BorrowExplanation::MustBeValidFor {
196 region_name.highlight_region_name(err);
198 if let Some(desc) = opt_place_desc {
202 "{}requires that `{}` is borrowed for `{}`",
203 category.description(),
212 "{}requires that {}borrow lasts for `{}`",
213 category.description(),
220 self.add_lifetime_bound_suggestion_to_diagnostic(err, &category, span, region_name);
225 pub(in crate::borrow_check) fn add_lifetime_bound_suggestion_to_diagnostic(
227 err: &mut DiagnosticBuilder<'_>,
228 category: &ConstraintCategory,
230 region_name: &RegionName,
232 if let ConstraintCategory::OpaqueType = category {
233 let suggestable_name =
234 if region_name.was_named() { region_name.to_string() } else { "'_".to_string() };
237 "you can add a bound to the {}to make it last less than `'static` and match `{}`",
238 category.description(),
242 err.span_suggestion_verbose(
245 format!(" + {}", suggestable_name),
246 Applicability::Unspecified,
252 impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
253 fn free_region_constraint_info(
255 borrow_region: RegionVid,
256 outlived_region: RegionVid,
257 ) -> (ConstraintCategory, bool, Span, Option<RegionName>) {
258 let (category, from_closure, span) = self.regioncx.best_blame_constraint(
261 NLLRegionVariableOrigin::FreeRegion,
262 |r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
265 let outlived_fr_name = self.give_region_a_name(outlived_region);
267 (category, from_closure, span, outlived_fr_name)
270 /// Returns structured explanation for *why* the borrow contains the
271 /// point from `location`. This is key for the "3-point errors"
272 /// [described in the NLL RFC][d].
276 /// - `borrow`: the borrow in question
277 /// - `location`: where the borrow occurs
278 /// - `kind_place`: if Some, this describes the statement that triggered the error.
279 /// - first half is the kind of write, if any, being performed
280 /// - second half is the place being accessed
282 /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
283 pub(in crate::borrow_check) fn explain_why_borrow_contains_point(
286 borrow: &BorrowData<'tcx>,
287 kind_place: Option<(WriteKind, Place<'tcx>)>,
288 ) -> BorrowExplanation {
290 "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})",
291 location, borrow, kind_place
294 let regioncx = &self.regioncx;
295 let body: &Body<'_> = &self.body;
296 let tcx = self.infcx.tcx;
298 let borrow_region_vid = borrow.region;
299 debug!("explain_why_borrow_contains_point: borrow_region_vid={:?}", borrow_region_vid);
301 let region_sub = self.regioncx.find_sub_region_live_at(borrow_region_vid, location);
302 debug!("explain_why_borrow_contains_point: region_sub={:?}", region_sub);
304 match find_use::find(body, regioncx, tcx, region_sub, location) {
305 Some(Cause::LiveVar(local, location)) => {
306 let span = body.source_info(location).span;
308 .move_spans(Place::from(local).as_ref(), location)
309 .or_else(|| self.borrow_spans(span, location));
311 let borrow_location = location;
312 if self.is_use_in_later_iteration_of_loop(borrow_location, location) {
313 let later_use = self.later_use_kind(borrow, spans, location);
314 BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1)
316 // Check if the location represents a `FakeRead`, and adapt the error
317 // message to the `FakeReadCause` it is from: in particular,
318 // the ones inserted in optimized `let var = <expr>` patterns.
319 let later_use = self.later_use_kind(borrow, spans, location);
320 BorrowExplanation::UsedLater(later_use.0, later_use.1)
324 Some(Cause::DropVar(local, location)) => {
325 let mut should_note_order = false;
326 if self.local_names[local].is_some() {
327 if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place {
328 if let Some(borrowed_local) = place.as_local() {
329 if self.local_names[borrowed_local].is_some() && local != borrowed_local
331 should_note_order = true;
337 BorrowExplanation::UsedLaterWhenDropped {
339 dropped_local: local,
345 if let Some(region) = self.to_error_region_vid(borrow_region_vid) {
346 let (category, from_closure, span, region_name) =
347 self.free_region_constraint_info(borrow_region_vid, region);
348 if let Some(region_name) = region_name {
349 let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref());
350 BorrowExplanation::MustBeValidFor {
359 "explain_why_borrow_contains_point: \
360 Could not generate a region name"
362 BorrowExplanation::Unexplained
366 "explain_why_borrow_contains_point: \
367 Could not generate an error region vid"
369 BorrowExplanation::Unexplained
375 /// true if `borrow_location` can reach `use_location` by going through a loop and
376 /// `use_location` is also inside of that loop
377 fn is_use_in_later_iteration_of_loop(
379 borrow_location: Location,
380 use_location: Location,
382 let back_edge = self.reach_through_backedge(borrow_location, use_location);
383 back_edge.map_or(false, |back_edge| self.can_reach_head_of_loop(use_location, back_edge))
386 /// Returns the outmost back edge if `from` location can reach `to` location passing through
388 fn reach_through_backedge(&self, from: Location, to: Location) -> Option<Location> {
389 let mut visited_locations = FxHashSet::default();
390 let mut pending_locations = VecDeque::new();
391 visited_locations.insert(from);
392 pending_locations.push_back(from);
393 debug!("reach_through_backedge: from={:?} to={:?}", from, to,);
395 let mut outmost_back_edge = None;
396 while let Some(location) = pending_locations.pop_front() {
398 "reach_through_backedge: location={:?} outmost_back_edge={:?}
399 pending_locations={:?} visited_locations={:?}",
400 location, outmost_back_edge, pending_locations, visited_locations
403 if location == to && outmost_back_edge.is_some() {
404 // We've managed to reach the use location
405 debug!("reach_through_backedge: found!");
406 return outmost_back_edge;
409 let block = &self.body.basic_blocks()[location.block];
411 if location.statement_index < block.statements.len() {
412 let successor = location.successor_within_block();
413 if visited_locations.insert(successor) {
414 pending_locations.push_back(successor);
417 pending_locations.extend(
421 .map(|bb| Location { statement_index: 0, block: *bb })
422 .filter(|s| visited_locations.insert(*s))
424 if self.is_back_edge(location, s) {
425 match outmost_back_edge {
427 outmost_back_edge = Some(location);
431 if location.dominates(back_edge, &self.dominators) =>
433 outmost_back_edge = Some(location);
449 /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the
450 /// intermediate nodes
451 fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool {
452 self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default())
455 fn find_loop_head_dfs(
459 visited_locations: &mut FxHashSet<Location>,
461 visited_locations.insert(from);
463 if from == loop_head {
467 if loop_head.dominates(from, &self.dominators) {
468 let block = &self.body.basic_blocks()[from.block];
470 if from.statement_index < block.statements.len() {
471 let successor = from.successor_within_block();
473 if !visited_locations.contains(&successor)
474 && self.find_loop_head_dfs(successor, loop_head, visited_locations)
479 for bb in block.terminator().successors() {
480 let successor = Location { statement_index: 0, block: *bb };
482 if !visited_locations.contains(&successor)
483 && self.find_loop_head_dfs(successor, loop_head, visited_locations)
494 /// True if an edge `source -> target` is a backedge -- in other words, if the target
495 /// dominates the source.
496 fn is_back_edge(&self, source: Location, target: Location) -> bool {
497 target.dominates(source, &self.dominators)
500 /// Determine how the borrow was later used.
503 borrow: &BorrowData<'tcx>,
504 use_spans: UseSpans<'tcx>,
506 ) -> (LaterUseKind, Span) {
508 UseSpans::ClosureUse { var_span, .. } => {
509 // Used in a closure.
510 (LaterUseKind::ClosureCapture, var_span)
512 UseSpans::PatUse(span)
513 | UseSpans::OtherUse(span)
514 | UseSpans::FnSelfUse { var_span: span, .. } => {
515 let block = &self.body.basic_blocks()[location.block];
517 let kind = if let Some(&Statement {
518 kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
520 }) = block.statements.get(location.statement_index)
522 LaterUseKind::FakeLetRead
523 } else if self.was_captured_by_trait_object(borrow) {
524 LaterUseKind::TraitCapture
525 } else if location.statement_index == block.statements.len() {
526 if let TerminatorKind::Call { ref func, from_hir_call: true, .. } =
527 block.terminator().kind
529 // Just point to the function, to reduce the chance of overlapping spans.
530 let function_span = match func {
531 Operand::Constant(c) => c.span,
532 Operand::Copy(place) | Operand::Move(place) => {
533 if let Some(l) = place.as_local() {
534 let local_decl = &self.body.local_decls[l];
535 if self.local_names[l].is_none() {
536 local_decl.source_info.span
545 return (LaterUseKind::Call, function_span);
558 /// Checks if a borrowed value was captured by a trait object. We do this by
559 /// looking forward in the MIR from the reserve location and checking if we see
560 /// a unsized cast to a trait object on our data.
561 fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
562 // Start at the reserve location, find the place that we want to see cast to a trait object.
563 let location = borrow.reserve_location;
564 let block = &self.body[location.block];
565 let stmt = block.statements.get(location.statement_index);
566 debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
568 // We make a `queue` vector that has the locations we want to visit. As of writing, this
569 // will only ever have one item at any given time, but by using a vector, we can pop from
570 // it which simplifies the termination logic.
571 let mut queue = vec![location];
572 let mut target = if let Some(&Statement {
573 kind: StatementKind::Assign(box (ref place, _)),
577 if let Some(local) = place.as_local() {
586 debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
587 while let Some(current_location) = queue.pop() {
588 debug!("was_captured_by_trait: target={:?}", target);
589 let block = &self.body[current_location.block];
590 // We need to check the current location to find out if it is a terminator.
591 let is_terminator = current_location.statement_index == block.statements.len();
593 let stmt = &block.statements[current_location.statement_index];
594 debug!("was_captured_by_trait_object: stmt={:?}", stmt);
596 // The only kind of statement that we care about is assignments...
597 if let StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
598 let into = match place.local_or_deref_local() {
601 // Continue at the next location.
602 queue.push(current_location.successor_within_block());
608 // If we see a use, we should check whether it is our data, and if so
609 // update the place that we're looking for to that new place.
610 Rvalue::Use(operand) => match operand {
611 Operand::Copy(place) | Operand::Move(place) => {
612 if let Some(from) = place.as_local() {
620 // If we see a unsized cast, then if it is our data we should check
621 // whether it is being cast to a trait object.
622 Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), operand, ty) => {
624 Operand::Copy(place) | Operand::Move(place) => {
625 if let Some(from) = place.as_local() {
627 debug!("was_captured_by_trait_object: ty={:?}", ty);
628 // Check the type for a trait object.
629 return match ty.kind() {
631 ty::Ref(_, ty, _) if ty.is_trait() => true,
633 _ if ty.is_box() && ty.boxed_ty().is_trait() => {
637 _ if ty.is_trait() => true,
652 // Continue at the next location.
653 queue.push(current_location.successor_within_block());
655 // The only thing we need to do for terminators is progress to the next block.
656 let terminator = block.terminator();
657 debug!("was_captured_by_trait_object: terminator={:?}", terminator);
659 if let TerminatorKind::Call { destination: Some((place, block)), args, .. } =
662 if let Some(dest) = place.as_local() {
664 "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
667 // Check if one of the arguments to this function is the target place.
668 let found_target = args.iter().any(|arg| {
669 if let Operand::Move(place) = arg {
670 if let Some(potential) = place.as_local() {
680 // If it is, follow this to the next block and update the target.
683 queue.push(block.start_location());
689 debug!("was_captured_by_trait: queue={:?}", queue);
692 // We didn't find anything and ran out of locations to check.