1 use std::collections::VecDeque;
3 use crate::borrow_check::borrow_set::BorrowData;
4 use crate::borrow_check::error_reporting::UseSpans;
5 use crate::borrow_check::nll::ConstraintDescription;
6 use crate::borrow_check::nll::region_infer::{Cause, RegionName};
7 use crate::borrow_check::{Context, MirBorrowckCtxt, WriteKind};
8 use rustc::ty::{self, TyCtxt};
10 CastKind, ConstraintCategory, FakeReadCause, Local, Location, Mir, Operand,
11 Place, Projection, ProjectionElem, Rvalue, Statement, StatementKind,
14 use rustc_data_structures::fx::FxHashSet;
15 use rustc_errors::DiagnosticBuilder;
20 pub(in crate::borrow_check) enum BorrowExplanation {
21 UsedLater(LaterUseKind, Span),
22 UsedLaterInLoop(LaterUseKind, Span),
23 UsedLaterWhenDropped {
26 should_note_order: bool,
29 category: ConstraintCategory,
32 region_name: RegionName,
33 opt_place_desc: Option<String>,
38 #[derive(Clone, Copy)]
39 pub(in crate::borrow_check) enum LaterUseKind {
47 impl BorrowExplanation {
48 pub(in crate::borrow_check) fn is_explained(&self) -> bool {
50 BorrowExplanation::Unexplained => false,
54 pub(in crate::borrow_check) fn add_explanation_to_diagnostic<'cx, 'gcx, 'tcx>(
56 tcx: TyCtxt<'cx, 'gcx, 'tcx>,
58 err: &mut DiagnosticBuilder<'_>,
62 BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => {
63 let message = match later_use_kind {
64 LaterUseKind::TraitCapture => "borrow later captured here by trait object",
65 LaterUseKind::ClosureCapture => "borrow later captured here by closure",
66 LaterUseKind::Call => "borrow later used by call",
67 LaterUseKind::FakeLetRead => "borrow later stored here",
68 LaterUseKind::Other => "borrow later used here",
70 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
72 BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => {
73 let message = match later_use_kind {
74 LaterUseKind::TraitCapture =>
75 "borrow captured here by trait object, in later iteration of loop",
76 LaterUseKind::ClosureCapture =>
77 "borrow captured here by closure, in later iteration of loop",
78 LaterUseKind::Call => "borrow used by call, in later iteration of loop",
79 LaterUseKind::FakeLetRead => "borrow later stored here",
80 LaterUseKind::Other => "borrow used here, in later iteration of loop",
82 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
84 BorrowExplanation::UsedLaterWhenDropped { drop_loc, dropped_local,
85 should_note_order } =>
87 let local_decl = &mir.local_decls[dropped_local];
88 let (dtor_desc, type_desc) = match local_decl.ty.sty {
89 // If type is an ADT that implements Drop, then
90 // simplify output by reporting just the ADT name.
91 ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() =>
92 ("`Drop` code", format!("type `{}`", tcx.item_path_str(adt.did))),
94 // Otherwise, just report the whole type (and use
95 // the intentionally fuzzy phrase "destructor")
97 ("destructor", "closure".to_owned()),
99 ("destructor", "generator".to_owned()),
101 _ => ("destructor", format!("type `{}`", local_decl.ty)),
104 match local_decl.name {
105 Some(local_name) => {
107 format!("{B}borrow might be used here, when `{LOC}` is dropped \
108 and runs the {DTOR} for {TYPE}",
109 B=borrow_desc, LOC=local_name, TYPE=type_desc, DTOR=dtor_desc);
110 err.span_label(mir.source_info(drop_loc).span, message);
112 if should_note_order {
114 "values in a scope are dropped \
115 in the opposite order they are defined",
120 err.span_label(local_decl.source_info.span,
121 format!("a temporary with access to the {B}borrow \
122 is created here ...",
125 format!("... and the {B}borrow might be used here, \
126 when that temporary is dropped \
127 and runs the {DTOR} for {TYPE}",
128 B=borrow_desc, TYPE=type_desc, DTOR=dtor_desc);
129 err.span_label(mir.source_info(drop_loc).span, message);
131 if let Some(info) = &local_decl.is_block_tail {
132 // FIXME: use span_suggestion instead, highlighting the
133 // whole block tail expression.
134 let msg = if info.tail_result_is_ignored {
135 "The temporary is part of an expression at the end of a block. \
136 Consider adding semicolon after the expression so its temporaries \
137 are dropped sooner, before the local variables declared by the \
140 "The temporary is part of an expression at the end of a block. \
141 Consider forcing this temporary to be dropped sooner, before \
142 the block's local variables are dropped. \
143 For example, you could save the expression's value in a new \
144 local variable `x` and then make `x` be the expression \
145 at the end of the block."
153 BorrowExplanation::MustBeValidFor {
160 region_name.highlight_region_name(err);
162 if let Some(desc) = opt_place_desc {
163 err.span_label(span, format!(
164 "{}requires that `{}` is borrowed for `{}`",
165 category.description(), desc, region_name,
168 err.span_label(span, format!(
169 "{}requires that {}borrow lasts for `{}`",
170 category.description(), borrow_desc, region_name,
179 impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
180 /// Returns structured explanation for *why* the borrow contains the
181 /// point from `context`. This is key for the "3-point errors"
182 /// [described in the NLL RFC][d].
186 /// - `borrow`: the borrow in question
187 /// - `context`: where the borrow occurs
188 /// - `kind_place`: if Some, this describes the statement that triggered the error.
189 /// - first half is the kind of write, if any, being performed
190 /// - second half is the place being accessed
192 /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
193 pub(in crate::borrow_check) fn explain_why_borrow_contains_point(
196 borrow: &BorrowData<'tcx>,
197 kind_place: Option<(WriteKind, &Place<'tcx>)>,
198 ) -> BorrowExplanation {
200 "explain_why_borrow_contains_point(context={:?}, borrow={:?}, kind_place={:?})",
201 context, borrow, kind_place
204 let regioncx = &self.nonlexical_regioncx;
206 let tcx = self.infcx.tcx;
208 let borrow_region_vid = borrow.region;
210 "explain_why_borrow_contains_point: borrow_region_vid={:?}",
214 let region_sub = regioncx.find_sub_region_live_at(borrow_region_vid, context.loc);
216 "explain_why_borrow_contains_point: region_sub={:?}",
220 match find_use::find(mir, regioncx, tcx, region_sub, context.loc) {
221 Some(Cause::LiveVar(local, location)) => {
222 let span = mir.source_info(location).span;
223 let spans = self.move_spans(&Place::Local(local), location)
224 .or_else(|| self.borrow_spans(span, location));
226 let borrow_location = context.loc;
227 if self.is_use_in_later_iteration_of_loop(borrow_location, location) {
228 let later_use = self.later_use_kind(borrow, spans, location);
229 BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1)
231 // Check if the location represents a `FakeRead`, and adapt the error
232 // message to the `FakeReadCause` it is from: in particular,
233 // the ones inserted in optimized `let var = <expr>` patterns.
234 let later_use = self.later_use_kind(borrow, spans, location);
235 BorrowExplanation::UsedLater(later_use.0, later_use.1)
239 Some(Cause::DropVar(local, location)) => {
240 let mut should_note_order = false;
241 if mir.local_decls[local].name.is_some() {
242 if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place {
243 if let Place::Local(borrowed_local) = place {
244 let dropped_local_scope = mir.local_decls[local].visibility_scope;
245 let borrowed_local_scope =
246 mir.local_decls[*borrowed_local].visibility_scope;
248 if mir.is_sub_scope(borrowed_local_scope, dropped_local_scope)
249 && local != *borrowed_local
251 should_note_order = true;
257 BorrowExplanation::UsedLaterWhenDropped {
259 dropped_local: local,
264 None => if let Some(region) = regioncx.to_error_region_vid(borrow_region_vid) {
265 let (category, from_closure, span, region_name) = self
267 .free_region_constraint_info(
274 if let Some(region_name) = region_name {
275 let opt_place_desc = self.describe_place(&borrow.borrowed_place);
276 BorrowExplanation::MustBeValidFor {
284 BorrowExplanation::Unexplained
287 BorrowExplanation::Unexplained
292 /// true if `borrow_location` can reach `use_location` by going through a loop and
293 /// `use_location` is also inside of that loop
294 fn is_use_in_later_iteration_of_loop(
296 borrow_location: Location,
297 use_location: Location,
299 let back_edge = self.reach_through_backedge(borrow_location, use_location);
300 back_edge.map_or(false, |back_edge| {
301 self.can_reach_head_of_loop(use_location, back_edge)
305 /// Returns the outmost back edge if `from` location can reach `to` location passing through
307 fn reach_through_backedge(&self, from: Location, to: Location) -> Option<Location> {
308 let mut visited_locations = FxHashSet::default();
309 let mut pending_locations = VecDeque::new();
310 visited_locations.insert(from);
311 pending_locations.push_back(from);
312 debug!("reach_through_backedge: from={:?} to={:?}", from, to,);
314 let mut outmost_back_edge = None;
315 while let Some(location) = pending_locations.pop_front() {
317 "reach_through_backedge: location={:?} outmost_back_edge={:?}
318 pending_locations={:?} visited_locations={:?}",
319 location, outmost_back_edge, pending_locations, visited_locations
322 if location == to && outmost_back_edge.is_some() {
323 // We've managed to reach the use location
324 debug!("reach_through_backedge: found!");
325 return outmost_back_edge;
328 let block = &self.mir.basic_blocks()[location.block];
330 if location.statement_index < block.statements.len() {
331 let successor = location.successor_within_block();
332 if visited_locations.insert(successor) {
333 pending_locations.push_back(successor);
336 pending_locations.extend(
344 .filter(|s| visited_locations.insert(*s))
346 if self.is_back_edge(location, s) {
347 match outmost_back_edge {
349 outmost_back_edge = Some(location);
353 if location.dominates(back_edge, &self.dominators) =>
355 outmost_back_edge = Some(location);
371 /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the
372 /// intermediate nodes
373 fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool {
374 self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default())
377 fn find_loop_head_dfs(
381 visited_locations: &mut FxHashSet<Location>,
383 visited_locations.insert(from);
385 if from == loop_head {
389 if loop_head.dominates(from, &self.dominators) {
390 let block = &self.mir.basic_blocks()[from.block];
392 if from.statement_index < block.statements.len() {
393 let successor = from.successor_within_block();
395 if !visited_locations.contains(&successor)
396 && self.find_loop_head_dfs(successor, loop_head, visited_locations)
401 for bb in block.terminator().successors() {
402 let successor = Location {
407 if !visited_locations.contains(&successor)
408 && self.find_loop_head_dfs(successor, loop_head, visited_locations)
419 /// True if an edge `source -> target` is a backedge -- in other words, if the target
420 /// dominates the source.
421 fn is_back_edge(&self, source: Location, target: Location) -> bool {
422 target.dominates(source, &self.mir.dominators())
425 /// Determine how the borrow was later used.
428 borrow: &BorrowData<'tcx>,
431 ) -> (LaterUseKind, Span) {
433 UseSpans::ClosureUse { var_span, .. } => {
434 // Used in a closure.
435 (LaterUseKind::ClosureCapture, var_span)
437 UseSpans::OtherUse(span) => {
438 let block = &self.mir.basic_blocks()[location.block];
440 let kind = if let Some(&Statement {
441 kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
443 }) = block.statements.get(location.statement_index) {
444 LaterUseKind::FakeLetRead
445 } else if self.was_captured_by_trait_object(borrow) {
446 LaterUseKind::TraitCapture
447 } else if location.statement_index == block.statements.len() {
448 if let TerminatorKind::Call {
449 ref func, from_hir_call: true, ..
450 } = block.terminator().kind {
451 // Just point to the function, to reduce the chance of overlapping spans.
452 let function_span = match func {
453 Operand::Constant(c) => c.span,
454 Operand::Copy(Place::Local(l)) | Operand::Move(Place::Local(l)) => {
455 let local_decl = &self.mir.local_decls[*l];
456 if local_decl.name.is_none() {
457 local_decl.source_info.span
464 return (LaterUseKind::Call, function_span);
477 /// Checks if a borrowed value was captured by a trait object. We do this by
478 /// looking forward in the MIR from the reserve location and checking if we see
479 /// a unsized cast to a trait object on our data.
480 fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
481 // Start at the reserve location, find the place that we want to see cast to a trait object.
482 let location = borrow.reserve_location;
483 let block = &self.mir[location.block];
484 let stmt = block.statements.get(location.statement_index);
485 debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
487 // We make a `queue` vector that has the locations we want to visit. As of writing, this
488 // will only ever have one item at any given time, but by using a vector, we can pop from
489 // it which simplifies the termination logic.
490 let mut queue = vec![location];
491 let mut target = if let Some(&Statement {
492 kind: StatementKind::Assign(Place::Local(local), _),
500 debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
501 while let Some(current_location) = queue.pop() {
502 debug!("was_captured_by_trait: target={:?}", target);
503 let block = &self.mir[current_location.block];
504 // We need to check the current location to find out if it is a terminator.
505 let is_terminator = current_location.statement_index == block.statements.len();
507 let stmt = &block.statements[current_location.statement_index];
508 debug!("was_captured_by_trait_object: stmt={:?}", stmt);
510 // The only kind of statement that we care about is assignments...
511 if let StatementKind::Assign(
515 let into = match place {
516 Place::Local(into) => into,
517 Place::Projection(box Projection {
518 base: Place::Local(into),
519 elem: ProjectionElem::Deref,
522 // Continue at the next location.
523 queue.push(current_location.successor_within_block());
529 // If we see a use, we should check whether it is our data, and if so
530 // update the place that we're looking for to that new place.
531 Rvalue::Use(operand) => match operand {
532 Operand::Copy(Place::Local(from)) |
533 Operand::Move(Place::Local(from)) if *from == target => {
538 // If we see a unsized cast, then if it is our data we should check
539 // whether it is being cast to a trait object.
540 Rvalue::Cast(CastKind::Unsize, operand, ty) => match operand {
541 Operand::Copy(Place::Local(from)) |
542 Operand::Move(Place::Local(from)) if *from == target => {
543 debug!("was_captured_by_trait_object: ty={:?}", ty);
544 // Check the type for a trait object.
545 return match ty.sty {
547 ty::TyKind::Ref(_, ty, _) if ty.is_trait() => true,
549 _ if ty.is_box() && ty.boxed_ty().is_trait() =>
552 _ if ty.is_trait() => true,
563 // Continue at the next location.
564 queue.push(current_location.successor_within_block());
566 // The only thing we need to do for terminators is progress to the next block.
567 let terminator = block.terminator();
568 debug!("was_captured_by_trait_object: terminator={:?}", terminator);
570 if let TerminatorKind::Call {
571 destination: Some((Place::Local(dest), block)),
574 } = &terminator.kind {
576 "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
579 // Check if one of the arguments to this function is the target place.
580 let found_target = args.iter().any(|arg| {
581 if let Operand::Move(Place::Local(potential)) = arg {
588 // If it is, follow this to the next block and update the target.
591 queue.push(block.start_location());
596 debug!("was_captured_by_trait: queue={:?}", queue);
599 // We didn't find anything and ran out of locations to check.