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] 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 } ;
@@ -645,6 +694,9 @@ fn record_rvalue_scope_if_borrow_expr<'tcx>(
645694 record_rvalue_scope_if_borrow_expr ( visitor, subexpr, blk_id)
646695 }
647696 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 ) ;
648700 if let Some ( subexpr) = block. expr {
649701 record_rvalue_scope_if_borrow_expr ( visitor, subexpr, blk_id) ;
650702 }
@@ -657,6 +709,9 @@ fn record_rvalue_scope_if_borrow_expr<'tcx>(
657709 }
658710 }
659711 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 ) ;
660715 record_rvalue_scope_if_borrow_expr ( visitor, then_block, blk_id) ;
661716 if let Some ( else_block) = else_block {
662717 record_rvalue_scope_if_borrow_expr ( visitor, else_block, blk_id) ;
@@ -822,6 +877,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
822877 tcx,
823878 scope_tree : ScopeTree :: default ( ) ,
824879 cx : Context { parent : None , var_parent : None } ,
880+ extended_blocks : Default :: default ( ) ,
825881 extended_super_lets : Default :: default ( ) ,
826882 } ;
827883
0 commit comments