1 // Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use borrow_check::borrow_set::BorrowData;
12 use borrow_check::error_reporting::UseSpans;
13 use borrow_check::nll::ConstraintDescription;
14 use borrow_check::nll::region_infer::{Cause, RegionName};
15 use borrow_check::{Context, MirBorrowckCtxt, WriteKind};
16 use rustc::ty::{self, TyCtxt};
18 CastKind, ConstraintCategory, FakeReadCause, Local, Location, Mir, Operand,
19 Place, Projection, ProjectionElem, Rvalue, Statement, StatementKind,
22 use rustc_errors::DiagnosticBuilder;
27 pub(in borrow_check) enum BorrowExplanation {
28 UsedLater(LaterUseKind, Span),
29 UsedLaterInLoop(LaterUseKind, Span),
30 UsedLaterWhenDropped {
33 should_note_order: bool,
36 category: ConstraintCategory,
39 region_name: RegionName,
40 opt_place_desc: Option<String>,
45 #[derive(Clone, Copy)]
46 pub(in borrow_check) enum LaterUseKind {
54 impl BorrowExplanation {
55 pub(in borrow_check) fn add_explanation_to_diagnostic<'cx, 'gcx, 'tcx>(
57 tcx: TyCtxt<'cx, 'gcx, 'tcx>,
59 err: &mut DiagnosticBuilder<'_>,
63 BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => {
64 let message = match later_use_kind {
65 LaterUseKind::TraitCapture => "borrow later captured here by trait object",
66 LaterUseKind::ClosureCapture => "borrow later captured here by closure",
67 LaterUseKind::Call => "borrow later used by call",
68 LaterUseKind::FakeLetRead => "borrow later stored here",
69 LaterUseKind::Other => "borrow later used here",
71 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
73 BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => {
74 let message = match later_use_kind {
75 LaterUseKind::TraitCapture =>
76 "borrow captured here by trait object, in later iteration of loop",
77 LaterUseKind::ClosureCapture =>
78 "borrow captured here by closure, in later iteration of loop",
79 LaterUseKind::Call => "borrow used by call, in later iteration of loop",
80 LaterUseKind::FakeLetRead => "borrow later stored here",
81 LaterUseKind::Other => "borrow used here, in later iteration of loop",
83 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
85 BorrowExplanation::UsedLaterWhenDropped { drop_loc, dropped_local,
86 should_note_order } =>
88 let local_decl = &mir.local_decls[dropped_local];
89 let (dtor_desc, type_desc) = match local_decl.ty.sty {
90 // If type is an ADT that implements Drop, then
91 // simplify output by reporting just the ADT name.
92 ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() =>
93 ("`Drop` code", format!("type `{}`", tcx.item_path_str(adt.did))),
95 // Otherwise, just report the whole type (and use
96 // the intentionally fuzzy phrase "destructor")
98 ("destructor", "closure".to_owned()),
100 ("destructor", "generator".to_owned()),
102 _ => ("destructor", format!("type `{}`", local_decl.ty)),
105 match local_decl.name {
106 Some(local_name) => {
108 format!("{B}borrow might be used here, when `{LOC}` is dropped \
109 and runs the {DTOR} for {TYPE}",
110 B=borrow_desc, LOC=local_name, TYPE=type_desc, DTOR=dtor_desc);
111 err.span_label(mir.source_info(drop_loc).span, message);
113 if should_note_order {
115 "values in a scope are dropped \
116 in the opposite order they are defined",
121 err.span_label(local_decl.source_info.span,
122 format!("a temporary with access to the {B}borrow \
123 is created here ...",
126 format!("... and the {B}borrow might be used here, \
127 when that temporary is dropped \
128 and runs the {DTOR} for {TYPE}",
129 B=borrow_desc, TYPE=type_desc, DTOR=dtor_desc);
130 err.span_label(mir.source_info(drop_loc).span, message);
132 if let Some(info) = &local_decl.is_block_tail {
133 // FIXME: use span_suggestion instead, highlighting the
134 // whole block tail expression.
135 let msg = if info.tail_result_is_ignored {
136 "The temporary is part of an expression at the end of a block. \
137 Consider adding semicolon after the expression so its temporaries \
138 are dropped sooner, before the local variables declared by the \
141 "The temporary is part of an expression at the end of a block. \
142 Consider forcing this temporary to be dropped sooner, before \
143 the block's local variables are dropped. \
144 For example, you could save the expression's value in a new \
145 local variable `x` and then make `x` be the expression \
146 at the end of the block."
154 BorrowExplanation::MustBeValidFor {
161 region_name.highlight_region_name(err);
163 if let Some(desc) = opt_place_desc {
164 err.span_label(span, format!(
165 "{}requires that `{}` is borrowed for `{}`",
166 category.description(), desc, region_name,
169 err.span_label(span, format!(
170 "{}requires that {}borrow lasts for `{}`",
171 category.description(), borrow_desc, region_name,
180 impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
181 /// Returns structured explanation for *why* the borrow contains the
182 /// point from `context`. This is key for the "3-point errors"
183 /// [described in the NLL RFC][d].
187 /// - `borrow`: the borrow in question
188 /// - `context`: where the borrow occurs
189 /// - `kind_place`: if Some, this describes the statement that triggered the error.
190 /// - first half is the kind of write, if any, being performed
191 /// - second half is the place being accessed
193 /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
194 pub(in borrow_check) fn explain_why_borrow_contains_point(
197 borrow: &BorrowData<'tcx>,
198 kind_place: Option<(WriteKind, &Place<'tcx>)>,
199 ) -> BorrowExplanation {
201 "explain_why_borrow_contains_point(context={:?}, borrow={:?}, kind_place={:?})",
202 context, borrow, kind_place
205 let regioncx = &self.nonlexical_regioncx;
207 let tcx = self.infcx.tcx;
209 let borrow_region_vid = borrow.region;
211 "explain_why_borrow_contains_point: borrow_region_vid={:?}",
215 let region_sub = regioncx.find_sub_region_live_at(borrow_region_vid, context.loc);
217 "explain_why_borrow_contains_point: region_sub={:?}",
221 match find_use::find(mir, regioncx, tcx, region_sub, context.loc) {
222 Some(Cause::LiveVar(local, location)) => {
223 let span = mir.source_info(location).span;
224 let spans = self.move_spans(&Place::Local(local), location)
225 .or_else(|| self.borrow_spans(span, location));
227 if self.is_borrow_location_in_loop(context.loc) {
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 let opt_place_desc = self.describe_place(&borrow.borrowed_place);
275 BorrowExplanation::MustBeValidFor {
283 BorrowExplanation::Unexplained
288 /// Check if a borrow location is within a loop.
289 fn is_borrow_location_in_loop(
291 borrow_location: Location,
293 let mut visited_locations = Vec::new();
294 let mut pending_locations = vec![ borrow_location ];
295 debug!("is_in_loop: borrow_location={:?}", borrow_location);
297 while let Some(location) = pending_locations.pop() {
298 debug!("is_in_loop: location={:?} pending_locations={:?} visited_locations={:?}",
299 location, pending_locations, visited_locations);
300 if location == borrow_location && visited_locations.contains(&borrow_location) {
301 // We've managed to return to where we started (and this isn't the start of the
303 debug!("is_in_loop: found!");
307 // Skip locations we've been.
308 if visited_locations.contains(&location) { continue; }
310 let block = &self.mir.basic_blocks()[location.block];
311 if location.statement_index == block.statements.len() {
312 // Add start location of the next blocks to pending locations.
313 match block.terminator().kind {
314 TerminatorKind::Goto { target } => {
315 pending_locations.push(target.start_location());
317 TerminatorKind::SwitchInt { ref targets, .. } => {
318 pending_locations.extend(
319 targets.into_iter().map(|target| target.start_location()));
321 TerminatorKind::Drop { target, unwind, .. } |
322 TerminatorKind::DropAndReplace { target, unwind, .. } |
323 TerminatorKind::Assert { target, cleanup: unwind, .. } |
324 TerminatorKind::Yield { resume: target, drop: unwind, .. } |
325 TerminatorKind::FalseUnwind { real_target: target, unwind, .. } => {
326 pending_locations.push(target.start_location());
327 if let Some(unwind) = unwind {
328 pending_locations.push(unwind.start_location());
331 TerminatorKind::Call { ref destination, cleanup, .. } => {
332 if let Some((_, destination)) = destination {
333 pending_locations.push(destination.start_location());
335 if let Some(cleanup) = cleanup {
336 pending_locations.push(cleanup.start_location());
339 TerminatorKind::FalseEdges { real_target, ref imaginary_targets, .. } => {
340 pending_locations.push(real_target.start_location());
341 pending_locations.extend(
342 imaginary_targets.into_iter().map(|target| target.start_location()));
347 // Add the next statement to pending locations.
348 pending_locations.push(location.successor_within_block());
351 // Keep track of where we have visited.
352 visited_locations.push(location);
358 /// Determine how the borrow was later used.
361 borrow: &BorrowData<'tcx>,
364 ) -> (LaterUseKind, Span) {
366 UseSpans::ClosureUse { var_span, .. } => {
367 // Used in a closure.
368 (LaterUseKind::ClosureCapture, var_span)
370 UseSpans::OtherUse(span) => {
371 let block = &self.mir.basic_blocks()[location.block];
373 let kind = if let Some(&Statement {
374 kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
376 }) = block.statements.get(location.statement_index) {
377 LaterUseKind::FakeLetRead
378 } else if self.was_captured_by_trait_object(borrow) {
379 LaterUseKind::TraitCapture
380 } else if location.statement_index == block.statements.len() {
381 if let TerminatorKind::Call {
382 ref func, from_hir_call: true, ..
383 } = block.terminator().kind {
384 // Just point to the function, to reduce the chance of overlapping spans.
385 let function_span = match func {
386 Operand::Constant(c) => c.span,
387 Operand::Copy(Place::Local(l)) | Operand::Move(Place::Local(l)) => {
388 let local_decl = &self.mir.local_decls[*l];
389 if local_decl.name.is_none() {
390 local_decl.source_info.span
397 return (LaterUseKind::Call, function_span);
410 /// Check if a borrowed value was captured by a trait object. We do this by
411 /// looking forward in the MIR from the reserve location and checking if we see
412 /// a unsized cast to a trait object on our data.
413 fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
414 // Start at the reserve location, find the place that we want to see cast to a trait object.
415 let location = borrow.reserve_location;
416 let block = &self.mir[location.block];
417 let stmt = block.statements.get(location.statement_index);
418 debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
420 // We make a `queue` vector that has the locations we want to visit. As of writing, this
421 // will only ever have one item at any given time, but by using a vector, we can pop from
422 // it which simplifies the termination logic.
423 let mut queue = vec![location];
424 let mut target = if let Some(&Statement {
425 kind: StatementKind::Assign(Place::Local(local), _),
433 debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
434 while let Some(current_location) = queue.pop() {
435 debug!("was_captured_by_trait: target={:?}", target);
436 let block = &self.mir[current_location.block];
437 // We need to check the current location to find out if it is a terminator.
438 let is_terminator = current_location.statement_index == block.statements.len();
440 let stmt = &block.statements[current_location.statement_index];
441 debug!("was_captured_by_trait_object: stmt={:?}", stmt);
443 // The only kind of statement that we care about is assignments...
444 if let StatementKind::Assign(
448 let into = match place {
449 Place::Local(into) => into,
450 Place::Projection(box Projection {
451 base: Place::Local(into),
452 elem: ProjectionElem::Deref,
455 // Continue at the next location.
456 queue.push(current_location.successor_within_block());
462 // If we see a use, we should check whether it is our data, and if so
463 // update the place that we're looking for to that new place.
464 Rvalue::Use(operand) => match operand {
465 Operand::Copy(Place::Local(from)) |
466 Operand::Move(Place::Local(from)) if *from == target => {
471 // If we see a unsized cast, then if it is our data we should check
472 // whether it is being cast to a trait object.
473 Rvalue::Cast(CastKind::Unsize, operand, ty) => match operand {
474 Operand::Copy(Place::Local(from)) |
475 Operand::Move(Place::Local(from)) if *from == target => {
476 debug!("was_captured_by_trait_object: ty={:?}", ty);
477 // Check the type for a trait object.
478 return match ty.sty {
480 ty::TyKind::Ref(_, ty, _) if ty.is_trait() => true,
482 _ if ty.is_box() && ty.boxed_ty().is_trait() =>
485 _ if ty.is_trait() => true,
496 // Continue at the next location.
497 queue.push(current_location.successor_within_block());
499 // The only thing we need to do for terminators is progress to the next block.
500 let terminator = block.terminator();
501 debug!("was_captured_by_trait_object: terminator={:?}", terminator);
503 if let TerminatorKind::Call {
504 destination: Some((Place::Local(dest), block)),
507 } = &terminator.kind {
509 "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
512 // Check if one of the arguments to this function is the target place.
513 let found_target = args.iter().any(|arg| {
514 if let Operand::Move(Place::Local(potential)) = arg {
521 // If it is, follow this to the next block and update the target.
524 queue.push(block.start_location());
529 debug!("was_captured_by_trait: queue={:?}", queue);
532 // We didn't find anything and ran out of locations to check.