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::region_infer::{Cause, RegionName};
6 use crate::borrow_check::nll::ConstraintDescription;
7 use crate::borrow_check::{MirBorrowckCtxt, WriteKind};
9 CastKind, ConstraintCategory, FakeReadCause, Local, Location, Body, Operand, Place, PlaceBase,
10 Projection, ProjectionElem, Rvalue, Statement, StatementKind, TerminatorKind,
12 use rustc::ty::{self, TyCtxt};
13 use rustc::ty::adjustment::{PointerCast};
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<'_>,
60 borrow_span: Option<Span>,
63 BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => {
64 let message = match later_use_kind {
65 LaterUseKind::TraitCapture => "captured here by trait object",
66 LaterUseKind::ClosureCapture => "captured here by closure",
67 LaterUseKind::Call => "used by call",
68 LaterUseKind::FakeLetRead => "stored here",
69 LaterUseKind::Other => "used here",
71 if !borrow_span.map(|sp| sp.overlaps(var_or_use_span)).unwrap_or(false) {
74 format!("{}borrow later {}", borrow_desc, message),
78 BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => {
79 let message = match later_use_kind {
80 LaterUseKind::TraitCapture => {
81 "borrow captured here by trait object, in later iteration of loop"
83 LaterUseKind::ClosureCapture => {
84 "borrow captured here by closure, in later iteration of loop"
86 LaterUseKind::Call => "borrow used by call, in later iteration of loop",
87 LaterUseKind::FakeLetRead => "borrow later stored here",
88 LaterUseKind::Other => "borrow used here, in later iteration of loop",
90 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
92 BorrowExplanation::UsedLaterWhenDropped {
97 let local_decl = &mir.local_decls[dropped_local];
98 let (dtor_desc, type_desc) = match local_decl.ty.sty {
99 // If type is an ADT that implements Drop, then
100 // simplify output by reporting just the ADT name.
101 ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => (
103 format!("type `{}`", tcx.def_path_str(adt.did)),
106 // Otherwise, just report the whole type (and use
107 // the intentionally fuzzy phrase "destructor")
108 ty::Closure(..) => ("destructor", "closure".to_owned()),
109 ty::Generator(..) => ("destructor", "generator".to_owned()),
111 _ => ("destructor", format!("type `{}`", local_decl.ty)),
114 match local_decl.name {
115 Some(local_name) if !local_decl.from_compiler_desugaring() => {
116 let message = format!(
117 "{B}borrow might be used here, when `{LOC}` is dropped \
118 and runs the {DTOR} for {TYPE}",
124 err.span_label(mir.source_info(drop_loc).span, message);
126 if should_note_order {
128 "values in a scope are dropped \
129 in the opposite order they are defined",
135 local_decl.source_info.span,
137 "a temporary with access to the {B}borrow \
138 is created here ...",
142 let message = format!(
143 "... and the {B}borrow might be used here, \
144 when that temporary is dropped \
145 and runs the {DTOR} for {TYPE}",
150 err.span_label(mir.source_info(drop_loc).span, message);
152 if let Some(info) = &local_decl.is_block_tail {
153 // FIXME: use span_suggestion instead, highlighting the
154 // whole block tail expression.
155 let msg = if info.tail_result_is_ignored {
156 "The temporary is part of an expression at the end of a block. \
157 Consider adding semicolon after the expression so its temporaries \
158 are dropped sooner, before the local variables declared by the \
161 "The temporary is part of an expression at the end of a block. \
162 Consider forcing this temporary to be dropped sooner, before \
163 the block's local variables are dropped. \
164 For example, you could save the expression's value in a new \
165 local variable `x` and then make `x` be the expression \
166 at the end of the block."
174 BorrowExplanation::MustBeValidFor {
181 region_name.highlight_region_name(err);
183 if let Some(desc) = opt_place_desc {
187 "{}requires that `{}` is borrowed for `{}`",
188 category.description(),
197 "{}requires that {}borrow lasts for `{}`",
198 category.description(),
210 impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
211 /// Returns structured explanation for *why* the borrow contains the
212 /// point from `location`. This is key for the "3-point errors"
213 /// [described in the NLL RFC][d].
217 /// - `borrow`: the borrow in question
218 /// - `location`: where the borrow occurs
219 /// - `kind_place`: if Some, this describes the statement that triggered the error.
220 /// - first half is the kind of write, if any, being performed
221 /// - second half is the place being accessed
223 /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
224 pub(in crate::borrow_check) fn explain_why_borrow_contains_point(
227 borrow: &BorrowData<'tcx>,
228 kind_place: Option<(WriteKind, &Place<'tcx>)>,
229 ) -> BorrowExplanation {
231 "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})",
232 location, borrow, kind_place
235 let regioncx = &self.nonlexical_regioncx;
237 let tcx = self.infcx.tcx;
239 let borrow_region_vid = borrow.region;
241 "explain_why_borrow_contains_point: borrow_region_vid={:?}",
245 let region_sub = regioncx.find_sub_region_live_at(borrow_region_vid, location);
247 "explain_why_borrow_contains_point: region_sub={:?}",
251 match find_use::find(mir, regioncx, tcx, region_sub, location) {
252 Some(Cause::LiveVar(local, location)) => {
253 let span = mir.source_info(location).span;
255 .move_spans(&Place::Base(PlaceBase::Local(local)), location)
256 .or_else(|| self.borrow_spans(span, location));
258 let borrow_location = location;
259 if self.is_use_in_later_iteration_of_loop(borrow_location, location) {
260 let later_use = self.later_use_kind(borrow, spans, location);
261 BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1)
263 // Check if the location represents a `FakeRead`, and adapt the error
264 // message to the `FakeReadCause` it is from: in particular,
265 // the ones inserted in optimized `let var = <expr>` patterns.
266 let later_use = self.later_use_kind(borrow, spans, location);
267 BorrowExplanation::UsedLater(later_use.0, later_use.1)
271 Some(Cause::DropVar(local, location)) => {
272 let mut should_note_order = false;
273 if mir.local_decls[local].name.is_some() {
274 if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place {
275 if let Place::Base(PlaceBase::Local(borrowed_local)) = place {
276 if mir.local_decls[*borrowed_local].name.is_some()
277 && local != *borrowed_local
279 should_note_order = true;
285 BorrowExplanation::UsedLaterWhenDropped {
287 dropped_local: local,
293 if let Some(region) = regioncx.to_error_region_vid(borrow_region_vid) {
294 let (category, from_closure, span, region_name) =
295 self.nonlexical_regioncx.free_region_constraint_info(
303 if let Some(region_name) = region_name {
304 let opt_place_desc = self.describe_place(&borrow.borrowed_place);
305 BorrowExplanation::MustBeValidFor {
313 debug!("explain_why_borrow_contains_point: \
314 Could not generate a region name");
315 BorrowExplanation::Unexplained
318 debug!("explain_why_borrow_contains_point: \
319 Could not generate an error region vid");
320 BorrowExplanation::Unexplained
326 /// true if `borrow_location` can reach `use_location` by going through a loop and
327 /// `use_location` is also inside of that loop
328 fn is_use_in_later_iteration_of_loop(
330 borrow_location: Location,
331 use_location: Location,
333 let back_edge = self.reach_through_backedge(borrow_location, use_location);
334 back_edge.map_or(false, |back_edge| {
335 self.can_reach_head_of_loop(use_location, back_edge)
339 /// Returns the outmost back edge if `from` location can reach `to` location passing through
341 fn reach_through_backedge(&self, from: Location, to: Location) -> Option<Location> {
342 let mut visited_locations = FxHashSet::default();
343 let mut pending_locations = VecDeque::new();
344 visited_locations.insert(from);
345 pending_locations.push_back(from);
346 debug!("reach_through_backedge: from={:?} to={:?}", from, to,);
348 let mut outmost_back_edge = None;
349 while let Some(location) = pending_locations.pop_front() {
351 "reach_through_backedge: location={:?} outmost_back_edge={:?}
352 pending_locations={:?} visited_locations={:?}",
353 location, outmost_back_edge, pending_locations, visited_locations
356 if location == to && outmost_back_edge.is_some() {
357 // We've managed to reach the use location
358 debug!("reach_through_backedge: found!");
359 return outmost_back_edge;
362 let block = &self.mir.basic_blocks()[location.block];
364 if location.statement_index < block.statements.len() {
365 let successor = location.successor_within_block();
366 if visited_locations.insert(successor) {
367 pending_locations.push_back(successor);
370 pending_locations.extend(
378 .filter(|s| visited_locations.insert(*s))
380 if self.is_back_edge(location, s) {
381 match outmost_back_edge {
383 outmost_back_edge = Some(location);
387 if location.dominates(back_edge, &self.dominators) =>
389 outmost_back_edge = Some(location);
405 /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the
406 /// intermediate nodes
407 fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool {
408 self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default())
411 fn find_loop_head_dfs(
415 visited_locations: &mut FxHashSet<Location>,
417 visited_locations.insert(from);
419 if from == loop_head {
423 if loop_head.dominates(from, &self.dominators) {
424 let block = &self.mir.basic_blocks()[from.block];
426 if from.statement_index < block.statements.len() {
427 let successor = from.successor_within_block();
429 if !visited_locations.contains(&successor)
430 && self.find_loop_head_dfs(successor, loop_head, visited_locations)
435 for bb in block.terminator().successors() {
436 let successor = Location {
441 if !visited_locations.contains(&successor)
442 && self.find_loop_head_dfs(successor, loop_head, visited_locations)
453 /// True if an edge `source -> target` is a backedge -- in other words, if the target
454 /// dominates the source.
455 fn is_back_edge(&self, source: Location, target: Location) -> bool {
456 target.dominates(source, &self.mir.dominators())
459 /// Determine how the borrow was later used.
462 borrow: &BorrowData<'tcx>,
465 ) -> (LaterUseKind, Span) {
467 UseSpans::ClosureUse { var_span, .. } => {
468 // Used in a closure.
469 (LaterUseKind::ClosureCapture, var_span)
471 UseSpans::OtherUse(span) => {
472 let block = &self.mir.basic_blocks()[location.block];
474 let kind = if let Some(&Statement {
475 kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
477 }) = block.statements.get(location.statement_index)
479 LaterUseKind::FakeLetRead
480 } else if self.was_captured_by_trait_object(borrow) {
481 LaterUseKind::TraitCapture
482 } else if location.statement_index == block.statements.len() {
483 if let TerminatorKind::Call {
487 } = block.terminator().kind
489 // Just point to the function, to reduce the chance of overlapping spans.
490 let function_span = match func {
491 Operand::Constant(c) => c.span,
492 Operand::Copy(Place::Base(PlaceBase::Local(l))) |
493 Operand::Move(Place::Base(PlaceBase::Local(l))) => {
494 let local_decl = &self.mir.local_decls[*l];
495 if local_decl.name.is_none() {
496 local_decl.source_info.span
503 return (LaterUseKind::Call, function_span);
516 /// Checks if a borrowed value was captured by a trait object. We do this by
517 /// looking forward in the MIR from the reserve location and checking if we see
518 /// a unsized cast to a trait object on our data.
519 fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
520 // Start at the reserve location, find the place that we want to see cast to a trait object.
521 let location = borrow.reserve_location;
522 let block = &self.mir[location.block];
523 let stmt = block.statements.get(location.statement_index);
525 "was_captured_by_trait_object: location={:?} stmt={:?}",
529 // We make a `queue` vector that has the locations we want to visit. As of writing, this
530 // will only ever have one item at any given time, but by using a vector, we can pop from
531 // it which simplifies the termination logic.
532 let mut queue = vec![location];
533 let mut target = if let Some(&Statement {
534 kind: StatementKind::Assign(Place::Base(PlaceBase::Local(local)), _),
544 "was_captured_by_trait: target={:?} queue={:?}",
547 while let Some(current_location) = queue.pop() {
548 debug!("was_captured_by_trait: target={:?}", target);
549 let block = &self.mir[current_location.block];
550 // We need to check the current location to find out if it is a terminator.
551 let is_terminator = current_location.statement_index == block.statements.len();
553 let stmt = &block.statements[current_location.statement_index];
554 debug!("was_captured_by_trait_object: stmt={:?}", stmt);
556 // The only kind of statement that we care about is assignments...
557 if let StatementKind::Assign(place, box rvalue) = &stmt.kind {
558 let into = match place {
559 Place::Base(PlaceBase::Local(into)) => into,
560 Place::Projection(box Projection {
561 base: Place::Base(PlaceBase::Local(into)),
562 elem: ProjectionElem::Deref,
565 // Continue at the next location.
566 queue.push(current_location.successor_within_block());
572 // If we see a use, we should check whether it is our data, and if so
573 // update the place that we're looking for to that new place.
574 Rvalue::Use(operand) => match operand {
575 Operand::Copy(Place::Base(PlaceBase::Local(from)))
576 | Operand::Move(Place::Base(PlaceBase::Local(from)))
577 if *from == target =>
583 // If we see a unsized cast, then if it is our data we should check
584 // whether it is being cast to a trait object.
586 CastKind::Pointer(PointerCast::Unsize), operand, ty
588 Operand::Copy(Place::Base(PlaceBase::Local(from)))
589 | Operand::Move(Place::Base(PlaceBase::Local(from)))
590 if *from == target =>
592 debug!("was_captured_by_trait_object: ty={:?}", ty);
593 // Check the type for a trait object.
594 return match ty.sty {
596 ty::Ref(_, ty, _) if ty.is_trait() => true,
598 _ if ty.is_box() && ty.boxed_ty().is_trait() => true,
600 _ if ty.is_trait() => true,
611 // Continue at the next location.
612 queue.push(current_location.successor_within_block());
614 // The only thing we need to do for terminators is progress to the next block.
615 let terminator = block.terminator();
616 debug!("was_captured_by_trait_object: terminator={:?}", terminator);
618 if let TerminatorKind::Call {
619 destination: Some((Place::Base(PlaceBase::Local(dest)), block)),
625 "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
628 // Check if one of the arguments to this function is the target place.
629 let found_target = args.iter().any(|arg| {
630 if let Operand::Move(Place::Base(PlaceBase::Local(potential))) = arg {
637 // If it is, follow this to the next block and update the target.
640 queue.push(block.start_location());
645 debug!("was_captured_by_trait: queue={:?}", queue);
648 // We didn't find anything and ran out of locations to check.