Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2e55e56

Browse files
committed
lifetime-extend extending borrows in block tail expressions
1 parent 0db1276 commit 2e55e56

File tree

7 files changed

+109
-102
lines changed

7 files changed

+109
-102
lines changed

‎compiler/rustc_hir_analysis/src/check/region.rs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
99
use std::mem;
1010

11-
use rustc_data_structures::fx::FxHashMap;
11+
use rustc_data_structures::fx::{FxHashMap,FxHashSet};
1212
use rustc_hir as hir;
1313
use rustc_hir::def::{CtorKind, DefKind, Res};
1414
use rustc_hir::def_id::DefId;
@@ -38,6 +38,14 @@ struct ScopeResolutionVisitor<'tcx> {
3838

3939
cx: Context,
4040

41+
/// Tracks [extending] block expressions. This is used in performing lifetime extension on block
42+
/// tail expressions: if we've already extended the temporary scopes of extending borrows within
43+
/// a block's tail when checking a parent `let` statement or block, we don't want to re-extend
44+
/// them to be shorter when checking the block itself.
45+
///
46+
/// [extending]: https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions
47+
extended_blocks: FxHashSet<hir::ItemLocalId>,
48+
4149
extended_super_lets: FxHashMap<hir::ItemLocalId, Option<Scope>>,
4250
}
4351

@@ -160,6 +168,20 @@ fn resolve_block<'tcx>(
160168
.backwards_incompatible_scope
161169
.insert(local_id, Scope { local_id, data: ScopeData::Node });
162170
}
171+
// If we haven't already checked for temporary lifetime extension due to a parent `let`
172+
// statement initializer or block, do so. This, e.g., allows `temp()` in `{ &temp() }`
173+
// to outlive the block even when the block itself is not in a `let` statement
174+
// initializer. The same rules for `let` are used here, so non-extending borrows are
175+
// unaffected: `{ f(&temp()) }` drops `temp()` at the end of the block.
176+
// NB: This should be checked even if the block is from Rust 2021 or before. Macro
177+
// expansion can result in nested blocks from different editions, and we always want to
178+
// propagate the outermost extending lifetime to the innermost extending expressions.
179+
if !visitor.extended_blocks.contains(&blk.hir_id.local_id) {
180+
let blk_result_scope = prev_cx.parent.and_then(|blk_parent| {
181+
visitor.scope_tree.default_temporary_scope(blk_parent).0
182+
});
183+
record_rvalue_scope_if_borrow_expr(visitor, tail_expr, blk_result_scope);
184+
}
163185
resolve_expr(visitor, tail_expr, terminating);
164186
}
165187
}
@@ -470,7 +492,7 @@ fn resolve_local<'tcx>(
470492
let mut extend_initializer = true;
471493
if let_kind == LetKind::Super {
472494
if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) {
473-
// This expression was lifetime-extended by a parent let binding. E.g.
495+
// This expression was lifetime-extended by a parent let binding or block. E.g.
474496
//
475497
// let a = {
476498
// super let b = temp();
@@ -483,7 +505,8 @@ fn resolve_local<'tcx>(
483505
// `super let` to its own var_scope. We use that scope.
484506
visitor.cx.var_parent = scope;
485507
} else {
486-
// This `super let` is not subject to lifetime extension from a parent let binding. E.g.
508+
// This `super let` is not subject to lifetime extension from a parent let binding or
509+
// block. E.g.
487510
//
488511
// identity({ super let x = temp(); &x }).method();
489512
//
@@ -494,10 +517,16 @@ fn resolve_local<'tcx>(
494517
if let Some(inner_scope) = visitor.cx.var_parent {
495518
(visitor.cx.var_parent, _) = visitor.scope_tree.default_temporary_scope(inner_scope)
496519
}
497-
// Don't lifetime-extend child `super let`s or block tail expressions' temporaries in
498-
// the initializer when this `super let` is not itself extended by a parent `let`
499-
// (#145784). Block tail expressions are temporary drop scopes in Editions 2024 and
500-
// later, their temps shouldn't outlive the block in e.g. `f(pin!({ &temp() }))`.
520+
// Don't apply lifetime extension to the initializer of non-extended `super let`.
521+
// This helps ensure that `{ super let x = &$EXPR; x }` is equivalent to `&$EXPR` in
522+
// non-extending contexts: we want to avoid extending temporaries in `$EXPR` past what
523+
// their temporary scopes would otherwise be (#145784).
524+
// Currently, this shouldn't do anything. The discrepancy in #145784 was due to
525+
// `{ super let x = &{ &temp() }; x }` extending `temp()` to outlive its immediately
526+
// enclosing temporary scope (the block tail expression in Rust 2024), whereas in a
527+
// non-extending context, `&{ &temp() }` would drop `temp()` at the end of the block.
528+
// This particular quirk no longer exists: lifetime extension rules are applied to block
529+
// tail expressions, so `temp()` is extended past the block in the latter case as well.
501530
extend_initializer = false;
502531
}
503532
}
@@ -639,6 +668,9 @@ fn record_rvalue_scope_if_borrow_expr<'tcx>(
639668
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id)
640669
}
641670
hir::ExprKind::Block(block, _) => {
671+
// Mark the block as extending, so we know its extending borrows and `super let`s
672+
// have extended scopes when checking the block itself.
673+
visitor.extended_blocks.insert(block.hir_id.local_id);
642674
if let Some(subexpr) = block.expr {
643675
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
644676
}
@@ -816,6 +848,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
816848
tcx,
817849
scope_tree: ScopeTree::default(),
818850
cx: Context { parent: None, var_parent: None },
851+
extended_blocks: Default::default(),
819852
extended_super_lets: Default::default(),
820853
};
821854

