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] 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
+
41
49
extended_super_lets : FxHashMap < hir:: ItemLocalId , Option < Scope > > ,
42
50
}
43
51
@@ -160,6 +168,17 @@ 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 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
+ }
163
182
resolve_expr ( visitor, tail_expr, terminating) ;
164
183
}
165
184
}
@@ -354,6 +373,22 @@ fn resolve_expr<'tcx>(
354
373
355
374
hir:: ExprKind :: If ( cond, then, Some ( otherwise) ) => {
356
375
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
+ }
357
392
let data = if expr. span . at_least_rust_2024 ( ) {
358
393
ScopeData :: IfThenRescope
359
394
} else {
@@ -369,6 +404,13 @@ fn resolve_expr<'tcx>(
369
404
370
405
hir:: ExprKind :: If ( cond, then, None ) => {
371
406
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
+ }
372
414
let data = if expr. span . at_least_rust_2024 ( ) {
373
415
ScopeData :: IfThenRescope
374
416
} else {
@@ -473,7 +515,7 @@ fn resolve_local<'tcx>(
473
515
if let Some ( scope) =
474
516
visitor. extended_super_lets . remove ( & pat. unwrap ( ) . hir_id . local_id ) =>
475
517
{
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.
477
519
//
478
520
// let a = {
479
521
// super let b = temp();
@@ -489,7 +531,8 @@ fn resolve_local<'tcx>(
489
531
true
490
532
}
491
533
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.
493
536
//
494
537
// identity({ super let x = temp(); &x }).method();
495
538
//
@@ -500,10 +543,16 @@ fn resolve_local<'tcx>(
500
543
if let Some ( inner_scope) = visitor. cx . var_parent {
501
544
( visitor. cx . var_parent , _) = visitor. scope_tree . default_temporary_scope ( inner_scope)
502
545
}
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.
507
556
false
508
557
}
509
558
} ;
@@ -645,6 +694,9 @@ fn record_rvalue_scope_if_borrow_expr<'tcx>(
645
694
record_rvalue_scope_if_borrow_expr ( visitor, subexpr, blk_id)
646
695
}
647
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 ) ;
648
700
if let Some ( subexpr) = block. expr {
649
701
record_rvalue_scope_if_borrow_expr ( visitor, subexpr, blk_id) ;
650
702
}
@@ -657,6 +709,9 @@ fn record_rvalue_scope_if_borrow_expr<'tcx>(
657
709
}
658
710
}
659
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 ) ;
660
715
record_rvalue_scope_if_borrow_expr ( visitor, then_block, blk_id) ;
661
716
if let Some ( else_block) = else_block {
662
717
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 {
822
877
tcx,
823
878
scope_tree : ScopeTree :: default ( ) ,
824
879
cx : Context { parent : None , var_parent : None } ,
880
+ extended_blocks : Default :: default ( ) ,
825
881
extended_super_lets : Default :: default ( ) ,
826
882
} ;
827
883
0 commit comments