Skip to content

Commit b79ad57

Browse files
authored
Rollup merge of rust-lang#102998 - nathanwhit:let-chains-drop-order, r=eholk
Drop temporaries created in a condition, even if it's a let chain Fixes rust-lang#100513. During the lowering from AST to HIR we wrap expressions acting as conditions in a `DropTemps` expression so that any temporaries created in the condition are dropped after the condition is executed. Effectively this means we transform ```rust if Some(1).is_some() { .. } ``` into (roughly) ```rust if { let _t = Some(1).is_some(); _t } { .. } ``` so that if we create any temporaries, they're lifted into the new scope surrounding the condition, so for example something along the lines of ```rust if { let temp = Some(1); let _t = temp.is_some(); _t }. ``` Before this PR, if the condition contained any let expressions we would not introduce that new scope, instead leaving the condition alone. This meant that in a let-chain like ```rust if get_drop("first").is_some() && let None = get_drop("last") { println!("second"); } else { .. } ``` the temporary created for `get_drop("first")` would be lifted into the _surrounding block_, which caused it to be dropped after the execution of the entire `if` expression. After this PR, we wrap everything but the `let` expression in terminating scopes. The upside to this solution is that it's minimally invasive, but the downside is that in the worst case, an expression with `let` exprs interspersed like ```rust if get_drop("first").is_some() && let Some(_a) = get_drop("fifth") && get_drop("second").is_some() && let Some(_b) = get_drop("fourth") { .. } ``` gets _multiple_ new scopes, roughly ```rust if { let _t = get_drop("first").is_some(); _t } && let Some(_a) = get_drop("fifth") && { let _t = get_drop("second").is_some(); _t } && let Some(_b) = get_drop("fourth") { .. } ``` so instead of all of the temporaries being dropped at the end of the entire condition, they will be dropped right after they're evaluated (before the subsequent `let` expr). So while I'd say the drop behavior around let-chains is _less_ surprising after this PR, it still might not exactly match what people might expect. For tests, I've just extended the drop order tests added in rust-lang#100526. I'm not sure if that's the best way to go about it, though, so suggestions are welcome.
2 parents 59e0af6 + 4e1c09d commit b79ad57

File tree

2 files changed

+109
-20
lines changed

2 files changed

+109
-20
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+45-20
Original file line numberDiff line numberDiff line change
@@ -387,32 +387,58 @@ impl<'hir> LoweringContext<'_, 'hir> {
387387
then: &Block,
388388
else_opt: Option<&Expr>,
389389
) -> hir::ExprKind<'hir> {
390-
let lowered_cond = self.lower_expr(cond);
391-
let new_cond = self.manage_let_cond(lowered_cond);
390+
let lowered_cond = self.lower_cond(cond);
392391
let then_expr = self.lower_block_expr(then);
393392
if let Some(rslt) = else_opt {
394-
hir::ExprKind::If(new_cond, self.arena.alloc(then_expr), Some(self.lower_expr(rslt)))
393+
hir::ExprKind::If(
394+
lowered_cond,
395+
self.arena.alloc(then_expr),
396+
Some(self.lower_expr(rslt)),
397+
)
395398
} else {
396-
hir::ExprKind::If(new_cond, self.arena.alloc(then_expr), None)
399+
hir::ExprKind::If(lowered_cond, self.arena.alloc(then_expr), None)
397400
}
398401
}
399402

