Skip to content

Commit 2440211

Browse files
Auto merge of #146098 - dianne:extending-blocks, r=<try>
Temporary lifetime extension for blocks
2 parents caccb4d + 98f09fb commit 2440211

10 files changed

+334
-173
lines changed

compiler/rustc_hir_analysis/src/check/region.rs

Lines changed: 132 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
99
use std::mem;
1010

11-
use rustc_data_structures::fx::FxHashMap;
11+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1212
use rustc_hir as hir;
1313
use rustc_hir::def::{CtorKind, DefKind, Res};
1414
use rustc_hir::def_id::DefId;
@@ -38,6 +38,14 @@ struct ScopeResolutionVisitor<'tcx> {
3838

3939
cx: Context,
4040

41+
/// Tracks [extending] blocks and `if` expressions. This is used in performing lifetime
42+
/// extension on block tail expressions: if we've already extended the temporary scopes of
43+
/// extending borrows within a block's tail when checking a parent `let` statement or block, we
44+
/// don't want to re-extend them to be shorter when checking the block itself.
45+
///
46+
/// [extending]: https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions
47+
extended_blocks: FxHashSet<hir::ItemLocalId>,
48+
4149
extended_super_lets: FxHashMap<hir::ItemLocalId, Option<Scope>>,
4250
}
4351

@@ -160,6 +168,17 @@ fn resolve_block<'tcx>(
160168
.backwards_incompatible_scope
161169
.insert(local_id, Scope { local_id, data: ScopeData::Node });
162170
}
171+
// If we haven't already checked for temporary lifetime extension due to a parent `let`
172+
// statement initializer or block, do so. This, e.g., allows `temp()` in `{ &temp() }`
173+
// to outlive the block even when the block itself is not in a `let` statement
174+
// initializer. The same rules for `let` are used here, so non-extending borrows are
175+
// unaffected: `{ f(&temp()) }` drops `temp()` at the end of the block in Rust 2024.
176+
if !visitor.extended_blocks.contains(&blk.hir_id.local_id) {
177+
let blk_result_scope = prev_cx.parent.and_then(|blk_parent| {
178+
visitor.scope_tree.default_temporary_scope(blk_parent).0
179+
});
180+
record_rvalue_scope_if_borrow_expr(visitor, tail_expr, blk_result_scope);
181+
}
163182
resolve_expr(visitor, tail_expr, terminating);
164183
}
165184
}
@@ -354,6 +373,22 @@ fn resolve_expr<'tcx>(
354373

