8
8
9
9
use std:: mem;
10
10
11
- use rustc_data_structures:: fx:: FxHashMap ;
11
+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
12
12
use rustc_hir as hir;
13
13
use rustc_hir:: def:: { CtorKind , DefKind , Res } ;
14
14
use rustc_hir:: def_id:: DefId ;
@@ -38,6 +38,14 @@ struct ScopeResolutionVisitor<'tcx> {
38
38
39
39
cx : Context ,
40
40
41
+ /// Tracks [extending] block expressions. This is used in performing lifetime extension on block
42
+ /// tail expressions: if we've already extended the temporary scopes of extending borrows within
43
+ /// a block's tail when checking a parent `let` statement or block, we don't want to re-extend
44
+ /// 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
+
41
49
extended_super_lets : FxHashMap < hir:: ItemLocalId , Option < Scope > > ,
42
50
}
43
51
@@ -160,6 +168,20 @@ fn resolve_block<'tcx>(
160
168
. backwards_incompatible_scope
161
169
. insert ( local_id, Scope { local_id, data : ScopeData :: Node } ) ;
162
170
}
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.
176
+ // NB: This should be checked even if the block is from Rust 2021 or before. Macro
177
+ // expansion can result in nested blocks from different editions, and we always want to
178
+ // propagate the outermost extending lifetime to the innermost extending expressions.
179
+ if !visitor. extended_blocks . contains ( & blk. hir_id . local_id ) {
180
+ let blk_result_scope = prev_cx. parent . and_then ( |blk_parent| {
181
+ visitor. scope_tree . default_temporary_scope ( blk_parent) . 0
182
+ } ) ;
183
+ record_rvalue_scope_if_borrow_expr ( visitor, tail_expr, blk_result_scope) ;
184
+ }
163
185
resolve_expr ( visitor, tail_expr, terminating) ;
164
186
}
165
187
}
@@ -470,7 +492,7 @@ fn resolve_local<'tcx>(
470
492
let mut extend_initializer = true ;
471
493
if let_kind == LetKind :: Super {
472
494
if let Some ( scope) = visitor. extended_super_lets . remove ( & pat. unwrap ( ) . hir_id . local_id ) {
473
- // This expression was lifetime-extended by a parent let binding. E.g.
495
+ // This expression was lifetime-extended by a parent let binding or block . E.g.
474
496
//
475
497
// let a = {
476
498
// super let b = temp();
@@ -483,7 +505,8 @@ fn resolve_local<'tcx>(
483
505
// `super let` to its own var_scope. We use that scope.
484
506
visitor. cx . var_parent = scope;
485
507
} else {
486
- // This `super let` is not subject to lifetime extension from a parent let binding. E.g.
508
+ // This `super let` is not subject to lifetime extension from a parent let binding or
509
+ // block. E.g.
487
510
//
488
511
// identity({ super let x = temp(); &x }).method();
489
512
//
@@ -494,10 +517,16 @@ fn resolve_local<'tcx>(
494
517
if let Some ( inner_scope) = visitor. cx . var_parent {
495
518
( visitor. cx . var_parent , _) = visitor. scope_tree . default_temporary_scope ( inner_scope)
496
519
}
497
- // Don't lifetime-extend child `super let`s or block tail expressions' temporaries in
498
- // the initializer when this `super let` is not itself extended by a parent `let`
499
- // (#145784). Block tail expressions are temporary drop scopes in Editions 2024 and
500
- // later, their temps shouldn't outlive the block in e.g. `f(pin!({ &temp() }))`.
520
+ // Don't apply lifetime extension to the initializer of non-extended `super let`.
521
+ // This helps ensure that `{ super let x = &$EXPR; x }` is equivalent to `&$EXPR` in
522
+ // non-extending contexts: we want to avoid extending temporaries in `$EXPR` past what
523
+ // their temporary scopes would otherwise be (#145784).
524
+ // Currently, this shouldn't do anything. The discrepancy in #145784 was due to
525
+ // `{ super let x = &{ &temp() }; x }` extending `temp()` to outlive its immediately
526
+ // enclosing temporary scope (the block tail expression in Rust 2024), whereas in a
527
+ // non-extending context, `&{ &temp() }` would drop `temp()` at the end of the block.
528
+ // This particular quirk no longer exists: lifetime extension rules are applied to block
529
+ // tail expressions, so `temp()` is extended past the block in the latter case as well.
501
530
extend_initializer = false ;
502
531
}
503
532
}
@@ -639,6 +668,9 @@ fn record_rvalue_scope_if_borrow_expr<'tcx>(
639
668
record_rvalue_scope_if_borrow_expr ( visitor, subexpr, blk_id)
640
669
}
641
670
hir:: ExprKind :: Block ( block, _) => {
671
+ // Mark the block as extending, so we know its extending borrows and `super let`s
672
+ // have extended scopes when checking the block itself.
673
+ visitor. extended_blocks . insert ( block. hir_id . local_id ) ;
642
674
if let Some ( subexpr) = block. expr {
643
675
record_rvalue_scope_if_borrow_expr ( visitor, subexpr, blk_id) ;
644
676
}
@@ -816,6 +848,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
816
848
tcx,
817
849
scope_tree : ScopeTree :: default ( ) ,
818
850
cx : Context { parent : None , var_parent : None } ,
851
+ extended_blocks : Default :: default ( ) ,
819
852
extended_super_lets : Default :: default ( ) ,
820
853
} ;
821
854
0 commit comments