Skip to content

Commit ad8b242

Browse files
committed
Let chains should still drop temporaries
by the end of the condition's execution
1 parent 0938e16 commit ad8b242

File tree

2 files changed

+97
-11
lines changed

2 files changed

+97
-11
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+34-11
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
388388
else_opt: Option<&Expr>,
389389
) -> hir::ExprKind<'hir> {
390390
let lowered_cond = self.lower_expr(cond);
391-
let new_cond = self.manage_let_cond(lowered_cond);
391+
let new_cond = self.wrap_cond_in_drop_scope(lowered_cond);
392392
let then_expr = self.lower_block_expr(then);
393393
if let Some(rslt) = else_opt {
394394
hir::ExprKind::If(new_cond, self.arena.alloc(then_expr), Some(self.lower_expr(rslt)))
@@ -397,22 +397,45 @@ impl<'hir> LoweringContext<'_, 'hir> {
397397
}
398398
}
399399

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> {
400+
// Wraps a condition (i.e. `cond` in `if cond` or `while cond`) in a terminating scope
401+
// so that temporaries created in the condition don't live beyond it.
402+
fn wrap_cond_in_drop_scope(&mut self, cond: &'hir hir::Expr<'hir>) -> &'hir hir::Expr<'hir> {
403403
fn has_let_expr<'hir>(expr: &'hir hir::Expr<'hir>) -> bool {
404404
match expr.kind {
405405
hir::ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
406406
hir::ExprKind::Let(..) => true,
407407
_ => false,
408408
}
409409
}
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())
410+
411+
// We have to take special care for `let` exprs in the condition, e.g. in
412+
// `if let pat = val` or `if foo && let pat = val`, as we _do_ want `val` to live beyond the
413+
// condition in this case.
414+
//
415+
// In order to mantain the drop behavior for the non `let` parts of the condition,
416+
// we still wrap them in terminating scopes, e.g. `if foo && let pat = val` essentially
417+
// gets transformed into `if { let _t = foo; _t } && let pat = val`
418+
match cond.kind {
419+
hir::ExprKind::Binary(
420+
op @ Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. },
421+
lhs,
422+
rhs,
423+
) if has_let_expr(cond) => {
424+
let lhs = self.wrap_cond_in_drop_scope(lhs);
425+
let rhs = self.wrap_cond_in_drop_scope(rhs);
426+
427+
self.arena.alloc(self.expr(
428+
cond.span,
429+
hir::ExprKind::Binary(op, lhs, rhs),
430+
AttrVec::new(),
431+
))
432+
}
433+
hir::ExprKind::Let(_) => cond,
434+
_ => {
435+
let reason = DesugaringKind::CondTemporary;
436+
let span_block = self.mark_span_with_reason(reason, cond.span, None);
437+
self.expr_drop_temps(span_block, cond, AttrVec::new())
438+
}
416439
}
417440
}
418441

@@ -440,7 +463,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
440463
opt_label: Option<Label>,
441464
) -> hir::ExprKind<'hir> {
442465
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_expr(cond));
443-
let new_cond = self.manage_let_cond(lowered_cond);
466+
let new_cond = self.wrap_cond_in_drop_scope(lowered_cond);
444467
let then = self.lower_block_expr(body);
445468
let expr_break = self.expr_break(span, AttrVec::new());
446469
let stmt_break = self.stmt_expr(span, expr_break);

src/test/ui/drop/drop_order.rs

+63
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// run-pass
2+
#![feature(let_chains)]
23

34
use std::cell::RefCell;
45
use std::convert::TryInto;
@@ -116,6 +117,58 @@ impl DropOrderCollector {
116117
}
117118
}
118119

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

0 commit comments

Comments
 (0)