Skip to content

Commit 90d7e44

Browse files
authored
Unrolled build for #145838
Rollup merge of #145838 - dianne:non-extending-super-let, r=jackh726,traviscross don't apply temporary lifetime extension rules to non-extended `super let` Reference PR: rust-lang/reference#1980 This changes the semantics for `super let` (and macros implemented in terms of it, such as `pin!`, `format_args!`, `write!`, and `println!`) as suggested by ````@theemathas```` in #145784 (comment), making `super let` initializers only count as [extending expressions](https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions) when the `super let` itself is within an extending block. Since `super let` initializers aren't temporary drop scopes, their temporaries outside of inner temporary scopes are effectively always extended, even when not in extending positions; this only affects two cases as far as I can tell: - Block tail expressions in Rust 2024. This PR makes `f(pin!({ &temp() }))` drop `temp()` at the end of the block in Rust 2024, whereas previously it would live until after the call to `f` because syntactically the `temp()` was in an extending position as a result of `super let` in `pin!`'s expansion. - `super let` nested within a non-extended `super let` is no longer extended. i.e. a normal `let` is required to treat `super let`s as extending (in which case nested `super let`s will also be extending). Closes #145784 This is a breaking change. Both static and dynamic semantics are affected. The most likely breakage is for programs to stop compiling, but it's technically possible for drop order to silently change as well (as in #145784). Since this affects stable macros, it probably would need a crater run. Nominating for discussion alongside #145784: ````@rustbot```` label +I-lang-nominated +I-libs-api-nominated Tracking issue for `super let`: #139076
2 parents ba4b643 + d5b5a4a commit 90d7e44

File tree

4 files changed

+259
-5
lines changed

4 files changed

+259
-5
lines changed

compiler/rustc_hir_analysis/src/check/region.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,12 @@ fn resolve_local<'tcx>(
467467
// A, but the inner rvalues `a()` and `b()` have an extended lifetime
468468
// due to rule C.
469469

