fix scope of super let bindings within if let

They now use the enclosing temporary scope as their scope, regardless of
which `ScopeData` was used to mark it.
This commit is contained in:
dianne 2025-08-13 01:02:13 -07:00
parent bbf08d87eb
commit 8fc3938d99
4 changed files with 54 additions and 52 deletions

View file

@ -490,12 +490,8 @@ fn resolve_local<'tcx>(
//
// Iterate up to the enclosing destruction scope to find the same scope that will also
// be used for the result of the block itself.
while let Some(s) = visitor.cx.var_parent {
let parent = visitor.scope_tree.parent_map.get(&s).cloned();
if let Some(Scope { data: ScopeData::Destruction, .. }) = parent {
break;
}
visitor.cx.var_parent = parent;
if let Some(inner_scope) = visitor.cx.var_parent {
(visitor.cx.var_parent, _) = visitor.scope_tree.default_temporary_scope(inner_scope)
}
}
}

View file

@ -299,4 +299,43 @@ impl ScopeTree {
true
}
/// Returns the scope of non-lifetime-extended temporaries within a given scope, as well as
/// whether we've recorded a potential backwards-incompatible change to lint on.
/// Returns `None` when no enclosing temporary scope is found, such as for static items.
pub fn default_temporary_scope(&self, inner: Scope) -> (Option<Scope>, Option<Scope>) {
let mut id = inner;
let mut backwards_incompatible = None;
while let Some(&p) = self.parent_map.get(&id) {
match p.data {
ScopeData::Destruction => {
debug!("temporary_scope({inner:?}) = {id:?} [enclosing]");
return (Some(id), backwards_incompatible);
}
ScopeData::IfThenRescope | ScopeData::MatchGuard => {
debug!("temporary_scope({inner:?}) = {p:?} [enclosing]");
return (Some(p), backwards_incompatible);
}
ScopeData::Node
| ScopeData::CallSite
| ScopeData::Arguments
| ScopeData::IfThen
| ScopeData::Remainder(_) => {
// If we haven't already passed through a backwards-incompatible node,
// then check if we are passing through one now and record it if so.
// This is for now only working for cases where a temporary lifetime is
// *shortened*.
if backwards_incompatible.is_none() {
backwards_incompatible =
self.backwards_incompatible_scope.get(&p.local_id).copied();
}
id = p
}
}
}
debug!("temporary_scope({inner:?}) = None");
(None, backwards_incompatible)
}
}

View file

@ -35,41 +35,8 @@ impl RvalueScopes {
// if there's one. Static items, for instance, won't
// have an enclosing scope, hence no scope will be
// returned.
let mut id = Scope { local_id: expr_id, data: ScopeData::Node };
let mut backwards_incompatible = None;
while let Some(&p) = region_scope_tree.parent_map.get(&id) {
match p.data {
ScopeData::Destruction => {
debug!("temporary_scope({expr_id:?}) = {id:?} [enclosing]");
return (Some(id), backwards_incompatible);
}
ScopeData::IfThenRescope | ScopeData::MatchGuard => {
debug!("temporary_scope({expr_id:?}) = {p:?} [enclosing]");
return (Some(p), backwards_incompatible);
}
ScopeData::Node
| ScopeData::CallSite
| ScopeData::Arguments
| ScopeData::IfThen
| ScopeData::Remainder(_) => {
// If we haven't already passed through a backwards-incompatible node,
// then check if we are passing through one now and record it if so.
// This is for now only working for cases where a temporary lifetime is
// *shortened*.
if backwards_incompatible.is_none() {
backwards_incompatible = region_scope_tree
.backwards_incompatible_scope
.get(&p.local_id)
.copied();
}
id = p
}
}
}
debug!("temporary_scope({expr_id:?}) = None");
(None, backwards_incompatible)
region_scope_tree
.default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node })
}
/// Make an association between a sub-expression and an extended lifetime

View file

@ -25,8 +25,8 @@ fn main() {
);
#[cfg(e2024)]
(
if let _ = { super let _x = o.log(2); } { o.push(0) },
o.push(1),
if let _ = { super let _x = o.log(1); } { o.push(0) },
o.push(2),
);
});
assert_drop_order(0..=2, |o| {
@ -37,15 +37,15 @@ fn main() {
);
#[cfg(e2024)]
(
if let true = { super let _x = o.log(2); false } {} else { o.push(0) },
o.push(1),
if let true = { super let _x = o.log(0); false } {} else { o.push(1) },
o.push(2),
);
});
// `pin!` should behave likewise.
assert_drop_order(0..=2, |o| {
#[cfg(e2021)] (if let _ = pin!(o.log(2)) { o.push(0) }, o.push(1));
#[cfg(e2024)] (if let _ = pin!(o.log(2)) { o.push(0) }, o.push(1));
#[cfg(e2024)] (if let _ = pin!(o.log(1)) { o.push(0) }, o.push(2));
});
assert_drop_order(0..=2, |o| {
#[cfg(e2021)]
@ -55,8 +55,8 @@ fn main() {
);
#[cfg(e2024)]
(
if let None = Some(pin!(o.log(2))) {} else { o.push(0) },
o.push(1),
if let None = Some(pin!(o.log(0))) {} else { o.push(1) },
o.push(2),
);
});
@ -65,15 +65,15 @@ fn main() {
// dropped before the first operand's temporary. This is consistent across Editions.
assert_drop_order(0..=1, |o| {
match () {
_ if let _ = o.log(0)
&& let _ = { super let _x = o.log(1); } => {}
_ if let _ = o.log(1)
&& let _ = { super let _x = o.log(0); } => {}
_ => unreachable!(),
}
});
assert_drop_order(0..=1, |o| {
match () {
_ if let _ = o.log(0)
&& let _ = pin!(o.log(1)) => {}
_ if let _ = o.log(1)
&& let _ = pin!(o.log(0)) => {}
_ => unreachable!(),
}
});