Skip to content

Commit 9746a2d

Browse files
committed
Auto merge of #54848 - davidtwco:issue-52663-trait-object, r=nikomatsakis
Better Diagnostic for Trait Object Capture Part of #52663. This commit enhances `LaterUseKind` detection to identify when a borrow is captured by a trait object which helps explain why there is a borrow error. r? @nikomatsakis cc @pnkfelix
2 parents f42c510 + 72911fb commit 9746a2d

File tree

4 files changed

+224
-37
lines changed

4 files changed

+224
-37
lines changed

src/librustc_mir/borrow_check/nll/explain_borrow/mod.rs

+183-36
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ use borrow_check::error_reporting::UseSpans;
1313
use borrow_check::nll::region_infer::Cause;
1414
use borrow_check::{Context, MirBorrowckCtxt, WriteKind};
1515
use rustc::ty::{self, Region, TyCtxt};
16-
use rustc::mir::{FakeReadCause, Local, Location, Mir, Operand};
17-
use rustc::mir::{Place, StatementKind, TerminatorKind};
16+
use rustc::mir::{
17+
CastKind, FakeReadCause, Local, Location, Mir, Operand, Place, Projection, ProjectionElem,
18+
Rvalue, Statement, StatementKind, TerminatorKind
19+
};
1820
use rustc_errors::DiagnosticBuilder;
1921
use syntax_pos::Span;
2022

@@ -34,6 +36,7 @@ pub(in borrow_check) enum BorrowExplanation<'tcx> {
3436

