Skip to content

Commit 6ca7bc0

Browse files
authored
Rollup merge of rust-lang#55781 - pnkfelix:issue-54382-more-precise-spans-for-temps-and-their-drops, r=davidtwco
More precise spans for temps and their drops This PR has two main enhancements: 1. when possible during code generation for a statement (like `expr();`), pass along the span of a statement, and then attribute the drops of temporaries from that statement to the statement's end-point (which will be the semicolon if it is a statement that is terminating by a semicolon). 2. when evaluating a block expression into a MIR temp, use the span of the block's tail expression (rather than the span of whole block including its statements and curly-braces) for the span of the temp. Each of these individually increases the precision of our diagnostic output; together they combine to make a much clearer picture about the control flow through the spans. Fix rust-lang#54382
2 parents 97d0d89 + dd63982 commit 6ca7bc0

11 files changed

+194
-34
lines changed

src/librustc_mir/build/block.rs

+3-13
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
9090

9191
let source_info = this.source_info(span);
9292
for stmt in stmts {
93-
let Stmt { kind, opt_destruction_scope } = this.hir.mirror(stmt);
93+
let Stmt { kind, opt_destruction_scope, span: stmt_span } = this.hir.mirror(stmt);
9494
match kind {
9595
StmtKind::Expr { scope, expr } => {
9696
this.block_context.push(BlockFrame::Statement { ignores_expr_result: true });
@@ -99,7 +99,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
9999
let si = (scope, source_info);
100100
this.in_scope(si, LintLevel::Inherited, block, |this| {
101101
let expr = this.hir.mirror(expr);
102-
this.stmt_expr(block, expr)
102+
this.stmt_expr(block, expr, Some(stmt_span))
103103
})
104104
}));
105105
}
@@ -177,17 +177,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
177177
let destination_ty = destination.ty(&this.local_decls, tcx).to_ty(tcx);
178178
if let Some(expr) = expr {
179179
let tail_result_is_ignored = destination_ty.is_unit() ||
180-
match this.block_context.last() {
181-
// no context: conservatively assume result is read
182-
None => false,
183-
184-
// sub-expression: block result feeds into some computation
185-
Some(BlockFrame::SubExpr) => false,
186-
187-
// otherwise: use accumualated is_ignored state.
188-
Some(BlockFrame::TailExpr { tail_result_is_ignored: ignored }) |
189-
Some(BlockFrame::Statement { ignores_expr_result: ignored }) => *ignored,
190-
};
180+
this.block_context.currently_ignores_tail_results();
191181
this.block_context.push(BlockFrame::TailExpr { tail_result_is_ignored });
192182

193183
unpack!(block = this.into(destination, block, expr));

src/librustc_mir/build/expr/as_rvalue.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
351351
block.and(Rvalue::Aggregate(adt, fields))
352352
}
353353
ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => {
354-
block = unpack!(this.stmt_expr(block, expr));
354+
block = unpack!(this.stmt_expr(block, expr, None));
355355
block.and(this.unit_rvalue())
356356
}
357357
ExprKind::Yield { value } => {

src/librustc_mir/build/expr/as_temp.rs

+3-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
//! See docs in build/expr/mod.rs
1212
13-
use build::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
13+
use build::{BlockAnd, BlockAndExtension, Builder};
1414
use hair::*;
1515
use rustc::middle::region;
1616
use rustc::mir::*;
@@ -68,19 +68,9 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
6868
debug!("creating temp {:?} with block_context: {:?}", local_decl, this.block_context);
6969
// Find out whether this temp is being created within the
7070
// tail expression of a block whose result is ignored.
71-
for bf in this.block_context.iter().rev() {
72-
match bf {
73-
BlockFrame::SubExpr => continue,
74-
BlockFrame::Statement { .. } => break,
75-
&BlockFrame::TailExpr { tail_result_is_ignored } => {
76-
local_decl = local_decl.block_tail(BlockTailInfo {
77-
tail_result_is_ignored
78-
});
79-
break;
80-
}
81-
}
71+
if let Some(tail_info) = this.block_context.currently_in_block_tail() {
72+
local_decl = local_decl.block_tail(tail_info);
8273
}
83-
8474
this.local_decls.push(local_decl)
8575
};
8676
if !expr_ty.is_never() {

src/librustc_mir/build/expr/into.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
351351
| ExprKind::Break { .. }
352352
| ExprKind::InlineAsm { .. }
353353
| ExprKind::Return { .. } => {
354-
unpack!(block = this.stmt_expr(block, expr));
354+
unpack!(block = this.stmt_expr(block, expr, None));
355355
this.cfg.push_assign_unit(block, source_info, destination);
356356
block.unit()
357357
}

src/librustc_mir/build/expr/stmt.rs

+62-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,18 @@ use hair::*;
1414
use rustc::mir::*;
1515

1616
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
17-
pub fn stmt_expr(&mut self, mut block: BasicBlock, expr: Expr<'tcx>) -> BlockAnd<()> {
17+
/// Builds a block of MIR statements to evaluate the HAIR `expr`.
18+
/// If the original expression was an AST statement,
19+
/// (e.g. `some().code(&here());`) then `opt_stmt_span` is the
20+
/// span of that statement (including its semicolon, if any).
21+
/// Diagnostics use this span (which may be larger than that of
22+
/// `expr`) to identify when statement temporaries are dropped.
23+
pub fn stmt_expr(&mut self,
24+
mut block: BasicBlock,
25+
expr: Expr<'tcx>,
26+
opt_stmt_span: Option<StatementSpan>)
27+
-> BlockAnd<()>
28+
{
1829
let this = self;
1930
let expr_span = expr.span;
2031
let source_info = this.source_info(expr.span);
@@ -29,7 +40,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
2940
} => {
3041
let value = this.hir.mirror(value);
3142
this.in_scope((region_scope, source_info), lint_level, block, |this| {
32-
this.stmt_expr(block, value)
43+
this.stmt_expr(block, value, opt_stmt_span)
3344
})
3445
}
3546
ExprKind::Assign { lhs, rhs } => {
@@ -190,9 +201,56 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
190201
}
191202
_ => {
192203
let expr_ty = expr.ty;
193-
let temp = this.temp(expr.ty.clone(), expr_span);
204+
205+
// Issue #54382: When creating temp for the value of
206+
// expression like:
207+
//
208+
// `{ side_effects(); { let l = stuff(); the_value } }`
209+
//
210+
// it is usually better to focus on `the_value` rather
211+
// than the entirety of block(s) surrounding it.
212+
let mut temp_span = expr_span;
213+
let mut temp_in_tail_of_block = false;
214+
if let ExprKind::Block { body } = expr.kind {
215+
if let Some(tail_expr) = &body.expr {
216+
let mut expr = tail_expr;
217+
while let rustc::hir::ExprKind::Block(subblock, _label) = &expr.node {
218+
if let Some(subtail_expr) = &subblock.expr {
219+
expr = subtail_expr
220+
} else {
221+
break;
222+
}
223+
}
224+
temp_span = expr.span;
225+
temp_in_tail_of_block = true;
226+
}
227+
}
228+
229+
let temp = {
230+
let mut local_decl = LocalDecl::new_temp(expr.ty.clone(), temp_span);
231+
if temp_in_tail_of_block {
232+
if this.block_context.currently_ignores_tail_results() {
233+
local_decl = local_decl.block_tail(BlockTailInfo {
234+
tail_result_is_ignored: true
235+
});
236+
}
237+
}
238+
let temp = this.local_decls.push(local_decl);
239+
let place = Place::Local(temp);
240+
debug!("created temp {:?} for expr {:?} in block_context: {:?}",
241+
temp, expr, this.block_context);
242+
place
243+
};
194244
unpack!(block = this.into(&temp, block, expr));
195-
unpack!(block = this.build_drop(block, expr_span, temp, expr_ty));
245+
246+
// Attribute drops of the statement's temps to the
247+
// semicolon at the statement's end.
248+
let drop_point = this.hir.tcx().sess.source_map().end_point(match opt_stmt_span {
249+
None => expr_span,
250+
Some(StatementSpan(span)) => span,
251+
});
252+
253+
unpack!(block = this.build_drop(block, drop_point, temp, expr_ty));
196254
block.unit()
197255
}
198256
}

src/librustc_mir/build/mod.rs

+54-2
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ impl BlockFrame {
336336
}
337337
}
338338

339+
#[derive(Debug)]
340+
struct BlockContext(Vec<BlockFrame>);
341+
339342
struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
340343
hir: Cx<'a, 'gcx, 'tcx>,
341344
cfg: CFG<'tcx>,
@@ -359,7 +362,7 @@ struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
359362
/// start just throwing new entries onto that vector in order to
360363
/// distinguish the context of EXPR1 from the context of EXPR2 in
361364
/// `{ STMTS; EXPR1 } + EXPR2`
362-
block_context: Vec<BlockFrame>,
365+
block_context: BlockContext,
363366

364367
/// The current unsafe block in scope, even if it is hidden by
365368
/// a PushUnsafeBlock
@@ -409,6 +412,55 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
409412
}
410413
}
411414

