Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cd59ee7

Browse files
Rollup merge of #145342 - dianne:if-let-super-let, r=nnethercote
fix drop scope for `super let` bindings within `if let` Fixes #145328 by making non-lifetime-extended `super let` reuse the logic used to compute drop scopes for non-lifetime-extended temporaries. Also fixes #145374, which regressed due to #143376 introducing `if let`-like scopes for match arms with guards. Tracking issue for `super let`: #139076 This is a regression fix / breaking change for macros stably exposing `super let`, including `pin!` and `format_args!`. Nominating to be discussed alongside #145328: ```@rustbot``` label +I-lang-nominated +I-libs-api-nominated
2 parents 98f6887 + 8fc3938 commit cd59ee7

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)

AltStyle によって変換されたページ (->オリジナル) /