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 } ;
@@ -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
0 commit comments