Skip to content

Commit a498031

Browse files
Auto merge of #145342 - dianne:if-let-super-let, r=<try>
fix drop scope for `super let` bindings within `if let`
2 parents 350d0ef + 8fc3938 commit a498031

File tree

4 files changed

+155
-41
lines changed

4 files changed

+155
-41
lines changed

compiler/rustc_hir_analysis/src/check/region.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -490,12 +490,8 @@ fn resolve_local<'tcx>(
490490
//
491491
// Iterate up to the enclosing destruction scope to find the same scope that will also
492492
// be used for the result of the block itself.
493-
while let Some(s) = visitor.cx.var_parent {
494-
let parent = visitor.scope_tree.parent_map.get(&s).cloned();
495-
if let Some(Scope { data: ScopeData::Destruction, .. }) = parent {
496-
break;
497-
}
498-
visitor.cx.var_parent = parent;
493+
if let Some(inner_scope) = visitor.cx.var_parent {
494+
(visitor.cx.var_parent, _) = visitor.scope_tree.default_temporary_scope(inner_scope)
499495
}
500496
}
501497
}

compiler/rustc_middle/src/middle/region.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,43 @@ impl ScopeTree {
299299

300300
true
301301
}
302+
303+
/// Returns the scope of non-lifetime-extended temporaries within a given scope, as well as
304+
/// whether we've recorded a potential backwards-incompatible change to lint on.
305+
/// Returns `None` when no enclosing temporary scope is found, such as for static items.
306+
pub fn default_temporary_scope(&self, inner: Scope) -> (Option<Scope>, Option<Scope>) {
307+
let mut id = inner;
308+
let mut backwards_incompatible = None;
309+
310+
while let Some(&p) = self.parent_map.get(&id) {
311+
match p.data {
312+
ScopeData::Destruction => {
313+
debug!("temporary_scope({inner:?}) = {id:?} [enclosing]");
314+
return (Some(id), backwards_incompatible);
315+
}
316+
ScopeData::IfThenRescope | ScopeData::MatchGuard => {
317+
debug!("temporary_scope({inner:?}) = {p:?} [enclosing]");
318+
return (Some(p), backwards_incompatible);
319+
}
320+
ScopeData::Node
321+
| ScopeData::CallSite
322+
| ScopeData::Arguments
323+
| ScopeData::IfThen
324+
| ScopeData::Remainder(_) => {
325+
// If we haven't already passed through a backwards-incompatible node,
326+
// then check if we are passing through one now and record it if so.
327+
// This is for now only working for cases where a temporary lifetime is
328+
// *shortened*.
329+
if backwards_incompatible.is_none() {
330+
backwards_incompatible =
331+
self.backwards_incompatible_scope.get(&p.local_id).copied();
332+
}
333+
id = p
334+
}
335+
}
336+
}
337+
338+
debug!("temporary_scope({inner:?}) = None");
339+
(None, backwards_incompatible)
340+
}
302341
}

compiler/rustc_middle/src/ty/rvalue_scopes.rs

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,41 +35,8 @@ impl RvalueScopes {
3535
// if there's one. Static items, for instance, won't
3636
// have an enclosing scope, hence no scope will be
3737
// returned.
38-
let mut id = Scope { local_id: expr_id, data: ScopeData::Node };
39-
let mut backwards_incompatible = None;
40-
41-
while let Some(&p) = region_scope_tree.parent_map.get(&id) {
42-
match p.data {
43-
ScopeData::Destruction => {
44-
debug!("temporary_scope({expr_id:?}) = {id:?} [enclosing]");
45-
return (Some(id), backwards_incompatible);
46-
}
47-
ScopeData::IfThenRescope | ScopeData::MatchGuard => {
48-
debug!("temporary_scope({expr_id:?}) = {p:?} [enclosing]");
49-
return (Some(p), backwards_incompatible);
50-
}
51-
ScopeData::Node
52-
| ScopeData::CallSite
53-
| ScopeData::Arguments
54-
| ScopeData::IfThen
55-
| ScopeData::Remainder(_) => {
56-
// If we haven't already passed through a backwards-incompatible node,
57-
// then check if we are passing through one now and record it if so.
58-
// This is for now only working for cases where a temporary lifetime is
59-
// *shortened*.
60-
if backwards_incompatible.is_none() {
61-
backwards_incompatible = region_scope_tree
62-
.backwards_incompatible_scope
63-
.get(&p.local_id)
64-
.copied();
65-
}
66-
id = p
67-
}
68-
}
69-
}
70-
71-
debug!("temporary_scope({expr_id:?}) = None");
72-
(None, backwards_incompatible)
38+
region_scope_tree
39+
.default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node })
7340
}
7441

