Skip to content

Commit

Permalink
Rollup merge of #125293 - dingxiangfei2009:tail-expr-temp-lifetime, r…
Browse files Browse the repository at this point in the history
…=estebank,davidtwco

Place tail expression behind terminating scope

This PR implements #123739 so that we can do further experiments in nightly.

A little rewrite has been applied to `for await` lowering. It was previously `unsafe { Pin::unchecked_new(into_async_iter(..)) }`. Under the edition 2024 rule, however, `into_async_iter` gets dropped at the end of the `unsafe` block. This presumably the first Edition 2024 migration rule goes by hoisting `into_async_iter(..)` into `match` one level above, so it now looks like the following.
```rust
match into_async_iter($iter_expr) {
  ref mut iter => match unsafe { Pin::unchecked_new(iter) } {
    ...
  }
}
```
  • Loading branch information
jieyouxu authored Jun 19, 2024
2 parents c9a9d5c + 0f8c3f7 commit 8eb2e5f
Show file tree
Hide file tree
Showing 19 changed files with 376 additions and 23 deletions.
54 changes: 35 additions & 19 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(<head>)`
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(<head>)) }`
// `match into_async_iter(<head>) { ref mut iter => match unsafe { Pin::new_unchecked(iter) } { ... } }`
ForLoopKind::ForAwait => {
// `::core::async_iter::IntoAsyncIterator::into_async_iter(<head>)`
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,
Expand All @@ -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(<head>)`
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
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
10 changes: 8 additions & 2 deletions compiler/rustc_hir_analysis/src/check/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1678,6 +1678,7 @@ symbols! {
shadow_call_stack,
shl,
shl_assign,
shorter_tail_lifetimes,
should_panic,
shr,
shr_assign,
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/drop/auxiliary/edition-2021-macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//@ edition:2021

#[macro_export]
macro_rules! edition_2021_block {
($($c:tt)*) => {
{ $($c)* }
}
}
9 changes: 9 additions & 0 deletions tests/ui/drop/auxiliary/edition-2024-macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//@ edition:2024
//@ compile-flags: -Zunstable-options

#[macro_export]
macro_rules! edition_2024_block {
($($c:tt)*) => {
{ $($c)* }
}
}
16 changes: 16 additions & 0 deletions tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr
Original file line number Diff line number Diff line change
@@ -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<Ref<'_, i32>>`
|
= 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`.
17 changes: 17 additions & 0 deletions tests/ui/drop/tail-expr-drop-order-negative.rs
Original file line number Diff line number Diff line change
@@ -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();
}
108 changes: 108 additions & 0 deletions tests/ui/drop/tail-expr-drop-order.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u32>>);

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<LoudDrop> {
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();
}
8 changes: 8 additions & 0 deletions tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs
Original file line number Diff line number Diff line change
@@ -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();
}
26 changes: 26 additions & 0 deletions tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr
Original file line number Diff line number Diff line change
@@ -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`.
26 changes: 26 additions & 0 deletions tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr
Original file line number Diff line number Diff line change
@@ -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<RefMut<'_, u8>, 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`.
16 changes: 16 additions & 0 deletions tests/ui/lifetimes/refcell-in-tail-expr.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
26 changes: 26 additions & 0 deletions tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading

0 comments on commit 8eb2e5f

Please sign in to comment.