470-
if let_kind == LetKind::Super {
471-
if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) {
470+
let extend_initializer = match let_kind {
471+
LetKind::Regular => true,
472+
LetKind::Super
473+
if let Some(scope) =
474+
visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) =>
475+
{
472476
// This expression was lifetime-extended by a parent let binding. E.g.
473477
//
474478
// let a = {
@@ -481,7 +485,10 @@ fn resolve_local<'tcx>(
481485
// Processing of `let a` will have already decided to extend the lifetime of this
482486
// `super let` to its own var_scope. We use that scope.
483487
visitor.cx.var_parent = scope;
484-
} else {
488+
// Extend temporaries to live in the same scope as the parent `let`'s bindings.
489+
true
490+
}
491+
LetKind::Super => {
485492
// This `super let` is not subject to lifetime extension from a parent let binding. E.g.
486493
//
487494
// identity({ super let x = temp(); &x }).method();
@@ -493,10 +500,17 @@ fn resolve_local<'tcx>(
493500
if let Some(inner_scope) = visitor.cx.var_parent {
494501
(visitor.cx.var_parent, _) = visitor.scope_tree.default_temporary_scope(inner_scope)
495502
}
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() }))`.
507+
false
496508
}
497-
}
509+
};
498510

499-
if let Some(expr) = init {
511+
if let Some(expr) = init
512+
&& extend_initializer
513+
{
500514
record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent);
501515

502516
if let Some(pat) = pat {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error[E0716]: temporary value dropped while borrowed
2+
--> $DIR/format-args-temporary-scopes.rs:13:25
3+
|
4+
LL | println!("{:?}", { &temp() });
5+
| ---^^^^^---
6+
| | | |
7+
| | | temporary value is freed at the end of this statement
8+
| | creates a temporary value which is freed while still in use
9+
| borrow later used here
10+
|
11+
= note: consider using a `let` binding to create a longer lived value
12+
13+
error[E0716]: temporary value dropped while borrowed
14+
--> $DIR/format-args-temporary-scopes.rs:19:29
15+
|
16+
LL | println!("{:?}{:?}", { &temp() }, ());
17+
| ---^^^^^---
18+
| | | |
19+
| | | temporary value is freed at the end of this statement
20+
| | creates a temporary value which is freed while still in use
21+
| borrow later used here
22+
|
23+
= note: consider using a `let` binding to create a longer lived value
24+
25+
error: aborting due to 2 previous errors
26+
27+
For more information about this error, try `rustc --explain E0716`.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//! Test for #145784 as it relates to format arguments: arguments to macros such as `println!`
2+
//! should obey normal temporary scoping rules.
3+
//@ revisions: e2021 e2024
4+
//@ [e2021] check-pass
5+
//@ [e2021] edition: 2021
6+
//@ [e2024] edition: 2024
7+
8+
fn temp() {}
9+
10+
fn main() {
11+
// In Rust 2024, block tail expressions are temporary scopes, so the result of `temp()` is
12+
// dropped after evaluating `&temp()`.
13+
println!("{:?}", { &temp() });
14+
//[e2024]~^ ERROR: temporary value dropped while borrowed [E0716]
15+
16+
// In Rust 1.89, `format_args!` extended the lifetime of all extending expressions in its
17+
// arguments when provided with two or more arguments. This caused the result of `temp()` to
18+
// outlive the result of the block, making this compile.
19+
println!("{:?}{:?}", { &temp() }, ());
20+
//[e2024]~^ ERROR: temporary value dropped while borrowed [E0716]
21+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
//! Test for #145784: the argument to `pin!` should be treated as an extending expression if and
2+
//! only if the whole `pin!` invocation is an extending expression. Likewise, since `pin!` is
3+
//! implemented in terms of `super let`, test the same for `super let` initializers. Since the
4+
//! argument to `pin!` and the initializer of `super let` are not temporary drop scopes, this only
5+
//! affects lifetimes in two cases:
6+
//!
7+
//! - Block tail expressions in Rust 2024, which are both extending expressions and temporary drop
8+
//! scopes; treating them as extending expressions within a non-extending `pin!` resulted in borrow
9+
//! expression operands living past the end of the block.
10+
//!
11+
//! - Nested `super let` statements, which can have their binding and temporary lifetimes extended
12+
//! when the block they're in is an extending expression.
13+
//!
14+
//! For more information on extending expressions, see
15+
//! https://doc.rust-lang.org/reference/destructors.html#extending-based-on-expressions
16+
//!
17+
//! For tests that `super let` initializers aren't temporary drop scopes, and tests for
18+
//! lifetime-extended `super let`, see tests/ui/borrowck/super-let-lifetime-and-drop.rs
19+
//@ run-pass
20+
//@ revisions: e2021 e2024
21+
//@ [e2021] edition: 2021
22+
//@ [e2024] edition: 2024
23+
24+
#![feature(super_let)]
25+
#![allow(unused_braces)]
26+
27+
use std::cell::RefCell;
28+
use std::pin::pin;
29+
30+
fn f<T>(_: LogDrop<'_>, x: T) -> T { x }
31+
32+
fn main() {
33+
// Test block arguments to `pin!` in non-extending expressions.
34+
// In Rust 2021, block tail expressions aren't temporary drop scopes, so their temporaries
35+
// should outlive the `pin!` invocation.
36+
// In Rust 2024, block tail expressions are temporary drop scopes, so their temporaries should
37+
// be dropped after evaluating the tail expression within the `pin!` invocation.
38+
// By nesting two `pin!` calls, this ensures non-extended `pin!` doesn't extend an inner `pin!`.
39+
assert_drop_order(1..=3, |o| {
40+
#[cfg(e2021)]
41+
(
42+
pin!((
43+
pin!({ &o.log(3) as *const LogDrop<'_> }),
44+
drop(o.log(1)),
45+
)),
46+
drop(o.log(2)),
47+
);
48+
#[cfg(e2024)]
49+
(
50+
pin!((
51+
pin!({ &o.log(1) as *const LogDrop<'_> }),
52+
drop(o.log(2)),
53+
)),
54+
drop(o.log(3)),
55+
);
56+
});
57+
58+
// The same holds for `super let` initializers in non-extending expressions.
59+
assert_drop_order(1..=4, |o| {
60+
#[cfg(e2021)]
61+
(
62+
{
63+
super let _ = {
64+
super let _ = { &o.log(4) as *const LogDrop<'_> };
65+
drop(o.log(1))
66+
};
67+
drop(o.log(2))
68+
},
69+
drop(o.log(3)),
70+
);
71+
#[cfg(e2024)]
72+
(
73+
{
74+
super let _ = {
75+
super let _ = { &o.log(1) as *const LogDrop<'_> };
76+
drop(o.log(2))
77+
};
78+
drop(o.log(3))
79+
},
80+
drop(o.log(4)),
81+
);
82+
});
83+
84+
// Within an extending expression, the argument to `pin!` is also an extending expression,
85+
// allowing borrow operands in block tail expressions to have extended lifetimes.
86+
assert_drop_order(1..=2, |o| {
87+
let _ = pin!({ &o.log(2) as *const LogDrop<'_> });
88+
drop(o.log(1));
89+
});
90+
91+
// The same holds for `super let` initializers in extending expressions.
92+
assert_drop_order(1..=2, |o| {
93+
let _ = { super let _ = { &o.log(2) as *const LogDrop<'_> }; };
94+
drop(o.log(1));
95+
});
96+
97+
// We have extending borrow expressions within an extending block
98+
// expression (within an extending borrow expression) within a
99+
// non-extending expresion within the initializer expression.
100+
#[cfg(e2021)]
101+
{
102+
// These two should be the same.
103+
assert_drop_order(1..=3, |e| {
104+
let _v = f(e.log(1), &{ &raw const *&e.log(2) });
105+
drop(e.log(3));
106+
});
107+
assert_drop_order(1..=3, |e| {
108+
let _v = f(e.log(1), {
109+
super let v = &{ &raw const *&e.log(2) };
110+
v
111+
});
112+
drop(e.log(3));
113+
});
114+
}
115+
#[cfg(e2024)]
116+
{
117+
// These two should be the same.
118+
assert_drop_order(1..=3, |e| {
119+
let _v = f(e.log(2), &{ &raw const *&e.log(1) });
120+
drop(e.log(3));
121+
});
122+
assert_drop_order(1..=3, |e| {
123+
let _v = f(e.log(2), {
124+
super let v = &{ &raw const *&e.log(1) };
125+
v
126+
});
127+
drop(e.log(3));
128+
});
129+
}
130+
131+
// We have extending borrow expressions within a non-extending
132+
// expression within the initializer expression.
133+
//
134+
// These two should be the same.
135+
assert_drop_order(1..=3, |e| {
136+
let _v = f(e.log(1), &&raw const *&e.log(2));
137+
drop(e.log(3));
138+
});
139+
assert_drop_order(1..=3, |e| {
140+
let _v = f(e.log(1), {
141+
super let v = &&raw const *&e.log(2);
142+
v
143+
});
144+
drop(e.log(3));
145+
});
146+
147+
// We have extending borrow expressions within an extending block
148+
// expression (within an extending borrow expression) within the
149+
// initializer expression.
150+
//
151+
// These two should be the same.
152+
assert_drop_order(1..=2, |e| {
153+
let _v = &{ &raw const *&e.log(2) };
154+
drop(e.log(1));
155+
});
156+
assert_drop_order(1..=2, |e| {
157+
let _v = {
158+
super let v = &{ &raw const *&e.log(2) };
159+
v
160+
};
161+
drop(e.log(1));
162+
});
163+
}
164+
165+
// # Test scaffolding...
166+
167+
struct DropOrder(RefCell<Vec<u64>>);
168+
struct LogDrop<'o>(&'o DropOrder, u64);
169+
170+
impl DropOrder {
171+
fn log(&self, n: u64) -> LogDrop<'_> {
172+
LogDrop(self, n)
173+
}
174+
}
175+
176+
impl<'o> Drop for LogDrop<'o> {
177+
fn drop(&mut self) {
178+
self.0.0.borrow_mut().push(self.1);
179+
}
180+
}
181+
182+
#[track_caller]
183+
fn assert_drop_order(
184+
ex: impl IntoIterator<Item = u64>,
185+
f: impl Fn(&DropOrder),
186+
) {
187+
let order = DropOrder(RefCell::new(Vec::new()));
188+
f(&order);
189+
let order = order.0.into_inner();
190+
let expected: Vec<u64> = ex.into_iter().collect();
191+
assert_eq!(order, expected);
192+
}

0 commit comments

Comments
 (0)