7542
/// Make an association between a sub-expression and an extended lifetime

tests/ui/drop/if-let-super-let.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//! Test for #145328: ensure the lifetime of a `super let` binding within an `if let` scrutinee is
2+
//! at most the scope of the `if` condition's temporaries. Additionally, test `pin!` since it's
3+
//! implemented in terms of `super let` and exposes this behavior.
4+
//@ run-pass
5+
//@ revisions: e2021 e2024
6+
//@ [e2021] edition: 2021
7+
//@ [e2024] edition: 2024
8+
9+
#![feature(if_let_guard)]
10+
#![feature(super_let)]
11+
#![expect(irrefutable_let_patterns)]
12+
13+
use std::cell::RefCell;
14+
use std::pin::pin;
15+
16+
fn main() {
17+
// The `super let` bindings here should have the same scope as `if let` temporaries.
18+
// In Rust 2021, this means it lives past the end of the `if` expression.
19+
// In Rust 2024, this means it lives to the end of the `if`'s success block.
20+
assert_drop_order(0..=2, |o| {
21+
#[cfg(e2021)]
22+
(
23+
if let _ = { super let _x = o.log(2); } { o.push(0) },
24+
o.push(1),
25+
);
26+
#[cfg(e2024)]
27+
(
28+
if let _ = { super let _x = o.log(1); } { o.push(0) },
29+
o.push(2),
30+
);
31+
});
32+
assert_drop_order(0..=2, |o| {
33+
#[cfg(e2021)]
34+
(
35+
if let true = { super let _x = o.log(2); false } {} else { o.push(0) },
36+
o.push(1),
37+
);
38+
#[cfg(e2024)]
39+
(
40+
if let true = { super let _x = o.log(0); false } {} else { o.push(1) },
41+
o.push(2),
42+
);
43+
});
44+
45+
// `pin!` should behave likewise.
46+
assert_drop_order(0..=2, |o| {
47+
#[cfg(e2021)] (if let _ = pin!(o.log(2)) { o.push(0) }, o.push(1));
48+
#[cfg(e2024)] (if let _ = pin!(o.log(1)) { o.push(0) }, o.push(2));
49+
});
50+
assert_drop_order(0..=2, |o| {
51+
#[cfg(e2021)]
52+
(
53+
if let None = Some(pin!(o.log(2))) {} else { o.push(0) },
54+
o.push(1),
55+
);
56+
#[cfg(e2024)]
57+
(
58+
if let None = Some(pin!(o.log(0))) {} else { o.push(1) },
59+
o.push(2),
60+
);
61+
});
62+
63+
// `super let` bindings' scope should also be consistent with `if let` temporaries in guards.
64+
// Here, that means the `super let` binding in the second guard condition operand should be
65+
// dropped before the first operand's temporary. This is consistent across Editions.
66+
assert_drop_order(0..=1, |o| {
67+
match () {
68+
_ if let _ = o.log(1)
69+
&& let _ = { super let _x = o.log(0); } => {}
70+
_ => unreachable!(),
71+
}
72+
});
73+
assert_drop_order(0..=1, |o| {
74+
match () {
75+
_ if let _ = o.log(1)
76+
&& let _ = pin!(o.log(0)) => {}
77+
_ => unreachable!(),
78+
}
79+
});
80+
}
81+
82+
// # Test scaffolding...
83+
84+
struct DropOrder(RefCell<Vec<u64>>);
85+
struct LogDrop<'o>(&'o DropOrder, u64);
86+
87+
impl DropOrder {
88+
fn log(&self, n: u64) -> LogDrop<'_> {
89+
LogDrop(self, n)
90+
}
91+
fn push(&self, n: u64) {
92+
self.0.borrow_mut().push(n);
93+
}
94+
}
95+
96+
impl<'o> Drop for LogDrop<'o> {
97+
fn drop(&mut self) {
98+
self.0.push(self.1);
99+
}
100+
}
101+
102+
#[track_caller]
103+
fn assert_drop_order(
104+
ex: impl IntoIterator<Item = u64>,
105+
f: impl Fn(&DropOrder),
106+
) {
107+
let order = DropOrder(RefCell::new(Vec::new()));
108+
f(&order);
109+
let order = order.0.into_inner();
110+
let expected: Vec<u64> = ex.into_iter().collect();
111+
assert_eq!(order, expected);
112+
}

0 commit comments

Comments
 (0)