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
} ;
@@ -602,87 +651,92 @@ fn resolve_local<'tcx>(
602
651
| PatKind :: Err ( _) => false ,
603
652
}
604
653
}
654
+ }
605
655
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) ;
638
686
}
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 ) ;
643
691
}
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) ;
646
702
}
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) ;
657
708
}
658
709
}
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) ;
664
718
}
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 ) ;
669
723
}
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 ) ;
682
736
}
683
737
}
684
- _ => { }
685
738
}
739
+ _ => { }
686
740
}
687
741
}
688
742
@@ -823,6 +877,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
823
877
tcx,
824
878
scope_tree : ScopeTree :: default ( ) ,
825
879
cx : Context { parent : None , var_parent : None } ,
880
+ extended_blocks : Default :: default ( ) ,
826
881
extended_super_lets : Default :: default ( ) ,
827
882
} ;
828
883
0 commit comments