355374
hir::ExprKind::If(cond, then, Some(otherwise)) => {
356375
let expr_cx = visitor.cx;
376+
// If we haven't already checked for temporary lifetime extension due to a parent `let`
377+
// statement initializer or block, do so. This, e.g., allows `format!("temp")` in
378+
// `if cond { &format!("temp") } else { "" }` to outlive the block even when the `if`
379+
// expression itself is not in a `let` statement initializer. The same rules for `let`
380+
// are used here, so non-extending borrows are unaffected:
381+
// `if cond { f(&format!("temp")) } else { "" }`
382+
// drops `format!("temp")` at the end of the block in all editions.
383+
// This also allows `super let` in the then and else blocks to have the scope of the
384+
// result of the block, as expected.
385+
if !visitor.extended_blocks.contains(&expr.hir_id.local_id) {
386+
let blk_result_scope = expr_cx
387+
.parent
388+
.and_then(|if_parent| visitor.scope_tree.default_temporary_scope(if_parent).0);
389+
record_rvalue_scope_if_borrow_expr(visitor, then, blk_result_scope);
390+
record_rvalue_scope_if_borrow_expr(visitor, otherwise, blk_result_scope);
391+
}
357392
let data = if expr.span.at_least_rust_2024() {
358393
ScopeData::IfThenRescope
359394
} else {
@@ -369,6 +404,13 @@ fn resolve_expr<'tcx>(
369404

370405
hir::ExprKind::If(cond, then, None) => {
371406
let expr_cx = visitor.cx;
407+
// As above, perform lifetime extension on the consequent block.
408+
if !visitor.extended_blocks.contains(&expr.hir_id.local_id) {
409+
let blk_result_scope = expr_cx
410+
.parent
411+
.and_then(|if_parent| visitor.scope_tree.default_temporary_scope(if_parent).0);
412+
record_rvalue_scope_if_borrow_expr(visitor, then, blk_result_scope);
413+
}
372414
let data = if expr.span.at_least_rust_2024() {
373415
ScopeData::IfThenRescope
374416
} else {
@@ -473,7 +515,7 @@ fn resolve_local<'tcx>(
473515
if let Some(scope) =
474516
visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) =>
475517
{
476-
// This expression was lifetime-extended by a parent let binding. E.g.
518+
// This expression was lifetime-extended by a parent let binding or block. E.g.
477519
//
478520
// let a = {
479521
// super let b = temp();
@@ -489,7 +531,8 @@ fn resolve_local<'tcx>(
489531
true
490532
}
491533
LetKind::Super => {
492-
// This `super let` is not subject to lifetime extension from a parent let binding. E.g.
534+
// This `super let` is not subject to lifetime extension from a parent let binding or
535+
// block. E.g.
493536
//
494537
// identity({ super let x = temp(); &x }).method();
495538
//
@@ -500,10 +543,16 @@ fn resolve_local<'tcx>(
500543
if let Some(inner_scope) = visitor.cx.var_parent {
501544
(visitor.cx.var_parent, _) = visitor.scope_tree.default_temporary_scope(inner_scope)
502545
}
503-
// Don't lifetime-extend child `super let`s or block tail expressions' temporaries in
504-
// the initializer when this `super let` is not itself extended by a parent `let`
505-
// (#145784). Block tail expressions are temporary drop scopes in Editions 2024 and
506-
// later, their temps shouldn't outlive the block in e.g. `f(pin!({ &temp() }))`.
546+
// Don't apply lifetime extension to the initializer of non-extended `super let`.
547+
// This helps ensure that `{ super let x = &$EXPR; x }` is equivalent to `&$EXPR` in
548+
// non-extending contexts: we want to avoid extending temporaries in `$EXPR` past what
549+
// their temporary scopes would otherwise be (#145784).
550+
// Currently, this shouldn't do anything. The discrepancy in #145784 was due to
551+
// `{ super let x = &{ &temp() }; x }` extending `temp()` to outlive its immediately
552+
// enclosing temporary scope (the block tail expression in Rust 2024), whereas in a
553+
// non-extending context, `&{ &temp() }` would drop `temp()` at the end of the block.
554+
// This particular quirk no longer exists: lifetime extension rules are applied to block
555+
// tail expressions, so `temp()` is extended past the block in the latter case as well.
507556
false
508557
}
509558
};
@@ -602,87 +651,92 @@ fn resolve_local<'tcx>(
602651
| PatKind::Err(_) => false,
603652
}
604653
}
654+
}
605655