400-
// If `cond` kind is `let`, returns `let`. Otherwise, wraps and returns `cond`
401-
// in a temporary block.
402-
fn manage_let_cond(&mut self, cond: &'hir hir::Expr<'hir>) -> &'hir hir::Expr<'hir> {
403-
fn has_let_expr<'hir>(expr: &'hir hir::Expr<'hir>) -> bool {
404-
match expr.kind {
405-
hir::ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
406-
hir::ExprKind::Let(..) => true,
403+
// Lowers a condition (i.e. `cond` in `if cond` or `while cond`), wrapping it in a terminating scope
404+
// so that temporaries created in the condition don't live beyond it.
405+
fn lower_cond(&mut self, cond: &Expr) -> &'hir hir::Expr<'hir> {
406+
fn has_let_expr(expr: &Expr) -> bool {
407+
match &expr.kind {
408+
ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
409+
ExprKind::Let(..) => true,
407410
_ => false,
408411
}
409412
}
410-
if has_let_expr(cond) {
411-
cond
412-
} else {
413-
let reason = DesugaringKind::CondTemporary;
414-
let span_block = self.mark_span_with_reason(reason, cond.span, None);
415-
self.expr_drop_temps(span_block, cond, AttrVec::new())
413+
414+
// We have to take special care for `let` exprs in the condition, e.g. in
415+
// `if let pat = val` or `if foo && let pat = val`, as we _do_ want `val` to live beyond the
416+
// condition in this case.
417+
//
418+
// In order to mantain the drop behavior for the non `let` parts of the condition,
419+
// we still wrap them in terminating scopes, e.g. `if foo && let pat = val` essentially
420+
// gets transformed into `if { let _t = foo; _t } && let pat = val`
421+
match &cond.kind {
422+
ExprKind::Binary(op @ Spanned { node: ast::BinOpKind::And, .. }, lhs, rhs)
423+
if has_let_expr(cond) =>
424+
{
425+
let op = self.lower_binop(*op);
426+
let lhs = self.lower_cond(lhs);
427+
let rhs = self.lower_cond(rhs);
428+
429+
self.arena.alloc(self.expr(
430+
cond.span,
431+
hir::ExprKind::Binary(op, lhs, rhs),
432+
AttrVec::new(),
433+
))
434+
}
435+
ExprKind::Let(..) => self.lower_expr(cond),
436+
_ => {
437+
let cond = self.lower_expr(cond);
438+
let reason = DesugaringKind::CondTemporary;
439+
let span_block = self.mark_span_with_reason(reason, cond.span, None);
440+
self.expr_drop_temps(span_block, cond, AttrVec::new())
441+
}
416442
}
417443
}
418444

@@ -439,14 +465,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
439465
body: &Block,
440466
opt_label: Option<Label>,
441467
) -> hir::ExprKind<'hir> {
442-
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_expr(cond));
443-
let new_cond = self.manage_let_cond(lowered_cond);
468+
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_cond(cond));
444469
let then = self.lower_block_expr(body);
445470
let expr_break = self.expr_break(span, AttrVec::new());
446471
let stmt_break = self.stmt_expr(span, expr_break);
447472
let else_blk = self.block_all(span, arena_vec![self; stmt_break], None);
448473
let else_expr = self.arena.alloc(self.expr_block(else_blk, AttrVec::new()));
449-
let if_kind = hir::ExprKind::If(new_cond, self.arena.alloc(then), Some(else_expr));
474+
let if_kind = hir::ExprKind::If(lowered_cond, self.arena.alloc(then), Some(else_expr));
450475
let if_expr = self.expr(span, if_kind, AttrVec::new());
451476
let block = self.block_expr(self.arena.alloc(if_expr));
452477
let span = self.lower_span(span.with_hi(cond.span.hi()));

src/test/ui/drop/drop_order.rs

+64
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// run-pass
2+
// compile-flags: -Z validate-mir
3+
#![feature(let_chains)]
24

35
use std::cell::RefCell;
46
use std::convert::TryInto;
@@ -116,6 +118,58 @@ impl DropOrderCollector {
116118
}
117119
}
118120

121+
fn let_chain(&self) {
122+
// take the "then" branch
123+
if self.option_loud_drop(2).is_some() // 2
124+
&& self.option_loud_drop(1).is_some() // 1
125+
&& let Some(_d) = self.option_loud_drop(4) { // 4
126+
self.print(3); // 3
127+
}
128+
129+
// take the "else" branch
130+
if self.option_loud_drop(6).is_some() // 2
131+
&& self.option_loud_drop(5).is_some() // 1
132+
&& let None = self.option_loud_drop(7) { // 3
133+
unreachable!();
134+
} else {
135+
self.print(8); // 4
136+
}
137+
138+
// let exprs interspersed
139+
if self.option_loud_drop(9).is_some() // 1
140+
&& let Some(_d) = self.option_loud_drop(13) // 5
141+
&& self.option_loud_drop(10).is_some() // 2
142+
&& let Some(_e) = self.option_loud_drop(12) { // 4
143+
self.print(11); // 3
144+
}
145+
146+
// let exprs first
147+
if let Some(_d) = self.option_loud_drop(18) // 5
148+
&& let Some(_e) = self.option_loud_drop(17) // 4
149+
&& self.option_loud_drop(14).is_some() // 1
150+
&& self.option_loud_drop(15).is_some() { // 2
151+
self.print(16); // 3
152+
}
153+
154+
// let exprs last
155+
if self.option_loud_drop(20).is_some() // 2
156+
&& self.option_loud_drop(19).is_some() // 1
157+
&& let Some(_d) = self.option_loud_drop(23) // 5
158+
&& let Some(_e) = self.option_loud_drop(22) { // 4
159+
self.print(21); // 3
160+
}
161+
}
162+
163+
fn while_(&self) {
164+
let mut v = self.option_loud_drop(4);
165+
while let Some(_d) = v
166+
&& self.option_loud_drop(1).is_some()
167+
&& self.option_loud_drop(2).is_some() {
168+
self.print(3);
169+
v = None;
170+
}
171+
}
172+
119173
fn assert_sorted(self) {
120174
assert!(
121175
self.0
@@ -142,4 +196,14 @@ fn main() {
142196
let collector = DropOrderCollector::default();
143197
collector.match_();
144198
collector.assert_sorted();
199+
200+
println!("-- let chain --");
201+
let collector = DropOrderCollector::default();
202+
collector.let_chain();
203+
collector.assert_sorted();
204+
205+
println!("-- while --");
206+
let collector = DropOrderCollector::default();
207+
collector.while_();
208+
collector.assert_sorted();
145209
}

0 commit comments

Comments
 (0)