‎tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
error[E0716]: temporary value dropped while borrowed
2-
--> $DIR/format-args-temporary-scopes.rs:13:25
2+
--> $DIR/format-args-temporary-scopes.rs:18:48
33
|
4-
LL | println!("{:?}", { &temp() });
5-
| ---^^^^^---
6-
| | | |
7-
| | | temporary value is freed at the end of this statement
8-
| | creates a temporary value which is freed while still in use
4+
LL | println!("{:?}", { std::convert::identity(&temp()) });
5+
| --------------------------^^^^^^---
6+
| | | |
7+
| | | temporary value is freed at the end of this statement
8+
| | creates a temporary value which is freed while still in use
99
| borrow later used here
1010
|
1111
= note: consider using a `let` binding to create a longer lived value
1212

1313
error[E0716]: temporary value dropped while borrowed
14-
--> $DIR/format-args-temporary-scopes.rs:19:29
14+
--> $DIR/format-args-temporary-scopes.rs:25:52
1515
|
16-
LL | println!("{:?}{:?}", { &temp() }, ());
17-
| ---^^^^^---
18-
| | | |
19-
| | | temporary value is freed at the end of this statement
20-
| | creates a temporary value which is freed while still in use
16+
LL | println!("{:?}{:?}", { std::convert::identity(&temp()) }, ());
17+
| --------------------------^^^^^^---
18+
| | | |
19+
| | | temporary value is freed at the end of this statement
20+
| | creates a temporary value which is freed while still in use
2121
| borrow later used here
2222
|
2323
= note: consider using a `let` binding to create a longer lived value

‎tests/ui/borrowck/format-args-temporary-scopes.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@
88
fn temp() {}
99

1010
fn main() {
11-
// In Rust 2024, block tail expressions are temporary scopes, so the result of `temp()` is
12-
// dropped after evaluating `&temp()`.
11+
// In Rust 2024, block tail expressions are temporary scopes, but temporary lifetime extension
12+
// rules apply: `&temp()` here is an extending borrow expression, so `temp()`'s lifetime is
13+
// extended past the block.
1314
println!("{:?}", { &temp() });
15+
16+
// Arguments to function calls aren't extending expressions, so `temp()` is dropped at the end
17+
// of the block in Rust 2024.
18+
println!("{:?}", { std::convert::identity(&temp()) });
1419
//[e2024]~^ ERROR: temporary value dropped while borrowed [E0716]
1520

16-
// In Rust 1.89, `format_args!` extended the lifetime of all extending expressions in its
17-
// arguments when provided with two or more arguments. This caused the result of `temp()` to
18-
// outlive the result of the block, making this compile.
21+
// In Rust 1.89, `format_args!` had different lifetime extension behavior dependent on how many
22+
// formatting arguments it had (#145880), so let's test that too.
1923
println!("{:?}{:?}", { &temp() }, ());
24+
25+
println!("{:?}{:?}", { std::convert::identity(&temp()) }, ());
2026
//[e2024]~^ ERROR: temporary value dropped while borrowed [E0716]
2127
}

‎tests/ui/drop/destructuring-assignments.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414

