1 //! Print diagnostics to explain why values are borrowed.
3 use std::collections::VecDeque;
5 use rustc::infer::NLLRegionVariableOrigin;
7 Body, CastKind, ConstraintCategory, FakeReadCause, Local, Location, Operand, Place, Rvalue,
8 Statement, StatementKind, TerminatorKind,
10 use rustc::ty::adjustment::PointerCast;
11 use rustc::ty::{self, RegionVid, TyCtxt};
12 use rustc_data_structures::fx::FxHashSet;
13 use rustc_errors::{Applicability, DiagnosticBuilder};
14 use rustc_index::vec::IndexVec;
15 use rustc_span::symbol::Symbol;
18 use crate::borrow_check::{
19 borrow_set::BorrowData, diagnostics::RegionErrorNamingCtx, nll::ConstraintDescription,
20 region_infer::Cause, MirBorrowckCtxt, WriteKind,
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 // FIXME: use span_suggestion instead, highlighting the
160 // whole block tail expression.
161 let msg = if info.tail_result_is_ignored {
162 "The temporary is part of an expression at the end of a block. \
163 Consider adding semicolon after the expression so its temporaries \
164 are dropped sooner, before the local variables declared by the \
167 "The temporary is part of an expression at the end of a block. \
168 Consider forcing this temporary to be dropped sooner, before \
169 the block's local variables are dropped. \
170 For example, you could save the expression's value in a new \
171 local variable `x` and then make `x` be the expression \
172 at the end of the block."
180 BorrowExplanation::MustBeValidFor {
187 region_name.highlight_region_name(err);
189 if let Some(desc) = opt_place_desc {
193 "{}requires that `{}` is borrowed for `{}`",
194 category.description(),
203 "{}requires that {}borrow lasts for `{}`",
204 category.description(),
211 self.add_lifetime_bound_suggestion_to_diagnostic(
222 pub(in crate::borrow_check) fn add_lifetime_bound_suggestion_to_diagnostic<'tcx>(
225 err: &mut DiagnosticBuilder<'_>,
226 category: &ConstraintCategory,
228 region_name: &RegionName,
231 ConstraintCategory::OpaqueType => {
232 if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(span) {
233 let suggestable_name = if region_name.was_named() {
234 region_name.to_string()
242 "you can add a bound to the {}to make it last less than \
243 `'static` and match `{}`",
244 category.description(),
247 format!("{} + {}", snippet, suggestable_name),
248 Applicability::Unspecified,
257 impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
258 fn free_region_constraint_info(
260 borrow_region: RegionVid,
261 outlived_region: RegionVid,
262 ) -> (ConstraintCategory, bool, Span, Option<RegionName>) {
263 let (category, from_closure, span) = self.nonlexical_regioncx.best_blame_constraint(
266 NLLRegionVariableOrigin::FreeRegion,
268 self.nonlexical_regioncx.provides_universal_region(
276 let mut renctx = RegionErrorNamingCtx::new();
277 let outlived_fr_name =
278 self.nonlexical_regioncx.give_region_a_name(self, &mut renctx, outlived_region);
279 // TODO(mark-i-m): just return the region and let the caller name it
281 (category, from_closure, span, outlived_fr_name)
284 /// Returns structured explanation for *why* the borrow contains the
285 /// point from `location`. This is key for the "3-point errors"
286 /// [described in the NLL RFC][d].
290 /// - `borrow`: the borrow in question
291 /// - `location`: where the borrow occurs
292 /// - `kind_place`: if Some, this describes the statement that triggered the error.
293 /// - first half is the kind of write, if any, being performed
294 /// - second half is the place being accessed
296 /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
297 pub(in crate::borrow_check) fn explain_why_borrow_contains_point(
300 borrow: &BorrowData<'tcx>,
301 kind_place: Option<(WriteKind, &Place<'tcx>)>,
302 ) -> BorrowExplanation {
304 "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})",
305 location, borrow, kind_place
308 let regioncx = &self.nonlexical_regioncx;
309 let body: &Body<'_> = &self.body;
310 let tcx = self.infcx.tcx;
312 let borrow_region_vid = borrow.region;
313 debug!("explain_why_borrow_contains_point: borrow_region_vid={:?}", borrow_region_vid);
316 self.nonlexical_regioncx.find_sub_region_live_at(borrow_region_vid, location);
317 debug!("explain_why_borrow_contains_point: region_sub={:?}", region_sub);
319 match find_use::find(body, regioncx, tcx, region_sub, location) {
320 Some(Cause::LiveVar(local, location)) => {
321 let span = body.source_info(location).span;
323 .move_spans(Place::from(local).as_ref(), location)
324 .or_else(|| self.borrow_spans(span, location));
326 let borrow_location = location;
327 if self.is_use_in_later_iteration_of_loop(borrow_location, location) {
328 let later_use = self.later_use_kind(borrow, spans, location);
329 BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1)
331 // Check if the location represents a `FakeRead`, and adapt the error
332 // message to the `FakeReadCause` it is from: in particular,
333 // the ones inserted in optimized `let var = <expr>` patterns.
334 let later_use = self.later_use_kind(borrow, spans, location);
335 BorrowExplanation::UsedLater(later_use.0, later_use.1)
339 Some(Cause::DropVar(local, location)) => {
340 let mut should_note_order = false;
341 if self.local_names[local].is_some() {
342 if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place {
343 if let Some(borrowed_local) = place.as_local() {
344 if self.local_names[borrowed_local].is_some() && local != borrowed_local
346 should_note_order = true;
352 BorrowExplanation::UsedLaterWhenDropped {
354 dropped_local: local,
360 if let Some(region) = regioncx.to_error_region_vid(borrow_region_vid) {
361 let (category, from_closure, span, region_name) =
362 self.free_region_constraint_info(borrow_region_vid, region);
363 if let Some(region_name) = region_name {
364 let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref());
365 BorrowExplanation::MustBeValidFor {
374 "explain_why_borrow_contains_point: \
375 Could not generate a region name"
377 BorrowExplanation::Unexplained
381 "explain_why_borrow_contains_point: \
382 Could not generate an error region vid"
384 BorrowExplanation::Unexplained
390 /// true if `borrow_location` can reach `use_location` by going through a loop and
391 /// `use_location` is also inside of that loop
392 fn is_use_in_later_iteration_of_loop(
394 borrow_location: Location,
395 use_location: Location,
397 let back_edge = self.reach_through_backedge(borrow_location, use_location);
398 back_edge.map_or(false, |back_edge| self.can_reach_head_of_loop(use_location, back_edge))
401 /// Returns the outmost back edge if `from` location can reach `to` location passing through
403 fn reach_through_backedge(&self, from: Location, to: Location) -> Option<Location> {
404 let mut visited_locations = FxHashSet::default();
405 let mut pending_locations = VecDeque::new();
406 visited_locations.insert(from);
407 pending_locations.push_back(from);
408 debug!("reach_through_backedge: from={:?} to={:?}", from, to,);
410 let mut outmost_back_edge = None;
411 while let Some(location) = pending_locations.pop_front() {
413 "reach_through_backedge: location={:?} outmost_back_edge={:?}
414 pending_locations={:?} visited_locations={:?}",
415 location, outmost_back_edge, pending_locations, visited_locations
418 if location == to && outmost_back_edge.is_some() {
419 // We've managed to reach the use location
420 debug!("reach_through_backedge: found!");
421 return outmost_back_edge;
424 let block = &self.body.basic_blocks()[location.block];
426 if location.statement_index < block.statements.len() {
427 let successor = location.successor_within_block();
428 if visited_locations.insert(successor) {
429 pending_locations.push_back(successor);
432 pending_locations.extend(
436 .map(|bb| Location { statement_index: 0, block: *bb })
437 .filter(|s| visited_locations.insert(*s))
439 if self.is_back_edge(location, s) {
440 match outmost_back_edge {
442 outmost_back_edge = Some(location);
446 if location.dominates(back_edge, &self.dominators) =>
448 outmost_back_edge = Some(location);
464 /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the
465 /// intermediate nodes
466 fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool {
467 self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default())
470 fn find_loop_head_dfs(
474 visited_locations: &mut FxHashSet<Location>,
476 visited_locations.insert(from);
478 if from == loop_head {
482 if loop_head.dominates(from, &self.dominators) {
483 let block = &self.body.basic_blocks()[from.block];
485 if from.statement_index < block.statements.len() {
486 let successor = from.successor_within_block();
488 if !visited_locations.contains(&successor)
489 && self.find_loop_head_dfs(successor, loop_head, visited_locations)
494 for bb in block.terminator().successors() {
495 let successor = Location { statement_index: 0, block: *bb };
497 if !visited_locations.contains(&successor)
498 && self.find_loop_head_dfs(successor, loop_head, visited_locations)
509 /// True if an edge `source -> target` is a backedge -- in other words, if the target
510 /// dominates the source.
511 fn is_back_edge(&self, source: Location, target: Location) -> bool {
512 target.dominates(source, &self.dominators)
515 /// Determine how the borrow was later used.
518 borrow: &BorrowData<'tcx>,
521 ) -> (LaterUseKind, Span) {
523 UseSpans::ClosureUse { var_span, .. } => {
524 // Used in a closure.
525 (LaterUseKind::ClosureCapture, var_span)
527 UseSpans::OtherUse(span) => {
528 let block = &self.body.basic_blocks()[location.block];
530 let kind = if let Some(&Statement {
531 kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
533 }) = block.statements.get(location.statement_index)
535 LaterUseKind::FakeLetRead
536 } else if self.was_captured_by_trait_object(borrow) {
537 LaterUseKind::TraitCapture
538 } else if location.statement_index == block.statements.len() {
539 if let TerminatorKind::Call { ref func, from_hir_call: true, .. } =
540 block.terminator().kind
542 // Just point to the function, to reduce the chance of overlapping spans.
543 let function_span = match func {
544 Operand::Constant(c) => c.span,
545 Operand::Copy(place) | Operand::Move(place) => {
546 if let Some(l) = place.as_local() {
547 let local_decl = &self.body.local_decls[l];
548 if self.local_names[l].is_none() {
549 local_decl.source_info.span
558 return (LaterUseKind::Call, function_span);
571 /// Checks if a borrowed value was captured by a trait object. We do this by
572 /// looking forward in the MIR from the reserve location and checking if we see
573 /// a unsized cast to a trait object on our data.
574 fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
575 // Start at the reserve location, find the place that we want to see cast to a trait object.
576 let location = borrow.reserve_location;
577 let block = &self.body[location.block];
578 let stmt = block.statements.get(location.statement_index);
579 debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
581 // We make a `queue` vector that has the locations we want to visit. As of writing, this
582 // will only ever have one item at any given time, but by using a vector, we can pop from
583 // it which simplifies the termination logic.
584 let mut queue = vec![location];
585 let mut target = if let Some(&Statement {
586 kind: StatementKind::Assign(box (ref place, _)),
590 if let Some(local) = place.as_local() {
599 debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
600 while let Some(current_location) = queue.pop() {
601 debug!("was_captured_by_trait: target={:?}", target);
602 let block = &self.body[current_location.block];
603 // We need to check the current location to find out if it is a terminator.
604 let is_terminator = current_location.statement_index == block.statements.len();
606 let stmt = &block.statements[current_location.statement_index];
607 debug!("was_captured_by_trait_object: stmt={:?}", stmt);
609 // The only kind of statement that we care about is assignments...
610 if let StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
611 let into = match place.local_or_deref_local() {
614 // Continue at the next location.
615 queue.push(current_location.successor_within_block());
621 // If we see a use, we should check whether it is our data, and if so
622 // update the place that we're looking for to that new place.
623 Rvalue::Use(operand) => match operand {
624 Operand::Copy(place) | Operand::Move(place) => {
625 if let Some(from) = place.as_local() {
633 // If we see a unsized cast, then if it is our data we should check
634 // whether it is being cast to a trait object.
635 Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), operand, ty) => {
637 Operand::Copy(place) | Operand::Move(place) => {
638 if let Some(from) = place.as_local() {
640 debug!("was_captured_by_trait_object: ty={:?}", ty);
641 // Check the type for a trait object.
642 return match ty.kind {
644 ty::Ref(_, ty, _) if ty.is_trait() => true,
646 _ if ty.is_box() && ty.boxed_ty().is_trait() => {
650 _ if ty.is_trait() => true,
665 // Continue at the next location.
666 queue.push(current_location.successor_within_block());
668 // The only thing we need to do for terminators is progress to the next block.
669 let terminator = block.terminator();
670 debug!("was_captured_by_trait_object: terminator={:?}", terminator);
672 if let TerminatorKind::Call { destination: Some((place, block)), args, .. } =
675 if let Some(dest) = place.as_local() {
677 "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
680 // Check if one of the arguments to this function is the target place.
681 let found_target = args.iter().any(|arg| {
682 if let Operand::Move(place) = arg {
683 if let Some(potential) = place.as_local() {
693 // If it is, follow this to the next block and update the target.
696 queue.push(block.start_location());
702 debug!("was_captured_by_trait: queue={:?}", queue);
705 // We didn't find anything and ran out of locations to check.