Skip to content

Commit b83b707

Browse files
Auto merge of #145838 - dianne:non-extending-super-let, r=<try>
don't apply temporary lifetime extension rules to non-extended `super let`
2 parents cdb45c8 + 7f4b0f4 commit b83b707

File tree

4 files changed

+249
-1
lines changed

4 files changed

+249
-1
lines changed

compiler/rustc_hir_analysis/src/check/region.rs

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

470+
let mut extend_initializer = true;
470471
if let_kind == LetKind::Super {
471472
if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) {
472473
// This expression was lifetime-extended by a parent let binding. E.g.
@@ -497,10 +498,17 @@ fn resolve_local<'tcx>(
497498
}
498499
visitor.cx.var_parent = parent;
499500
}
501+
// Don't lifetime-extend child `super let`s or block tail expressions' temporaries in
502+
// the initializer when this `super let` is not itself extended by a parent `let`
503+
// (#145784). Block tail expressions are temporary drop scopes in Editions 2024 and
504+
// later, their temps shouldn't outlive the block in e.g. `f(pin!({ &temp() }))`.
505+
extend_initializer = false;
500506
}
501507
}
502508

503-
if let Some(expr) = init {
509+
if let Some(expr) = init
510+
&& extend_initializer
511+
{
504512
record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent);
505513

506514
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)