Skip to content

Commit 5b86c0d

Browse files
tail expression behind terminating scope
1 parent db8aca4 commit 5b86c0d

19 files changed

+376
-23
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+35-19
Original file line numberDiff line numberDiff line change
@@ -1712,24 +1712,28 @@ impl<'hir> LoweringContext<'_, 'hir> {
17121712
// `mut iter => { ... }`
17131713
let iter_arm = self.arm(iter_pat, loop_expr);
17141714

1715-
let into_iter_expr = match loop_kind {
1715+
let match_expr = match loop_kind {
17161716
ForLoopKind::For => {
17171717
// `::std::iter::IntoIterator::into_iter(<head>)`
1718-
self.expr_call_lang_item_fn(
1718+
let into_iter_expr = self.expr_call_lang_item_fn(
17191719
head_span,
17201720
hir::LangItem::IntoIterIntoIter,
17211721
arena_vec![self; head],
1722-
)
1722+
);
1723+
1724+
self.arena.alloc(self.expr_match(
1725+
for_span,
1726+
into_iter_expr,
1727+
arena_vec![self; iter_arm],
1728+
hir::MatchSource::ForLoopDesugar,
1729+
))
17231730
}
1724-
// ` unsafe { Pin::new_unchecked(&mut into_async_iter(<head>)) }`
1731+
// `match into_async_iter(<head>) { ref mut iter => match unsafe { Pin::new_unchecked(iter) } { ... } }`
17251732
ForLoopKind::ForAwait => {
1726-
// `::core::async_iter::IntoAsyncIterator::into_async_iter(<head>)`
1727-
let iter = self.expr_call_lang_item_fn(
1728-
head_span,
1729-
hir::LangItem::IntoAsyncIterIntoIter,
1730-
arena_vec![self; head],
1731-
);
1732-
let iter = self.expr_mut_addr_of(head_span, iter);
1733+
let iter_ident = iter;
1734+
let (async_iter_pat, async_iter_pat_id) =
1735+
self.pat_ident_binding_mode(head_span, iter_ident, hir::BindingMode::REF_MUT);
1736+
let iter = self.expr_ident_mut(head_span, iter_ident, async_iter_pat_id);
17331737
// `Pin::new_unchecked(...)`
17341738
let iter = self.arena.alloc(self.expr_call_lang_item_fn_mut(
17351739
head_span,
@@ -1738,17 +1742,29 @@ impl<'hir> LoweringContext<'_, 'hir> {
17381742
));
17391743
// `unsafe { ... }`
17401744
let iter = self.arena.alloc(self.expr_unsafe(iter));
1741-
iter
1745+
let inner_match_expr = self.arena.alloc(self.expr_match(
1746+
for_span,
1747+
iter,
1748+
arena_vec![self; iter_arm],
1749+
hir::MatchSource::ForLoopDesugar,
1750+
));
1751+
1752+
// `::core::async_iter::IntoAsyncIterator::into_async_iter(<head>)`
1753+
let iter = self.expr_call_lang_item_fn(
1754+
head_span,
1755+
hir::LangItem::IntoAsyncIterIntoIter,
1756+
arena_vec![self; head],
1757+
);
1758+
let iter_arm = self.arm(async_iter_pat, inner_match_expr);
1759+
self.arena.alloc(self.expr_match(
1760+
for_span,
1761+
iter,
1762+
arena_vec![self; iter_arm],
1763+
hir::MatchSource::ForLoopDesugar,
1764+
))
17421765
}
17431766
};
17441767

1745-
let match_expr = self.arena.alloc(self.expr_match(
1746-
for_span,
1747-
into_iter_expr,
1748-
arena_vec![self; iter_arm],
1749-
hir::MatchSource::ForLoopDesugar,
1750-
));
1751-
17521768
// This is effectively `{ let _result = ...; _result }`.
17531769
// The construct was introduced in #21984 and is necessary to make sure that
17541770
// temporaries in the `head` expression are dropped and do not leak to the

compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,8 @@ declare_features! (
588588
(incomplete, return_type_notation, "1.70.0", Some(109417)),
589589
/// Allows `extern "rust-cold"`.
590590
(unstable, rust_cold_cc, "1.63.0", Some(97544)),
591+
/// Shortern the tail expression lifetime
592+
(unstable, shorter_tail_lifetimes, "1.79.0", Some(123739)),
591593
/// Allows the use of SIMD types in functions declared in `extern` blocks.
592594
(unstable, simd_ffi, "1.0.0", Some(27731)),
593595
/// Allows specialization of implementations (RFC 1210).

compiler/rustc_hir_analysis/src/check/region.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
//!
77
//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/borrow_check.html
88
9-
use rustc_ast::visit::visit_opt;
109
use rustc_data_structures::fx::FxHashSet;
1110
use rustc_hir as hir;
1211
use rustc_hir::def_id::DefId;
@@ -168,7 +167,14 @@ fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx h
168167
hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => visitor.visit_stmt(statement),
169168
}
170169
}
171-
visit_opt!(visitor, visit_expr, &blk.expr);
170+
if let Some(tail_expr) = blk.expr {
171+
if visitor.tcx.features().shorter_tail_lifetimes
172+
&& blk.span.edition().at_least_rust_2024()
173+
{
174+
visitor.terminating_scopes.insert(tail_expr.hir_id.local_id);
175+
}
176+
visitor.visit_expr(tail_expr);
177+
}
172178
}
173179

