88
99use std:: mem;
1010
11- use rustc_data_structures:: fx:: FxHashMap ;
11+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
1212use rustc_hir as hir;
1313use rustc_hir:: def:: { CtorKind , DefKind , Res } ;
1414use rustc_hir:: def_id:: DefId ;
@@ -38,6 +38,14 @@ struct ScopeResolutionVisitor<'tcx> {
3838
3939 cx : Context ,
4040
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+
4149 extended_super_lets : FxHashMap < hir:: ItemLocalId , Option < Scope > > ,
4250}
4351
@@ -160,6 +168,20 @@ 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.
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+ }
163185 resolve_expr ( visitor, tail_expr, terminating) ;
164186 }
165187 }
@@ -470,7 +492,7 @@ fn resolve_local<'tcx>(
470492 let mut extend_initializer = true ;
471493 if let_kind == LetKind :: Super {
472494 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.
474496 //
475497 // let a = {
476498 // super let b = temp();
@@ -483,7 +505,8 @@ fn resolve_local<'tcx>(
483505 // `super let` to its own var_scope. We use that scope.
484506 visitor. cx . var_parent = scope;
485507 } 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.
487510 //
488511 // identity({ super let x = temp(); &x }).method();
489512 //
@@ -494,10 +517,16 @@ fn resolve_local<'tcx>(
494517 if let Some ( inner_scope) = visitor. cx . var_parent {
495518 ( visitor. cx . var_parent , _) = visitor. scope_tree . default_temporary_scope ( inner_scope)
496519 }
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.
501530 extend_initializer = false ;
502531 }
503532 }
@@ -639,6 +668,9 @@ fn record_rvalue_scope_if_borrow_expr<'tcx>(
639668 record_rvalue_scope_if_borrow_expr ( visitor, subexpr, blk_id)
640669 }
641670 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 ) ;
642674 if let Some ( subexpr) = block. expr {
643675 record_rvalue_scope_if_borrow_expr ( visitor, subexpr, blk_id) ;
644676 }
@@ -816,6 +848,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
816848 tcx,
817849 scope_tree : ScopeTree :: default ( ) ,
818850 cx : Context { parent : None , var_parent : None } ,
851+ extended_blocks : Default :: default ( ) ,
819852 extended_super_lets : Default :: default ( ) ,
820853 } ;
821854
0 commit comments