diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 77f95869e9dac..79cff0fbcd2ee 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -1716,24 +1716,28 @@ impl<'hir> LoweringContext<'_, 'hir> {
// `mut iter => { ... }`
let iter_arm = self.arm(iter_pat, loop_expr);
- let into_iter_expr = match loop_kind {
+ let match_expr = match loop_kind {
ForLoopKind::For => {
// `::std::iter::IntoIterator::into_iter(
)`
- self.expr_call_lang_item_fn(
+ let into_iter_expr = self.expr_call_lang_item_fn(
head_span,
hir::LangItem::IntoIterIntoIter,
arena_vec![self; head],
- )
+ );
+
+ self.arena.alloc(self.expr_match(
+ for_span,
+ into_iter_expr,
+ arena_vec![self; iter_arm],
+ hir::MatchSource::ForLoopDesugar,
+ ))
}
- // ` unsafe { Pin::new_unchecked(&mut into_async_iter()) }`
+ // `match into_async_iter() { ref mut iter => match unsafe { Pin::new_unchecked(iter) } { ... } }`
ForLoopKind::ForAwait => {
- // `::core::async_iter::IntoAsyncIterator::into_async_iter()`
- let iter = self.expr_call_lang_item_fn(
- head_span,
- hir::LangItem::IntoAsyncIterIntoIter,
- arena_vec![self; head],
- );
- let iter = self.expr_mut_addr_of(head_span, iter);
+ let iter_ident = iter;
+ let (async_iter_pat, async_iter_pat_id) =
+ self.pat_ident_binding_mode(head_span, iter_ident, hir::BindingMode::REF_MUT);
+ let iter = self.expr_ident_mut(head_span, iter_ident, async_iter_pat_id);
// `Pin::new_unchecked(...)`
let iter = self.arena.alloc(self.expr_call_lang_item_fn_mut(
head_span,
@@ -1742,17 +1746,29 @@ impl<'hir> LoweringContext<'_, 'hir> {
));
// `unsafe { ... }`
let iter = self.arena.alloc(self.expr_unsafe(iter));
- iter
+ let inner_match_expr = self.arena.alloc(self.expr_match(
+ for_span,
+ iter,
+ arena_vec![self; iter_arm],
+ hir::MatchSource::ForLoopDesugar,
+ ));
+
+ // `::core::async_iter::IntoAsyncIterator::into_async_iter()`
+ let iter = self.expr_call_lang_item_fn(
+ head_span,
+ hir::LangItem::IntoAsyncIterIntoIter,
+ arena_vec![self; head],
+ );
+ let iter_arm = self.arm(async_iter_pat, inner_match_expr);
+ self.arena.alloc(self.expr_match(
+ for_span,
+ iter,
+ arena_vec![self; iter_arm],
+ hir::MatchSource::ForLoopDesugar,
+ ))
}
};
- let match_expr = self.arena.alloc(self.expr_match(
- for_span,
- into_iter_expr,
- arena_vec![self; iter_arm],
- hir::MatchSource::ForLoopDesugar,
- ));
-
// This is effectively `{ let _result = ...; _result }`.
// The construct was introduced in #21984 and is necessary to make sure that
// temporaries in the `head` expression are dropped and do not leak to the
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index 066a5a8f046e1..5cfbcdcbbbe05 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -588,6 +588,8 @@ declare_features! (
(incomplete, return_type_notation, "1.70.0", Some(109417)),
/// Allows `extern "rust-cold"`.
(unstable, rust_cold_cc, "1.63.0", Some(97544)),
+ /// Shortern the tail expression lifetime
+ (unstable, shorter_tail_lifetimes, "1.79.0", Some(123739)),
/// Allows the use of SIMD types in functions declared in `extern` blocks.
(unstable, simd_ffi, "1.0.0", Some(27731)),
/// Allows specialization of implementations (RFC 1210).
diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs
index 72e431926ca32..2b5efd3b2f6f9 100644
--- a/compiler/rustc_hir_analysis/src/check/region.rs
+++ b/compiler/rustc_hir_analysis/src/check/region.rs
@@ -6,7 +6,6 @@
//!
//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/borrow_check.html
-use rustc_ast::visit::visit_opt;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
@@ -168,7 +167,14 @@ fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx h
hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => visitor.visit_stmt(statement),
}
}
- visit_opt!(visitor, visit_expr, &blk.expr);
+ if let Some(tail_expr) = blk.expr {
+ if visitor.tcx.features().shorter_tail_lifetimes
+ && blk.span.edition().at_least_rust_2024()
+ {
+ visitor.terminating_scopes.insert(tail_expr.hir_id.local_id);
+ }
+ visitor.visit_expr(tail_expr);
+ }
}
visitor.cx = prev_cx;
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index f44fa1bcb4fd1..9fa8086c8fad2 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1678,6 +1678,7 @@ symbols! {
shadow_call_stack,
shl,
shl_assign,
+ shorter_tail_lifetimes,
should_panic,
shr,
shr_assign,
diff --git a/tests/ui/drop/auxiliary/edition-2021-macros.rs b/tests/ui/drop/auxiliary/edition-2021-macros.rs
new file mode 100644
index 0000000000000..8a6444f8614d4
--- /dev/null
+++ b/tests/ui/drop/auxiliary/edition-2021-macros.rs
@@ -0,0 +1,8 @@
+//@ edition:2021
+
+#[macro_export]
+macro_rules! edition_2021_block {
+ ($($c:tt)*) => {
+ { $($c)* }
+ }
+}
diff --git a/tests/ui/drop/auxiliary/edition-2024-macros.rs b/tests/ui/drop/auxiliary/edition-2024-macros.rs
new file mode 100644
index 0000000000000..236340bfed4f4
--- /dev/null
+++ b/tests/ui/drop/auxiliary/edition-2024-macros.rs
@@ -0,0 +1,9 @@
+//@ edition:2024
+//@ compile-flags: -Zunstable-options
+
+#[macro_export]
+macro_rules! edition_2024_block {
+ ($($c:tt)*) => {
+ { $($c)* }
+ }
+}
diff --git a/tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr b/tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr
new file mode 100644
index 0000000000000..75fc34e409b54
--- /dev/null
+++ b/tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr
@@ -0,0 +1,16 @@
+error[E0716]: temporary value dropped while borrowed
+ --> $DIR/tail-expr-drop-order-negative.rs:11:15
+ |
+LL | x.replace(std::cell::RefCell::new(123).borrow()).is_some()
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
+ | |
+ | creates a temporary value which is freed while still in use
+LL |
+LL | }
+ | - borrow might be used here, when `x` is dropped and runs the destructor for type `Option[>`
+ |
+ = note: consider using a `let` binding to create a longer lived value
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0716`.
diff --git a/tests/ui/drop/tail-expr-drop-order-negative.rs b/tests/ui/drop/tail-expr-drop-order-negative.rs
new file mode 100644
index 0000000000000..c570b3a1ee235
--- /dev/null
+++ b/tests/ui/drop/tail-expr-drop-order-negative.rs
@@ -0,0 +1,17 @@
+//@ revisions: edition2021 edition2024
+//@ [edition2024] compile-flags: -Zunstable-options
+//@ [edition2024] edition: 2024
+//@ [edition2021] check-pass
+
+#![feature(shorter_tail_lifetimes)]
+
+fn why_would_you_do_this() -> bool {
+ let mut x = None;
+ // Make a temporary `RefCell` and put a `Ref` that borrows it in `x`.
+ x.replace(std::cell::RefCell::new(123).borrow()).is_some()
+ //[edition2024]~^ ERROR: temporary value dropped while borrowed
+}
+
+fn main() {
+ why_would_you_do_this();
+}
diff --git a/tests/ui/drop/tail-expr-drop-order.rs b/tests/ui/drop/tail-expr-drop-order.rs
new file mode 100644
index 0000000000000..5d87f980b1563
--- /dev/null
+++ b/tests/ui/drop/tail-expr-drop-order.rs
@@ -0,0 +1,108 @@
+//@ aux-build:edition-2021-macros.rs
+//@ aux-build:edition-2024-macros.rs
+//@ compile-flags: -Z validate-mir -Zunstable-options
+//@ edition: 2024
+//@ run-pass
+
+#![feature(shorter_tail_lifetimes)]
+#![allow(unused_imports)]
+#![allow(dead_code)]
+#![allow(unused_variables)]
+
+#[macro_use]
+extern crate edition_2021_macros;
+#[macro_use]
+extern crate edition_2024_macros;
+use std::cell::RefCell;
+use std::convert::TryInto;
+
+#[derive(Default)]
+struct DropOrderCollector(RefCell>);
+
+struct LoudDrop<'a>(&'a DropOrderCollector, u32);
+
+impl Drop for LoudDrop<'_> {
+ fn drop(&mut self) {
+ println!("{}", self.1);
+ self.0.0.borrow_mut().push(self.1);
+ }
+}
+
+impl DropOrderCollector {
+ fn option_loud_drop(&self, n: u32) -> Option {
+ Some(LoudDrop(self, n))
+ }
+
+ fn loud_drop(&self, n: u32) -> LoudDrop {
+ LoudDrop(self, n)
+ }
+
+ fn assert_sorted(&self, expected: usize) {
+ let result = self.0.borrow();
+ assert_eq!(result.len(), expected);
+ for i in 1..result.len() {
+ assert!(
+ result[i - 1] < result[i],
+ "inversion at {} ({} followed by {})",
+ i - 1,
+ result[i - 1],
+ result[i]
+ );
+ }
+ }
+}
+
+fn edition_2021_around_2021() {
+ let c = DropOrderCollector::default();
+ let _ = edition_2021_block! {
+ let a = c.loud_drop(1);
+ edition_2021_block! {
+ let b = c.loud_drop(0);
+ c.loud_drop(2).1
+ }
+ };
+ c.assert_sorted(3);
+}
+
+fn edition_2021_around_2024() {
+ let c = DropOrderCollector::default();
+ let _ = edition_2021_block! {
+ let a = c.loud_drop(2);
+ edition_2024_block! {
+ let b = c.loud_drop(1);
+ c.loud_drop(0).1
+ }
+ };
+ c.assert_sorted(3);
+}
+
+fn edition_2024_around_2021() {
+ let c = DropOrderCollector::default();
+ let _ = edition_2024_block! {
+ let a = c.loud_drop(2);
+ edition_2021_block! {
+ let b = c.loud_drop(0);
+ c.loud_drop(1).1
+ }
+ };
+ c.assert_sorted(3);
+}
+
+fn edition_2024_around_2024() {
+ let c = DropOrderCollector::default();
+ let _ = edition_2024_block! {
+ let a = c.loud_drop(2);
+ edition_2024_block! {
+ let b = c.loud_drop(1);
+ c.loud_drop(0).1
+ }
+ };
+ c.assert_sorted(3);
+}
+
+fn main() {
+ edition_2021_around_2021();
+ edition_2021_around_2024();
+ edition_2024_around_2021();
+ edition_2024_around_2024();
+}
diff --git a/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs b/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs
new file mode 100644
index 0000000000000..5292c44bb2d40
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs
@@ -0,0 +1,8 @@
+fn f() -> usize {
+ let c = std::cell::RefCell::new("..");
+ c.borrow().len() //~ ERROR: `c` does not live long enough
+}
+
+fn main() {
+ let _ = f();
+}
diff --git a/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr b/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr
new file mode 100644
index 0000000000000..648c3d5daa1c6
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr
@@ -0,0 +1,26 @@
+error[E0597]: `c` does not live long enough
+ --> $DIR/feature-gate-shorter_tail_lifetimes.rs:3:5
+ |
+LL | let c = std::cell::RefCell::new("..");
+ | - binding `c` declared here
+LL | c.borrow().len()
+ | ^---------
+ | |
+ | borrowed value does not live long enough
+ | a temporary with access to the borrow is created here ...
+LL | }
+ | -
+ | |
+ | `c` dropped here while still borrowed
+ | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
+ |
+ = note: the temporary is part of an expression at the end of a block;
+ consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
+help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
+ |
+LL | let x = c.borrow().len(); x
+ | +++++++ +++
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0597`.
diff --git a/tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr b/tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr
new file mode 100644
index 0000000000000..858be42d54094
--- /dev/null
+++ b/tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr
@@ -0,0 +1,26 @@
+error[E0597]: `cell` does not live long enough
+ --> $DIR/refcell-in-tail-expr.rs:12:27
+ |
+LL | let cell = std::cell::RefCell::new(0u8);
+ | ---- binding `cell` declared here
+LL |
+LL | if let Ok(mut byte) = cell.try_borrow_mut() {
+ | ^^^^-----------------
+ | |
+ | borrowed value does not live long enough
+ | a temporary with access to the borrow is created here ...
+...
+LL | }
+ | -
+ | |
+ | `cell` dropped here while still borrowed
+ | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Result, BorrowMutError>`
+ |
+help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
+ |
+LL | };
+ | +
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0597`.
diff --git a/tests/ui/lifetimes/refcell-in-tail-expr.rs b/tests/ui/lifetimes/refcell-in-tail-expr.rs
new file mode 100644
index 0000000000000..b1814c1e32713
--- /dev/null
+++ b/tests/ui/lifetimes/refcell-in-tail-expr.rs
@@ -0,0 +1,16 @@
+//@ revisions: edition2021 edition2024
+//@ [edition2021] edition: 2021
+//@ [edition2024] edition: 2024
+//@ [edition2024] compile-flags: -Zunstable-options
+//@ [edition2024] check-pass
+
+#![cfg_attr(edition2024, feature(shorter_tail_lifetimes))]
+
+fn main() {
+ let cell = std::cell::RefCell::new(0u8);
+
+ if let Ok(mut byte) = cell.try_borrow_mut() {
+ //[edition2021]~^ ERROR: `cell` does not live long enough
+ *byte = 1;
+ }
+}
diff --git a/tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr b/tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr
new file mode 100644
index 0000000000000..ad28ae2f80d66
--- /dev/null
+++ b/tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr
@@ -0,0 +1,26 @@
+error[E0597]: `c` does not live long enough
+ --> $DIR/shorter-tail-expr-lifetime.rs:10:5
+ |
+LL | let c = std::cell::RefCell::new("..");
+ | - binding `c` declared here
+LL | c.borrow().len()
+ | ^---------
+ | |
+ | borrowed value does not live long enough
+ | a temporary with access to the borrow is created here ...
+LL | }
+ | -
+ | |
+ | `c` dropped here while still borrowed
+ | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
+ |
+ = note: the temporary is part of an expression at the end of a block;
+ consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
+help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
+ |
+LL | let x = c.borrow().len(); x
+ | +++++++ +++
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0597`.
diff --git a/tests/ui/lifetimes/shorter-tail-expr-lifetime.rs b/tests/ui/lifetimes/shorter-tail-expr-lifetime.rs
new file mode 100644
index 0000000000000..0392b6c6d9ada
--- /dev/null
+++ b/tests/ui/lifetimes/shorter-tail-expr-lifetime.rs
@@ -0,0 +1,15 @@
+//@ revisions: edition2021 edition2024
+//@ [edition2024] compile-flags: -Zunstable-options
+//@ [edition2024] edition: 2024
+//@ [edition2024] run-pass
+
+#![cfg_attr(edition2024, feature(shorter_tail_lifetimes))]
+
+fn f() -> usize {
+ let c = std::cell::RefCell::new("..");
+ c.borrow().len() //[edition2021]~ ERROR: `c` does not live long enough
+}
+
+fn main() {
+ let _ = f();
+}
diff --git a/tests/ui/lifetimes/tail-expr-in-nested-expr.rs b/tests/ui/lifetimes/tail-expr-in-nested-expr.rs
new file mode 100644
index 0000000000000..a8989f22f4b5b
--- /dev/null
+++ b/tests/ui/lifetimes/tail-expr-in-nested-expr.rs
@@ -0,0 +1,9 @@
+//@ edition: 2024
+//@ compile-flags: -Zunstable-options
+
+#![feature(shorter_tail_lifetimes)]
+
+fn main() {
+ let _ = { String::new().as_str() }.len();
+ //~^ ERROR temporary value dropped while borrowed
+}
diff --git a/tests/ui/lifetimes/tail-expr-in-nested-expr.stderr b/tests/ui/lifetimes/tail-expr-in-nested-expr.stderr
new file mode 100644
index 0000000000000..f699d184bdb1d
--- /dev/null
+++ b/tests/ui/lifetimes/tail-expr-in-nested-expr.stderr
@@ -0,0 +1,15 @@
+error[E0716]: temporary value dropped while borrowed
+ --> $DIR/tail-expr-in-nested-expr.rs:7:15
+ |
+LL | let _ = { String::new().as_str() }.len();
+ | ^^^^^^^^^^^^^---------
+ | | |
+ | | 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 1 previous error
+
+For more information about this error, try `rustc --explain E0716`.
diff --git a/tests/ui/lifetimes/tail-expr-lock-poisoning.rs b/tests/ui/lifetimes/tail-expr-lock-poisoning.rs
new file mode 100644
index 0000000000000..69b8f286d774f
--- /dev/null
+++ b/tests/ui/lifetimes/tail-expr-lock-poisoning.rs
@@ -0,0 +1,29 @@
+//@ revisions: edition2021 edition2024
+//@ ignore-wasm no panic or subprocess support
+//@ [edition2024] compile-flags: -Zunstable-options
+//@ [edition2024] edition: 2024
+//@ run-pass
+#![cfg_attr(edition2024, feature(shorter_tail_lifetimes))]
+
+use std::sync::Mutex;
+
+struct PanicOnDrop;
+impl Drop for PanicOnDrop {
+ fn drop(&mut self) {
+ panic!()
+ }
+}
+
+fn f(m: &Mutex) -> i32 {
+ let _x = PanicOnDrop;
+ *m.lock().unwrap()
+}
+
+fn main() {
+ let m = Mutex::new(0);
+ let _ = std::panic::catch_unwind(|| f(&m));
+ #[cfg(edition2024)]
+ assert!(m.lock().is_ok());
+ #[cfg(edition2021)]
+ assert!(m.lock().is_err());
+}
diff --git a/tests/ui/nll/issue-52534-1.rs b/tests/ui/nll/issue-52534-1.rs
index d9ea3ae42c49e..526b81bb2d056 100644
--- a/tests/ui/nll/issue-52534-1.rs
+++ b/tests/ui/nll/issue-52534-1.rs
@@ -17,14 +17,14 @@ fn foo(x: &u32) -> &u32 {
fn baz(x: &u32) -> &&u32 {
let x = 22;
&&x
-//~^ ERROR cannot return value referencing local variable
+//~^ ERROR cannot return value referencing local variable `x`
//~| ERROR cannot return reference to temporary value
}
fn foobazbar<'a>(x: u32, y: &'a u32) -> &'a u32 {
let x = 22;
&x
-//~^ ERROR cannot return reference to local variable
+//~^ ERROR cannot return reference to local variable `x`
}
fn foobar<'a>(x: &'a u32) -> &'a u32 {
]