606-
/// If `expr` matches the `E&` grammar, then records an extended rvalue scope as appropriate:
607-
///
608-
/// ```text
609-
/// E& = & ET
610-
/// | StructName { ..., f: E&, ... }
611-
/// | [ ..., E&, ... ]
612-
/// | ( ..., E&, ... )
613-
/// | {...; E&}
614-
/// | { super let ... = E&; ... }
615-
/// | if _ { ...; E& } else { ...; E& }
616-
/// | match _ { ..., _ => E&, ... }
617-
/// | box E&
618-
/// | E& as ...
619-
/// | ( E& )
620-
/// ```
621-
fn record_rvalue_scope_if_borrow_expr<'tcx>(
622-
visitor: &mut ScopeResolutionVisitor<'tcx>,
623-
expr: &hir::Expr<'_>,
624-
blk_id: Option<Scope>,
625-
) {
626-
match expr.kind {
627-
hir::ExprKind::AddrOf(_, _, subexpr) => {
628-
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
629-
visitor.scope_tree.record_rvalue_candidate(
630-
subexpr.hir_id,
631-
RvalueCandidate { target: subexpr.hir_id.local_id, lifetime: blk_id },
632-
);
633-
}
634-
hir::ExprKind::Struct(_, fields, _) => {
635-
for field in fields {
636-
record_rvalue_scope_if_borrow_expr(visitor, field.expr, blk_id);
637-
}
656+
/// If `expr` matches the `E&` grammar, then records an extended rvalue scope as appropriate:
657+
///
658+
/// ```text
659+
/// E& = & ET
660+
/// | StructName { ..., f: E&, ... }
661+
/// | StructName(..., E&, ...)
662+
/// | [ ..., E&, ... ]
663+
/// | ( ..., E&, ... )
664+
/// | {...; E&}
665+
/// | { super let ... = E&; ... }
666+
/// | if _ { ...; E& } else { ...; E& }
667+
/// | match _ { ..., _ => E&, ... }
668+
/// | E& as ...
669+
/// ```
670+
fn record_rvalue_scope_if_borrow_expr<'tcx>(
671+
visitor: &mut ScopeResolutionVisitor<'tcx>,
672+
expr: &hir::Expr<'_>,
673+
blk_id: Option<Scope>,
674+
) {
675+
match expr.kind {
676+
hir::ExprKind::AddrOf(_, _, subexpr) => {
677+
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
678+
visitor.scope_tree.record_rvalue_candidate(
679+
subexpr.hir_id,
680+
RvalueCandidate { target: subexpr.hir_id.local_id, lifetime: blk_id },
681+
);
682+
}
683+
hir::ExprKind::Struct(_, fields, _) => {
684+
for field in fields {
685+
record_rvalue_scope_if_borrow_expr(visitor, field.expr, blk_id);
638686
}
639-
hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => {
640-
for subexpr in subexprs {
641-
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
642-
}
687+
}
688+
hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => {
689+
for subexpr in subexprs {
690+
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
643691
}
644-
hir::ExprKind::Cast(subexpr, _) => {
645-
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id)
692+
}
693+
hir::ExprKind::Cast(subexpr, _) => {
694+
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id)
695+
}
696+
hir::ExprKind::Block(block, _) => {
697+
// Mark the block as extending, so we know its extending borrows and `super let`s have
698+
// extended scopes when checking the block itself.
699+
visitor.extended_blocks.insert(block.hir_id.local_id);
700+
if let Some(subexpr) = block.expr {
701+
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
646702
}
647-
hir::ExprKind::Block(block, _) => {
648-
if let Some(subexpr) = block.expr {
649-
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
650-
}
651-
for stmt in block.stmts {
652-
if let hir::StmtKind::Let(local) = stmt.kind
653-
&& let Some(_) = local.super_
654-
{
655-
visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id);
656-
}
703+
for stmt in block.stmts {
704+
if let hir::StmtKind::Let(local) = stmt.kind
705+
&& let Some(_) = local.super_
706+
{
707+
visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id);
657708
}
658709
}
659-
hir::ExprKind::If(_, then_block, else_block) => {
660-
record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id);
661-
if let Some(else_block) = else_block {
662-
record_rvalue_scope_if_borrow_expr(visitor, else_block, blk_id);
663-
}
710+
}
711+
hir::ExprKind::If(_, then_block, else_block) => {
712+
// Mark the expression as extending, so we know its extending borrows and `super let`s
713+
// have extended scopes when checking the `if` expression's blocks.
714+
visitor.extended_blocks.insert(expr.hir_id.local_id);
715+
record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id);
716+
if let Some(else_block) = else_block {
717+
record_rvalue_scope_if_borrow_expr(visitor, else_block, blk_id);
664718
}
665-
hir::ExprKind::Match(_, arms, _) => {
666-
for arm in arms {
667-
record_rvalue_scope_if_borrow_expr(visitor, arm.body, blk_id);
668-
}
719+
}
720+
hir::ExprKind::Match(_, arms, _) => {
721+
for arm in arms {
722+
record_rvalue_scope_if_borrow_expr(visitor, arm.body, blk_id);
669723
}
670-
hir::ExprKind::Call(func, args) => {
671-
// Recurse into tuple constructors, such as `Some(&temp())`.
672-
//
673-
// That way, there is no difference between `Some(..)` and `Some { 0: .. }`,
674-
// even though the former is syntactically a function call.
675-
if let hir::ExprKind::Path(path) = &func.kind
676-
&& let hir::QPath::Resolved(None, path) = path
677-
&& let Res::SelfCtor(_) | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) = path.res
678-
{
679-
for arg in args {
680-
record_rvalue_scope_if_borrow_expr(visitor, arg, blk_id);
681-
}
724+
}
725+
hir::ExprKind::Call(func, args) => {
726+
// Recurse into tuple constructors, such as `Some(&temp())`.
727+
//
728+
// That way, there is no difference between `Some(..)` and `Some { 0: .. }`,
729+
// even though the former is syntactically a function call.
730+
if let hir::ExprKind::Path(path) = &func.kind
731+
&& let hir::QPath::Resolved(None, path) = path
732+
&& let Res::SelfCtor(_) | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) = path.res
733+
{
734+
for arg in args {
735+
record_rvalue_scope_if_borrow_expr(visitor, arg, blk_id);
682736
}
683737
}
684-
_ => {}
685738
}
739+
_ => {}
686740
}
687741
}
688742

@@ -823,6 +877,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
823877
tcx,
824878
scope_tree: ScopeTree::default(),
825879
cx: Context { parent: None, var_parent: None },
880+
extended_blocks: Default::default(),
826881
extended_super_lets: Default::default(),
827882
};
828883

tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-unwind.mir

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ fn slice_index_range(_1: &[u32], _2: std::ops::Range<usize>) -> &[u32] {
6666
StorageDead(_10);
6767
StorageDead(_9);
6868
_0 = &(*_12);
69-
StorageDead(_12);
7069
StorageDead(_8);
70+
StorageDead(_12);
7171
return;
7272
}
7373

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error[E0716]: temporary value dropped while borrowed
2+
--> $DIR/format-args-temporary-scopes.rs:33:64
3+
|
4+
LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" });
5+
| ----------------------------------^^^^^^^^^^^---------------
6+
| | | |
7+
| | | temporary value is freed at the end of this statement
8+
| | creates a temporary value which is freed while still in use
9+
| borrow later used here
10+
|
11+
= note: consider using a `let` binding to create a longer lived value
12+
= note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info)
13+
14+
error: aborting due to 1 previous error
15+
16+
For more information about this error, try `rustc --explain E0716`.
Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
11
error[E0716]: temporary value dropped while borrowed
2-
--> $DIR/format-args-temporary-scopes.rs:13:25
2+
--> $DIR/format-args-temporary-scopes.rs:17:48
33
|
4-
LL | println!("{:?}", { &temp() });
5-
| ---^^^^^---
6-
| | | |
7-
| | | temporary value is freed at the end of this statement
8-
| | creates a temporary value which is freed while still in use
4+
LL | println!("{:?}", { std::convert::identity(&temp()) });
5+
| --------------------------^^^^^^---
6+
| | | |
7+
| | | temporary value is freed at the end of this statement
8+
| | creates a temporary value which is freed while still in use
99
| borrow later used here
1010
|
1111
= note: consider using a `let` binding to create a longer lived value
1212