1515
fn main() {
1616
assert_drop_order(1..=3, |e| {
17-
&({ &raw const *&e.log(1) }, drop(e.log(2)));
17+
&({ &raw const *&e.log(2) }, drop(e.log(1)));
1818
drop(e.log(3));
1919
});
2020
assert_drop_order(1..=3, |e| {
21-
{ let _x; _x = &({ &raw const *&e.log(1) }, drop(e.log(2))); }
21+
{ let _x; _x = &({ &raw const *&e.log(2) }, drop(e.log(1))); }
2222
drop(e.log(3));
2323
});
2424
assert_drop_order(1..=3, |e| {

‎tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ fn should_lint_with_unsafe_block() {
2828
fn should_lint_with_big_block() {
2929
fn f<T>(_: T) {}
3030
f({
31-
&mut || 0
31+
std::convert::identity(&mut || 0)
3232
//~^ ERROR: relative drop order changing
3333
//~| WARN: this changes meaning in Rust 2024
3434
//~| NOTE: this temporary value will be dropped at the end of the block
@@ -40,12 +40,17 @@ fn should_lint_with_big_block() {
4040
fn another_temp_that_is_copy_in_arg() {
4141
fn f() {}
4242
fn g(_: &()) {}
43-
g({ &f() });
43+
g({ std::convert::identity(&f()) });
4444
//~^ ERROR: relative drop order changing
4545
//~| WARN: this changes meaning in Rust 2024
4646
//~| NOTE: this temporary value will be dropped at the end of the block
4747
//~| NOTE: borrow later used by call
4848
//~| NOTE: for more information, see
4949
}
5050

51+
fn do_not_lint_extending_borrow() {
52+
fn f<T>(_: T) {}
53+
f({ &mut || 0 });
54+
}
55+
5156
fn main() {}

‎tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,22 @@ LL | f(unsafe { String::new().as_str() }.len());
2626
= note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/temporary-tail-expr-scope.html>
2727

2828
error: relative drop order changing in Rust 2024
29-
--> $DIR/lint-tail-expr-drop-order-borrowck.rs:31:9
29+
--> $DIR/lint-tail-expr-drop-order-borrowck.rs:31:32
3030
|
31-
LL | &mut || 0
32-
| ^^^^^^^^^
33-
| |
34-
| this temporary value will be dropped at the end of the block
31+
LL | std::convert::identity(&mut || 0)
32+
| -----------------------^^^^^^^^^-
33+
| | |
34+
| | this temporary value will be dropped at the end of the block
3535
| borrow later used here
3636
|
3737
= warning: this changes meaning in Rust 2024
3838
= note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/temporary-tail-expr-scope.html>
3939

4040
error: relative drop order changing in Rust 2024
41-
--> $DIR/lint-tail-expr-drop-order-borrowck.rs:43:9
41+
--> $DIR/lint-tail-expr-drop-order-borrowck.rs:43:32
4242
|
43-
LL | g({ &f() });
44-
| - ^^^^ this temporary value will be dropped at the end of the block
43+
LL | g({ std::convert::identity(&f()) });
44+
| - ^^^^ this temporary value will be dropped at the end of the block
4545
| |
4646
| borrow later used by call
4747
|

‎tests/ui/drop/super-let-tail-expr-drop-order.rs

Lines changed: 29 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
//! Test for #145784: the argument to `pin!` should be treated as an extending expression if and
2-
//! only if the whole `pin!` invocation is an extending expression. Likewise, since `pin!` is
3-
//! implemented in terms of `super let`, test the same for `super let` initializers. Since the
4-
//! argument to `pin!` and the initializer of `super let` are not temporary drop scopes, this only
5-
//! affects lifetimes in two cases:
1+
//! Test for #145784. This tests three things:
62
//!
7-
//! - Block tail expressions in Rust 2024, which are both extending expressions and temporary drop
8-
//! scopes; treating them as extending expressions within a non-extending `pin!` resulted in borrow
9-
//! expression operands living past the end of the block.
3+
//! - In Rust 2024, temporary lifetime extension applies to block tail expressions, such that
4+
//! extending borrows and `super let`s in block tails are extended to outlive the result of the
5+
//! block.
106
//!
11-
//! - Nested `super let` statements, which can have their binding and temporary lifetimes extended
12-
//! when the block they're in is an extending expression.
7+
//! - Since `super let`'s initializer has the same temporary scope as the variable scope of its
8+
//! bindings, this means that lifetime extension can effectively see through `super let`.
139
//!
14-
//! For more information on extending expressions, see
15-
//! https://doc.rust-lang.org/reference/destructors.html#extending-based-on-expressions
10+
//! - In particular, the argument to `pin!` is an extending expression, and the argument of an
11+
//! extending `pin!` has an extended temporary scope. The lifetime of the argument, as well those
12+
//! of extending borrows and `super lets` within it, should match the result of the `pin!`,
13+
//! regardless of whether it itself is extended by a parent expression.
14+
//!
15+
//! For more information on temporary lifetime extension, see
16+
//! https://doc.rust-lang.org/nightly/reference/destructors.html#temporary-lifetime-extension
1617
//!
1718
//! For tests that `super let` initializers aren't temporary drop scopes, and tests for
1819
//! lifetime-extended `super let`, see tests/ui/borrowck/super-let-lifetime-and-drop.rs
@@ -30,34 +31,25 @@ use std::pin::pin;
3031
fn f<T>(_: LogDrop<'_>, x: T) -> T { x }
3132

3233
fn main() {
33-
// Test block arguments to `pin!` in non-extending expressions.
34+
// Test block arguments to non-extending `pin!`.
3435
// In Rust 2021, block tail expressions aren't temporary drop scopes, so their temporaries
3536
// should outlive the `pin!` invocation.
36-
// In Rust 2024, block tail expressions are temporary drop scopes, so their temporaries should
37-
// be dropped after evaluating the tail expression within the `pin!` invocation.
38-
// By nesting two `pin!` calls, this ensures non-extended `pin!` doesn't extend an inner `pin!`.
37+
// In Rust 2024, extending borrows within block tail expressions have extended lifetimes to
38+
// outlive result of the block, so the end result is the same in this case.
39+
// By nesting two `pin!` calls, this ensures extending borrows in the inner `pin!` outlive the
40+
// outer `pin!`.
3941
assert_drop_order(1..=3, |o| {
40-
#[cfg(e2021)]
4142
(
4243
pin!((
4344
pin!({ &o.log(3) as *const LogDrop<'_> }),
4445
drop(o.log(1)),
4546
)),
4647
drop(o.log(2)),
4748
);
48-
#[cfg(e2024)]
49-
(
50-
pin!((
51-
pin!({ &o.log(1) as *const LogDrop<'_> }),
52-
drop(o.log(2)),
53-
)),
54-
drop(o.log(3)),
55-
);
5649
});
5750

5851
// The same holds for `super let` initializers in non-extending expressions.
5952
assert_drop_order(1..=4, |o| {
60-
#[cfg(e2021)]
6153
(
6254
{
6355
super let _ = {
@@ -68,17 +60,6 @@ fn main() {
6860
},
6961
drop(o.log(3)),
7062
);
71-
#[cfg(e2024)]
72-
(
73-
{
74-
super let _ = {
75-
super let _ = { &o.log(1) as *const LogDrop<'_> };
76-
drop(o.log(2))
77-
};
78-
drop(o.log(3))
79-
},
80-
drop(o.log(4)),
81-
);
8263
});
8364

8465
// Within an extending expression, the argument to `pin!` is also an extending expression,
@@ -97,36 +78,18 @@ fn main() {
9778
// We have extending borrow expressions within an extending block
9879
// expression (within an extending borrow expression) within a
9980
// non-extending expresion within the initializer expression.
100-
#[cfg(e2021)]
101-
{
102-
// These two should be the same.
103-
assert_drop_order(1..=3, |e| {
104-
let _v = f(e.log(1), &{ &raw const *&e.log(2) });
105-
drop(e.log(3));
106-
});
107-
assert_drop_order(1..=3, |e| {
108-
let _v = f(e.log(1), {
109-
super let v = &{ &raw const *&e.log(2) };
110-
v
111-
});
112-
drop(e.log(3));
113-
});
114-
}
115-
#[cfg(e2024)]
116-
{
117-
// These two should be the same.
118-
assert_drop_order(1..=3, |e| {
119-
let _v = f(e.log(2), &{ &raw const *&e.log(1) });
120-
drop(e.log(3));
121-
});
122-
assert_drop_order(1..=3, |e| {
123-
let _v = f(e.log(2), {
124-
super let v = &{ &raw const *&e.log(1) };
125-
v
126-
});
127-
drop(e.log(3));
81+
// These two should be the same.
82+
assert_drop_order(1..=3, |e| {
83+
let _v = f(e.log(1), &{ &raw const *&e.log(2) });
84+
drop(e.log(3));
85+
});
86+
assert_drop_order(1..=3, |e| {
87+
let _v = f(e.log(1), {
88+
super let v = &{ &raw const *&e.log(2) };
89+
v
12890
});
129-
}
91+
drop(e.log(3));
92+
});
13093

13194
// We have extending borrow expressions within a non-extending
13295
// expression within the initializer expression.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /