Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion compiler/rustc_hir_analysis/src/check/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ fn resolve_local<'tcx>(
// A, but the inner rvalues `a()` and `b()` have an extended lifetime
// due to rule C.

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

if let Some(expr) = init {
if let Some(expr) = init
&& extend_initializer
{
record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent);

if let Some(pat) = pat {
Expand Down
27 changes: 27 additions & 0 deletions tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
error[E0716]: temporary value dropped while borrowed
--> $DIR/format-args-temporary-scopes.rs:13:25
|
LL | println!("{:?}", { &temp() });
| ---^^^^^---
| | | |
| | | temporary value is freed at the end of this statement
| | creates a temporary value which is freed while still in use
| borrow later used here
|
= note: consider using a `let` binding to create a longer lived value

error[E0716]: temporary value dropped while borrowed
--> $DIR/format-args-temporary-scopes.rs:19:29
|
LL | println!("{:?}{:?}", { &temp() }, ());
| ---^^^^^---
| | | |
| | | temporary value is freed at the end of this statement
| | creates a temporary value which is freed while still in use
| borrow later used here
|
= note: consider using a `let` binding to create a longer lived value

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0716`.
21 changes: 21 additions & 0 deletions tests/ui/borrowck/format-args-temporary-scopes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Test for #145784 as it relates to format arguments: arguments to macros such as `println!`
//! should obey normal temporary scoping rules.
//@ revisions: e2021 e2024
//@ [e2021] check-pass
//@ [e2021] edition: 2021
//@ [e2024] edition: 2024

fn temp() {}

fn main() {
// In Rust 2024, block tail expressions are temporary scopes, so the result of `temp()` is
// dropped after evaluating `&temp()`.
println!("{:?}", { &temp() });
//[e2024]~^ ERROR: temporary value dropped while borrowed [E0716]

// In Rust 1.89, `format_args!` extended the lifetime of all extending expressions in its
// arguments when provided with two or more arguments. This caused the result of `temp()` to
// outlive the result of the block, making this compile.
println!("{:?}{:?}", { &temp() }, ());
//[e2024]~^ ERROR: temporary value dropped while borrowed [E0716]
}
192 changes: 192 additions & 0 deletions tests/ui/drop/super-let-tail-expr-drop-order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Test for #145784: the argument to `pin!` should be treated as an extending expression if and
//! only if the whole `pin!` invocation is an extending expression. Likewise, since `pin!` is
//! implemented in terms of `super let`, test the same for `super let` initializers. Since the
//! argument to `pin!` and the initializer of `super let` are not temporary drop scopes, this only
//! affects lifetimes in two cases:
//!
//! - Block tail expressions in Rust 2024, which are both extending expressions and temporary drop
//! scopes; treating them as extending expressions within a non-extending `pin!` resulted in borrow
//! expression operands living past the end of the block.
//!
//! - Nested `super let` statements, which can have their binding and temporary lifetimes extended
//! when the block they're in is an extending expression.
//!
//! For more information on extending expressions, see
//! https://doc.rust-lang.org/reference/destructors.html#extending-based-on-expressions
//!
//! For tests that `super let` initializers aren't temporary drop scopes, and tests for
//! lifetime-extended `super let`, see tests/ui/borrowck/super-let-lifetime-and-drop.rs
//@ run-pass
//@ revisions: e2021 e2024
//@ [e2021] edition: 2021
//@ [e2024] edition: 2024

#![feature(super_let)]
#![allow(unused_braces)]

use std::cell::RefCell;
use std::pin::pin;

fn f<T>(_: LogDrop<'_>, x: T) -> T { x }

fn main() {
// Test block arguments to `pin!` in non-extending expressions.
// In Rust 2021, block tail expressions aren't temporary drop scopes, so their temporaries
// should outlive the `pin!` invocation.
// In Rust 2024, block tail expressions are temporary drop scopes, so their temporaries should
// be dropped after evaluating the tail expression within the `pin!` invocation.
// By nesting two `pin!` calls, this ensures non-extended `pin!` doesn't extend an inner `pin!`.
assert_drop_order(1..=3, |o| {
#[cfg(e2021)]
(
pin!((
pin!({ &o.log(3) as *const LogDrop<'_> }),
drop(o.log(1)),
)),
drop(o.log(2)),
);
#[cfg(e2024)]
(
pin!((
pin!({ &o.log(1) as *const LogDrop<'_> }),
drop(o.log(2)),
)),
drop(o.log(3)),
);
});

// The same holds for `super let` initializers in non-extending expressions.
assert_drop_order(1..=4, |o| {
#[cfg(e2021)]
(
{
super let _ = {
super let _ = { &o.log(4) as *const LogDrop<'_> };
drop(o.log(1))
};
drop(o.log(2))
},
drop(o.log(3)),
);
#[cfg(e2024)]
(
{
super let _ = {
super let _ = { &o.log(1) as *const LogDrop<'_> };
drop(o.log(2))
};
drop(o.log(3))
},
drop(o.log(4)),
);
});

// Within an extending expression, the argument to `pin!` is also an extending expression,
// allowing borrow operands in block tail expressions to have extended lifetimes.
assert_drop_order(1..=2, |o| {
let _ = pin!({ &o.log(2) as *const LogDrop<'_> });
drop(o.log(1));
});

// The same holds for `super let` initializers in extending expressions.
assert_drop_order(1..=2, |o| {
let _ = { super let _ = { &o.log(2) as *const LogDrop<'_> }; };
drop(o.log(1));
});

// We have extending borrow expressions within an extending block
// expression (within an extending borrow expression) within a
// non-extending expresion within the initializer expression.
#[cfg(e2021)]
{
// These two should be the same.
assert_drop_order(1..=3, |e| {
let _v = f(e.log(1), &{ &raw const *&e.log(2) });
drop(e.log(3));
});
assert_drop_order(1..=3, |e| {
let _v = f(e.log(1), {
super let v = &{ &raw const *&e.log(2) };
v
});
drop(e.log(3));
});
}
#[cfg(e2024)]
{
// These two should be the same.
assert_drop_order(1..=3, |e| {
let _v = f(e.log(2), &{ &raw const *&e.log(1) });
drop(e.log(3));
});
assert_drop_order(1..=3, |e| {
let _v = f(e.log(2), {
super let v = &{ &raw const *&e.log(1) };
v
});
drop(e.log(3));
});
}

// We have extending borrow expressions within a non-extending
// expression within the initializer expression.
//
// These two should be the same.
assert_drop_order(1..=3, |e| {
let _v = f(e.log(1), &&raw const *&e.log(2));
drop(e.log(3));
});
assert_drop_order(1..=3, |e| {
let _v = f(e.log(1), {
super let v = &&raw const *&e.log(2);
v
});
drop(e.log(3));
});

// We have extending borrow expressions within an extending block
// expression (within an extending borrow expression) within the
// initializer expression.
//
// These two should be the same.
assert_drop_order(1..=2, |e| {
let _v = &{ &raw const *&e.log(2) };
drop(e.log(1));
});
assert_drop_order(1..=2, |e| {
let _v = {
super let v = &{ &raw const *&e.log(2) };
v
};
drop(e.log(1));
});
}

// # Test scaffolding...

struct DropOrder(RefCell<Vec<u64>>);
struct LogDrop<'o>(&'o DropOrder, u64);

impl DropOrder {
fn log(&self, n: u64) -> LogDrop<'_> {
LogDrop(self, n)
}
}

impl<'o> Drop for LogDrop<'o> {
fn drop(&mut self) {
self.0.0.borrow_mut().push(self.1);
}
}

#[track_caller]
fn assert_drop_order(
ex: impl IntoIterator<Item = u64>,
f: impl Fn(&DropOrder),
) {
let order = DropOrder(RefCell::new(Vec::new()));
f(&order);
let order = order.0.into_inner();
let expected: Vec<u64> = ex.into_iter().collect();
assert_eq!(order, expected);
}
Loading