Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Place tail expression behind terminating scope #125293

Merged
merged 1 commit into from
Jun 19, 2024
Merged
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
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
Loading