From 0f8c3f78825a1b6eb765b97f658b92252b55c5df Mon Sep 17 00:00:00 2001 From: Ding Xiang Fei Date: Fri, 12 Apr 2024 23:24:45 +0800 Subject: [PATCH] tail expression behind terminating scope --- compiler/rustc_ast_lowering/src/expr.rs | 54 ++++++--- compiler/rustc_feature/src/unstable.rs | 2 + .../rustc_hir_analysis/src/check/region.rs | 10 +- compiler/rustc_span/src/symbol.rs | 1 + .../ui/drop/auxiliary/edition-2021-macros.rs | 8 ++ .../ui/drop/auxiliary/edition-2024-macros.rs | 9 ++ ...xpr-drop-order-negative.edition2024.stderr | 16 +++ .../ui/drop/tail-expr-drop-order-negative.rs | 17 +++ tests/ui/drop/tail-expr-drop-order.rs | 108 ++++++++++++++++++ .../feature-gate-shorter_tail_lifetimes.rs | 8 ++ ...feature-gate-shorter_tail_lifetimes.stderr | 26 +++++ .../refcell-in-tail-expr.edition2021.stderr | 26 +++++ tests/ui/lifetimes/refcell-in-tail-expr.rs | 16 +++ ...rter-tail-expr-lifetime.edition2021.stderr | 26 +++++ .../lifetimes/shorter-tail-expr-lifetime.rs | 15 +++ .../ui/lifetimes/tail-expr-in-nested-expr.rs | 9 ++ .../lifetimes/tail-expr-in-nested-expr.stderr | 15 +++ .../ui/lifetimes/tail-expr-lock-poisoning.rs | 29 +++++ tests/ui/nll/issue-52534-1.rs | 4 +- 19 files changed, 376 insertions(+), 23 deletions(-) create mode 100644 tests/ui/drop/auxiliary/edition-2021-macros.rs create mode 100644 tests/ui/drop/auxiliary/edition-2024-macros.rs create mode 100644 tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr create mode 100644 tests/ui/drop/tail-expr-drop-order-negative.rs create mode 100644 tests/ui/drop/tail-expr-drop-order.rs create mode 100644 tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs create mode 100644 tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr create mode 100644 tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr create mode 100644 tests/ui/lifetimes/refcell-in-tail-expr.rs create mode 100644 tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr create mode 100644 tests/ui/lifetimes/shorter-tail-expr-lifetime.rs create mode 100644 tests/ui/lifetimes/tail-expr-in-nested-expr.rs create mode 100644 tests/ui/lifetimes/tail-expr-in-nested-expr.stderr create mode 100644 tests/ui/lifetimes/tail-expr-lock-poisoning.rs 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 58832cb108750..77e72b5f4fb67 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 {