415+
impl BlockContext {
416+
fn new() -> Self { BlockContext(vec![]) }
417+
fn push(&mut self, bf: BlockFrame) { self.0.push(bf); }
418+
fn pop(&mut self) -> Option<BlockFrame> { self.0.pop() }
419+
420+
/// Traverses the frames on the BlockContext, searching for either
421+
/// the first block-tail expression frame with no intervening
422+
/// statement frame.
423+
///
424+
/// Notably, this skips over `SubExpr` frames; this method is
425+
/// meant to be used in the context of understanding the
426+
/// relationship of a temp (created within some complicated
427+
/// expression) with its containing expression, and whether the
428+
/// value of that *containing expression* (not the temp!) is
429+
/// ignored.
430+
fn currently_in_block_tail(&self) -> Option<BlockTailInfo> {
431+
for bf in self.0.iter().rev() {
432+
match bf {
433+
BlockFrame::SubExpr => continue,
434+
BlockFrame::Statement { .. } => break,
435+
&BlockFrame::TailExpr { tail_result_is_ignored } =>
436+
return Some(BlockTailInfo { tail_result_is_ignored })
437+
}
438+
}
439+
440+
return None;
441+
}
442+
443+
/// Looks at the topmost frame on the BlockContext and reports
444+
/// whether its one that would discard a block tail result.
445+
///
446+
/// Unlike `currently_within_ignored_tail_expression`, this does
447+
/// *not* skip over `SubExpr` frames: here, we want to know
448+
/// whether the block result itself is discarded.
449+
fn currently_ignores_tail_results(&self) -> bool {
450+
match self.0.last() {
451+
// no context: conservatively assume result is read
452+
None => false,
453+
454+
// sub-expression: block result feeds into some computation
455+
Some(BlockFrame::SubExpr) => false,
456+
457+
// otherwise: use accumulated is_ignored state.
458+
Some(BlockFrame::TailExpr { tail_result_is_ignored: ignored }) |
459+
Some(BlockFrame::Statement { ignores_expr_result: ignored }) => *ignored,
460+
}
461+
}
462+
}
463+
412464
#[derive(Debug)]
413465
enum LocalsForNode {
414466
/// In the usual case, a node-id for an identifier maps to at most
@@ -764,7 +816,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
764816
fn_span: span,
765817
arg_count,
766818
scopes: vec![],
767-
block_context: vec![],
819+
block_context: BlockContext::new(),
768820
source_scopes: IndexVec::new(),
769821
source_scope: OUTERMOST_SOURCE_SCOPE,
770822
source_scope_local_data: IndexVec::new(),

src/librustc_mir/hair/cx/block.rs

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ fn mirror_stmts<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>,
5757
for (index, stmt) in stmts.iter().enumerate() {
5858
let hir_id = cx.tcx.hir.node_to_hir_id(stmt.node.id());
5959
let opt_dxn_ext = cx.region_scope_tree.opt_destruction_scope(hir_id.local_id);
60+
let stmt_span = StatementSpan(cx.tcx.hir.span(stmt.node.id()));
6061
match stmt.node {
6162
hir::StmtKind::Expr(ref expr, _) |
6263
hir::StmtKind::Semi(ref expr, _) => {
@@ -69,6 +70,7 @@ fn mirror_stmts<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>,
6970
expr: expr.to_ref(),
7071
},
7172
opt_destruction_scope: opt_dxn_ext,
73+
span: stmt_span,
7274
})))
7375
}
7476
hir::StmtKind::Decl(ref decl, _) => {
@@ -111,6 +113,7 @@ fn mirror_stmts<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>,
111113
lint_level: cx.lint_level_of(local.id),
112114
},
113115
opt_destruction_scope: opt_dxn_ext,
116+
span: stmt_span,
114117
})));
115118
}
116119
}