174180
visitor.cx = prev_cx;

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,7 @@ symbols! {
16731673
shadow_call_stack,
16741674
shl,
16751675
shl_assign,
1676+
shorter_tail_lifetimes,
16761677
should_panic,
16771678
shr,
16781679
shr_assign,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//@ edition:2021
2+
3+
#[macro_export]
4+
macro_rules! edition_2021_block {
5+
($($c:tt)*) => {
6+
{ $($c)* }
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//@ edition:2024
2+
//@ compile-flags: -Zunstable-options
3+
4+
#[macro_export]
5+
macro_rules! edition_2024_block {
6+
($($c:tt)*) => {
7+
{ $($c)* }
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error[E0716]: temporary value dropped while borrowed
2+
--> $DIR/tail-expr-drop-order-negative.rs:11:15
3+
|
4+
LL | x.replace(std::cell::RefCell::new(123).borrow()).is_some()
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
6+
| |
7+
| creates a temporary value which is freed while still in use
8+
LL |
9+
LL | }
10+
| - borrow might be used here, when `x` is dropped and runs the destructor for type `Option<Ref<'_, i32>>`
11+
|
12+
= note: consider using a `let` binding to create a longer lived value
13+
14+
error: aborting due to 1 previous error
15+
16+
For more information about this error, try `rustc --explain E0716`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@ revisions: edition2021 edition2024
2+
//@ [edition2024] compile-flags: -Zunstable-options
3+
//@ [edition2024] edition: 2024
4+
//@ [edition2021] check-pass
5+
6+
#![feature(shorter_tail_lifetimes)]
7+
8+
fn why_would_you_do_this() -> bool {
9+
let mut x = None;
10+
// Make a temporary `RefCell` and put a `Ref` that borrows it in `x`.
11+
x.replace(std::cell::RefCell::new(123).borrow()).is_some()
12+
//[edition2024]~^ ERROR: temporary value dropped while borrowed
13+
}
14+
15+
fn main() {
16+
why_would_you_do_this();
17+
}

tests/ui/drop/tail-expr-drop-order.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//@ aux-build:edition-2021-macros.rs
2+
//@ aux-build:edition-2024-macros.rs
3+
//@ compile-flags: -Z validate-mir -Zunstable-options
4+
//@ edition: 2024
5+
//@ run-pass
6+
7+
#![feature(shorter_tail_lifetimes)]
8+
#![allow(unused_imports)]
9+
#![allow(dead_code)]
10+
#![allow(unused_variables)]
11+
12+
#[macro_use]
13+
extern crate edition_2021_macros;
14+
#[macro_use]
15+
extern crate edition_2024_macros;
16+
use std::cell::RefCell;
17+
use std::convert::TryInto;
18+
19+
#[derive(Default)]
20+
struct DropOrderCollector(RefCell<Vec<u32>>);
21+
22+
struct LoudDrop<'a>(&'a DropOrderCollector, u32);
23+
24+
impl Drop for LoudDrop<'_> {
25+
fn drop(&mut self) {
26+
println!("{}", self.1);
27+
self.0.0.borrow_mut().push(self.1);
28+
}
29+
}
30+
31+
impl DropOrderCollector {
32+
fn option_loud_drop(&self, n: u32) -> Option<LoudDrop> {
33+
Some(LoudDrop(self, n))
34+
}
35+
36+
fn loud_drop(&self, n: u32) -> LoudDrop {
37+
LoudDrop(self, n)
38+
}
39+
40+
fn assert_sorted(&self, expected: usize) {
41+
let result = self.0.borrow();
42+
assert_eq!(result.len(), expected);
43+
for i in 1..result.len() {
44+
assert!(
45+
result[i - 1] < result[i],
46+
"inversion at {} ({} followed by {})",
47+
i - 1,
48+
result[i - 1],
49+
result[i]
50+
);
51+
}
52+
}
53+
}
54+
55+
fn edition_2021_around_2021() {
56+
let c = DropOrderCollector::default();
57+
let _ = edition_2021_block! {
58+
let a = c.loud_drop(1);
59+
edition_2021_block! {
60+
let b = c.loud_drop(0);
61+
c.loud_drop(2).1
62+
}
63+
};
64+
c.assert_sorted(3);
65+
}
66+
67+
fn edition_2021_around_2024() {
68+
let c = DropOrderCollector::default();
69+
let _ = edition_2021_block! {
70+
let a = c.loud_drop(2);
71+
edition_2024_block! {
72+
let b = c.loud_drop(1);
73+
c.loud_drop(0).1
74+
}
75+
};
76+
c.assert_sorted(3);
77+
}
78+
79+
fn edition_2024_around_2021() {
80+
let c = DropOrderCollector::default();
81+
let _ = edition_2024_block! {
82+
let a = c.loud_drop(2);
83+
edition_2021_block! {
84+
let b = c.loud_drop(0);
85+
c.loud_drop(1).1
86+
}
87+
};
88+
c.assert_sorted(3);
89+
}
90+
91+
fn edition_2024_around_2024() {
92+
let c = DropOrderCollector::default();
93+
let _ = edition_2024_block! {
94+
let a = c.loud_drop(2);
95+
edition_2024_block! {
96+
let b = c.loud_drop(1);
97+
c.loud_drop(0).1
98+
}
99+
};
100+
c.assert_sorted(3);
101+
}
102+
103+
fn main() {
104+
edition_2021_around_2021();
105+
edition_2021_around_2024();
106+
edition_2024_around_2021();
107+
edition_2024_around_2024();
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn f() -> usize {
2+
let c = std::cell::RefCell::new("..");
3+
c.borrow().len() //~ ERROR: `c` does not live long enough
4+
}
5+
6+
fn main() {
7+
let _ = f();
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error[E0597]: `c` does not live long enough
2+
--> $DIR/feature-gate-shorter_tail_lifetimes.rs:3:5
3+
|
4+
LL | let c = std::cell::RefCell::new("..");
5+
| - binding `c` declared here
6+
LL | c.borrow().len()
7+
| ^---------
8+
| |
9+
| borrowed value does not live long enough
10+
| a temporary with access to the borrow is created here ...
11+
LL | }
12+
| -
13+
| |
14+
| `c` dropped here while still borrowed
15+
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
16+
|
17+
= note: the temporary is part of an expression at the end of a block;
18+
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
19+
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
20+
|
21+
LL | let x = c.borrow().len(); x
22+
| +++++++ +++
23+
24+
error: aborting due to 1 previous error
25+
26+
For more information about this error, try `rustc --explain E0597`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error[E0597]: `cell` does not live long enough
2+
--> $DIR/refcell-in-tail-expr.rs:12:27
3+
|
4+
LL | let cell = std::cell::RefCell::new(0u8);
5+
| ---- binding `cell` declared here
6+
LL |
7+
LL | if let Ok(mut byte) = cell.try_borrow_mut() {
8+
| ^^^^-----------------
9+
| |
10+
| borrowed value does not live long enough
11+
| a temporary with access to the borrow is created here ...
12+
...
13+
LL | }
14+
| -
15+
| |
16+
| `cell` dropped here while still borrowed
17+
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Result<RefMut<'_, u8>, BorrowMutError>`
18+
|
19+
help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
20+
|
21+
LL | };
22+
| +
23+
24+
error: aborting due to 1 previous error
25+
26+
For more information about this error, try `rustc --explain E0597`.
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ revisions: edition2021 edition2024
2+
//@ [edition2021] edition: 2021
3+
//@ [edition2024] edition: 2024
4+
//@ [edition2024] compile-flags: -Zunstable-options
5+
//@ [edition2024] check-pass
6+
7+
#![cfg_attr(edition2024, feature(shorter_tail_lifetimes))]
8+
9+
fn main() {
10+
let cell = std::cell::RefCell::new(0u8);
11+
12+
if let Ok(mut byte) = cell.try_borrow_mut() {
13+
//[edition2021]~^ ERROR: `cell` does not live long enough
14+
*byte = 1;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error[E0597]: `c` does not live long enough
2+
--> $DIR/shorter-tail-expr-lifetime.rs:10:5
3+
|
4+
LL | let c = std::cell::RefCell::new("..");
5+
| - binding `c` declared here
6+
LL | c.borrow().len()
7+
| ^---------
8+
| |
9+
| borrowed value does not live long enough
10+
| a temporary with access to the borrow is created here ...
11+
LL | }
12+
| -
13+
| |
14+
| `c` dropped here while still borrowed
15+
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
16+
|
17+
= note: the temporary is part of an expression at the end of a block;
18+
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
19+
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
20+
|
21+
LL | let x = c.borrow().len(); x
22+
| +++++++ +++
23+
24+
error: aborting due to 1 previous error
25+
26+
For more information about this error, try `rustc --explain E0597`.

0 commit comments

Comments
 (0)