3537
#[derive(Clone, Copy)]
3638
pub(in borrow_check) enum LaterUseKind {
39+
TraitCapture,
3740
ClosureCapture,
3841
Call,
3942
FakeLetRead,
@@ -51,6 +54,7 @@ impl<'tcx> BorrowExplanation<'tcx> {
5154
match *self {
5255
BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => {
5356
let message = match later_use_kind {
57+
LaterUseKind::TraitCapture => "borrow later captured here by trait object",
5458
LaterUseKind::ClosureCapture => "borrow later captured here by closure",
5559
LaterUseKind::Call => "borrow later used by call",
5660
LaterUseKind::FakeLetRead => "borrow later stored here",
@@ -60,9 +64,10 @@ impl<'tcx> BorrowExplanation<'tcx> {
6064
},
6165
BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => {
6266
let message = match later_use_kind {
63-
LaterUseKind::ClosureCapture => {
64-
"borrow captured here by closure, in later iteration of loop"
65-
},
67+
LaterUseKind::TraitCapture =>
68+
"borrow captured here by trait object, in later iteration of loop",
69+
LaterUseKind::ClosureCapture =>
70+
"borrow captured here by closure, in later iteration of loop",
6671
LaterUseKind::Call => "borrow used by call, in later iteration of loop",
6772
LaterUseKind::FakeLetRead => "borrow later stored here",
6873
LaterUseKind::Other => "borrow used here, in later iteration of loop",
@@ -200,13 +205,13 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
200205
.or_else(|| self.borrow_spans(span, location));
201206

202207
if self.is_borrow_location_in_loop(context.loc) {
203-
let later_use = self.later_use_kind(spans, location);
208+
let later_use = self.later_use_kind(borrow, spans, location);
204209
BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1)
205210
} else {
206211
// Check if the location represents a `FakeRead`, and adapt the error
207212
// message to the `FakeReadCause` it is from: in particular,
208213
// the ones inserted in optimized `let var = <expr>` patterns.
209-
let later_use = self.later_use_kind(spans, location);
214+
let later_use = self.later_use_kind(borrow, spans, location);
210215
BorrowExplanation::UsedLater(later_use.0, later_use.1)
211216
}
212217
}
@@ -316,42 +321,184 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
316321
false
317322
}
318323

319-
fn later_use_kind(&self, use_spans: UseSpans, location: Location) -> (LaterUseKind, Span) {
320-
use self::LaterUseKind::*;
321-
322-
let block = &self.mir.basic_blocks()[location.block];
324+
/// Determine how the borrow was later used.
325+
fn later_use_kind(
326+
&self,
327+
borrow: &BorrowData<'tcx>,
328+
use_spans: UseSpans,
329+
location: Location
330+
) -> (LaterUseKind, Span) {
323331
match use_spans {
324-
UseSpans::ClosureUse { var_span, .. } => (LaterUseKind::ClosureCapture, var_span),
332+
UseSpans::ClosureUse { var_span, .. } => {
333+
// Used in a closure.
334+
(LaterUseKind::ClosureCapture, var_span)
335+
},
325336
UseSpans::OtherUse(span) => {
326-
(if let Some(stmt) = block.statements.get(location.statement_index) {
327-
match stmt.kind {
328-
StatementKind::FakeRead(FakeReadCause::ForLet, _) => FakeLetRead,
329-
_ => Other,
337+
let block = &self.mir.basic_blocks()[location.block];
338+
339+
let kind = if let Some(&Statement {
340+
kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
341+
..
342+
}) = block.statements.get(location.statement_index) {
343+
LaterUseKind::FakeLetRead
344+
} else if self.was_captured_by_trait_object(borrow) {
345+
LaterUseKind::TraitCapture
346+
} else if location.statement_index == block.statements.len() {
347+
if let TerminatorKind::Call {
348+
ref func, from_hir_call: true, ..
349+
} = block.terminator().kind {
350+
// Just point to the function, to reduce the chance of overlapping spans.
351+
let function_span = match func {
352+
Operand::Constant(c) => c.span,
353+
Operand::Copy(Place::Local(l)) | Operand::Move(Place::Local(l)) => {
354+
let local_decl = &self.mir.local_decls[*l];
355+
if local_decl.name.is_none() {
356+
local_decl.source_info.span
357+
} else {
358+
span
359+
}
360+
},
361+
_ => span,
362+
};
363+
return (LaterUseKind::Call, function_span);
364+
} else {
365+
LaterUseKind::Other
330366
}
331367
} else {
332-
assert_eq!(location.statement_index, block.statements.len());
333-
match block.terminator().kind {
334-
TerminatorKind::Call { ref func, from_hir_call: true, .. } => {
335-
// Just point to the function, to reduce the chance
336-
// of overlapping spans.
337-
let function_span = match func {
338-
Operand::Constant(c) => c.span,
339-
Operand::Copy(Place::Local(l)) | Operand::Move(Place::Local(l)) => {
340-
let local_decl = &self.mir.local_decls[*l];
341-
if local_decl.name.is_none() {
342-
local_decl.source_info.span
343-
} else {
344-
span
345-
}
346-
},
347-
_ => span,
348-
};
349-
return (Call, function_span);
368+
LaterUseKind::Other
369+
};
370+
371+
(kind, span)
372+
}
373+
}
374+
}
375+
376+
/// Check if a borrowed value was captured by a trait object. We do this by
377+
/// looking forward in the MIR from the reserve location and checking if we see
378+
/// a unsized cast to a trait object on our data.
379+
fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
380+
// Start at the reserve location, find the place that we want to see cast to a trait object.
381+
let location = borrow.reserve_location;
382+
let block = &self.mir[location.block];
383+
let stmt = block.statements.get(location.statement_index);
384+
debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
385+
386+
// We make a `queue` vector that has the locations we want to visit. As of writing, this
387+
// will only ever have one item at any given time, but by using a vector, we can pop from
388+
// it which simplifies the termination logic.
389+
let mut queue = vec![location];
390+
let mut target = if let Some(&Statement {
391+
kind: StatementKind::Assign(Place::Local(local), _),
392+
..
393+
}) = stmt {
394+
local
395+
} else {
396+
return false;
397+
};
398+
399+
debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
400+
while let Some(current_location) = queue.pop() {
401+
debug!("was_captured_by_trait: target={:?}", target);
402+
let block = &self.mir[current_location.block];
403+
// We need to check the current location to find out if it is a terminator.
404+
let is_terminator = current_location.statement_index == block.statements.len();
405+
if !is_terminator {
406+
let stmt = &block.statements[current_location.statement_index];
407+
debug!("was_captured_by_trait_object: stmt={:?}", stmt);
408+
409+
// The only kind of statement that we care about is assignments...
410+
if let StatementKind::Assign(
411+
place,
412+
box rvalue,
413+
) = &stmt.kind {
414+
let into = match place {
415+
Place::Local(into) => into,
416+
Place::Projection(box Projection {
417+
base: Place::Local(into),
418+
elem: ProjectionElem::Deref,
419+
}) => into,
420+
_ => {
421+
// Continue at the next location.
422+
queue.push(current_location.successor_within_block());
423+
continue;
350424
},
351-
_ => Other,
425+
};
426+
427+
match rvalue {
428+
// If we see a use, we should check whether it is our data, and if so
429+
// update the place that we're looking for to that new place.
430+
Rvalue::Use(operand) => match operand {
431+
Operand::Copy(Place::Local(from)) |
432+
Operand::Move(Place::Local(from)) if *from == target => {
433+
target = *into;
434+
},
435+
_ => {},
436+
},
437+
// If we see a unsized cast, then if it is our data we should check
438+
// whether it is being cast to a trait object.
439+
Rvalue::Cast(CastKind::Unsize, operand, ty) => match operand {
440+
Operand::Copy(Place::Local(from)) |
441+
Operand::Move(Place::Local(from)) if *from == target => {
442+
debug!("was_captured_by_trait_object: ty={:?}", ty);
443+
// Check the type for a trait object.
444+
match ty.sty {
445+
// `&dyn Trait`
446+
ty::TyKind::Ref(_, ty, _) if ty.is_trait() => return true,
447+
// `Box<dyn Trait>`
448+
_ if ty.is_box() && ty.boxed_ty().is_trait() =>
449+
return true,
450+
// `dyn Trait`
451+
_ if ty.is_trait() => return true,
452+
// Anything else.
453+
_ => return false,
454+
}
455+
},
456+
_ => return false,
457+
},
458+
_ => {},
352459
}
353-
}, span)
460+
}
461+
462+
// Continue at the next location.
463+
queue.push(current_location.successor_within_block());
464+
} else {
465+
// The only thing we need to do for terminators is progress to the next block.
466+
let terminator = block.terminator();
467+
debug!("was_captured_by_trait_object: terminator={:?}", terminator);
468+
469+
match &terminator.kind {
470+
TerminatorKind::Call {
471+
destination: Some((Place::Local(dest), block)),
472+
args,
473+
..
474+
} => {
475+
debug!(
476+
"was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
477+
target, dest, args
478+
);
479+
// Check if one of the arguments to this function is the target place.
480+
let found_target = args.iter().any(|arg| {
481+
if let Operand::Move(Place::Local(potential)) = arg {
482+
*potential == target
483+
} else {
484+
false
485+
}
486+
});
487+
488+
// If it is, follow this to the next block and update the target.
489+
if found_target {
490+
target = *dest;
491+
queue.push(block.start_location());
492+
}
493+
},
494+
_ => {},
495+
}
354496
}
497+
498+
debug!("was_captured_by_trait: queue={:?}", queue);
355499
}
500+
501+
// We didn't find anything and ran out of locations to check.
502+
false
356503
}
357504
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2014 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.
4+
//
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.
10+
11+
#![feature(box_syntax)]
12+
#![feature(nll)]
13+
14+
trait Foo { fn get(&self); }
15+
16+
impl<A> Foo for A {
17+
fn get(&self) { }
18+
}
19+
20+
fn main() {
21+
let _ = {
22+
let tmp0 = 3;
23+
let tmp1 = &tmp0;
24+
box tmp1 as Box<Foo + '_>
25+
};
26+
//~^^^ ERROR `tmp0` does not live long enough
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0597]: `tmp0` does not live long enough
2+
--> $DIR/issue-52663-trait-object.rs:23:20
3+
|
4+
LL | let tmp1 = &tmp0;
5+
| ^^^^^ borrowed value does not live long enough
6+
LL | box tmp1 as Box<Foo + '_>
7+
| ------------------------- borrow later captured here by trait object
8+
LL | };
9+
| - `tmp0` dropped here while still borrowed
10+
11+
error: aborting due to previous error
12+
13+
For more information about this error, try `rustc --explain E0597`.

src/test/ui/span/regions-close-over-type-parameter-2.nll.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ error[E0597]: `tmp0` does not live long enough
44
LL | let tmp1 = &tmp0;
55
| ^^^^^ borrowed value does not live long enough
66
LL | repeater3(tmp1)
7-
| --------------- borrow later used here
7+
| --------------- borrow later captured here by trait object
88
LL | };
99
| - `tmp0` dropped here while still borrowed
1010

0 commit comments

Comments
 (0)