1313
error[E0716]: temporary value dropped while borrowed
14-
--> $DIR/format-args-temporary-scopes.rs:19:29
14+
--> $DIR/format-args-temporary-scopes.rs:24:52
1515
|
16-
LL | println!("{:?}{:?}", { &temp() }, ());
17-
| ---^^^^^---
18-
| | | |
19-
| | | temporary value is freed at the end of this statement
20-
| | creates a temporary value which is freed while still in use
16+
LL | println!("{:?}{:?}", { std::convert::identity(&temp()) }, ());
17+
| --------------------------^^^^^^---
18+
| | | |
19+
| | | temporary value is freed at the end of this statement
20+
| | creates a temporary value which is freed while still in use
2121
| borrow later used here
2222
|
2323
= note: consider using a `let` binding to create a longer lived value
2424

25-
error: aborting due to 2 previous errors
25+
error[E0716]: temporary value dropped while borrowed
26+
--> $DIR/format-args-temporary-scopes.rs:33:64
27+
|
28+
LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" });
29+
| ------------------------^^^^^^^^^^^-
30+
| | | |
31+
| | | temporary value is freed at the end of this statement
32+
| | creates a temporary value which is freed while still in use
33+
| borrow later used here
34+
|
35+
= note: consider using a `let` binding to create a longer lived value
36+
= note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info)
37+
38+
error: aborting due to 3 previous errors
2639

2740
For more information about this error, try `rustc --explain E0716`.

0 commit comments

Comments
 (0)