src/librustc_mir/hair/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,14 @@ pub enum StmtRef<'tcx> {
7272
Mirror(Box<Stmt<'tcx>>),
7373
}
7474

75+
#[derive(Clone, Debug)]
76+
pub struct StatementSpan(pub Span);
77+
7578
#[derive(Clone, Debug)]
7679
pub struct Stmt<'tcx> {
7780
pub kind: StmtKind<'tcx>,
7881
pub opt_destruction_scope: Option<region::Scope>,
82+
pub span: StatementSpan,
7983
}
8084

8185
#[derive(Clone, Debug)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0597]: `_thing1` does not live long enough
2+
--> $DIR/issue-54382-use-span-of-tail-of-block.rs:7:29
3+
|
4+
LL | D("other").next(&_thing1)
5+
| ----------------^^^^^^^^-
6+
| | |
7+
| | borrowed value does not live long enough
8+
| a temporary with access to the borrow is created here ...
9+
LL | }
10+
LL | }
11+
| - `_thing1` dropped here while still borrowed
12+
LL |
13+
LL | ;
14+
| - ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `D`
15+
|
16+
= note: The temporary is part of an expression at the end of a block. Consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped.
17+
18+
error: aborting due to previous error
19+
20+
For more information about this error, try `rustc --explain E0597`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
fn main() {
2+
{
3+
let mut _thing1 = D(Box::new("thing1"));
4+
{
5+
let _thing2 = D("thing2");
6+
side_effects();
7+
D("other").next(&_thing1)
8+
}
9+
}
10+
11+
;
12+
}
13+
14+
#[derive(Debug)]
15+
struct D<T: std::fmt::Debug>(T);
16+
17+
impl<T: std::fmt::Debug> Drop for D<T> {
18+
fn drop(&mut self) {
19+
println!("dropping {:?})", self);
20+
}
21+
}
22+
23+
impl<T: std::fmt::Debug> D<T> {
24+
fn next<U: std::fmt::Debug>(&self, _other: U) -> D<U> { D(_other) }
25+
fn end(&self) { }
26+
}
27+
28+
fn side_effects() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0597]: `_thing1` does not live long enough
2+
--> $DIR/issue-54382-use-span-of-tail-of-block.rs:7:30
3+
|
4+
LL | D("other").next(&_thing1)
5+
| ^^^^^^^ borrowed value does not live long enough
6+
LL | }
7+
LL | }
8+
| - `_thing1` dropped here while still borrowed
9+
LL |
10+
LL | ;
11+
| - borrowed value needs to live until here
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0597`.

0 commit comments

Comments
 (0)