Skip to content

Commit 35a0407

Browse files
committed
Auto merge of #101410 - dingxiangfei2009:fix-let-else-scoping, r=jackh726
Reorder nesting scopes and declare bindings without drop schedule Fix #99228 Fix #99975 Storages are previously not declared before entering the `else` block of a `let .. else` statement. However, when breaking out of the pattern matching into the `else` block, those storages are recorded as scheduled for drops. This is not expected. This MR fixes this issue by not scheduling the drops for those storages. cc `@est31`
2 parents 294f0ee + 4a5d2a5 commit 35a0407

File tree

6 files changed

+221
-65
lines changed

6 files changed

+221
-65
lines changed

Diff for: compiler/rustc_mir_build/src/build/block.rs

+166-22
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,170 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
109109
)
110110
);
111111
}
112+
StmtKind::Let {
113+
remainder_scope,
114+
init_scope,
115+
pattern,
116+
initializer: Some(initializer),
117+
lint_level,
118+
else_block: Some(else_block),
119+
} => {
120+
// When lowering the statement `let <pat> = <expr> else { <else> };`,
121+
// the `<else>` block is nested in the parent scope enclosing this statment.
122+
// That scope is usually either the enclosing block scope,
123+
// or the remainder scope of the last statement.
124+
// This is to make sure that temporaries instantiated in `<expr>` are dropped
125+
// as well.
126+
// In addition, even though bindings in `<pat>` only come into scope if
127+
// the pattern matching passes, in the MIR building the storages for them
128+
// are declared as live any way.
129+
// This is similar to `let x;` statements without an initializer expression,
130+
// where the value of `x` in this example may or may be assigned,
131+
// because the storage for their values may not be live after all due to
132+
// failure in pattern matching.
133+
// For this reason, we declare those storages as live but we do not schedule
134+
// any drop yet- they are scheduled later after the pattern matching.
135+
// The generated MIR will have `StorageDead` whenever the control flow breaks out
136+
// of the parent scope, regardless of the result of the pattern matching.
137+
// However, the drops are inserted in MIR only when the control flow breaks out of
138+
// the scope of the remainder scope associated with this `let .. else` statement.
139+
// Pictorial explanation of the scope structure:
140+
// ┌─────────────────────────────────┐
141+
// │ Scope of the enclosing block, │
142+
// │ or the last remainder scope │
143+
// │ ┌───────────────────────────┐ │
144+
// │ │ Scope for <else> block │ │
145+
// │ └───────────────────────────┘ │
146+
// │ ┌───────────────────────────┐ │
147+
// │ │ Remainder scope of │ │
148+
// │ │ this let-else statement │ │
149+
// │ │ ┌─────────────────────┐ │ │
150+
// │ │ │ <expr> scope │ │ │
151+
// │ │ └─────────────────────┘ │ │
152+
// │ │ extended temporaries in │ │
153+
// │ │ <expr> lives in this │ │
154+
// │ │ scope │ │
155+
// │ │ ┌─────────────────────┐ │ │
156+
// │ │ │ Scopes for the rest │ │ │
157+
// │ │ └─────────────────────┘ │ │
158+
// │ └───────────────────────────┘ │
159+
// └─────────────────────────────────┘
160+
// Generated control flow:
161+
// │ let Some(x) = y() else { return; }
162+
// │
163+
// ┌────────▼───────┐
164+
// │ evaluate y() │
165+
// └────────┬───────┘
166+
// │ ┌────────────────┐
167+
// ┌────────▼───────┐ │Drop temporaries│
168+
// │Test the pattern├──────►in y() │
169+
// └────────┬───────┘ │because breaking│
170+
// │ │out of <expr> │
171+
// ┌────────▼───────┐ │scope │
172+
// │Move value into │ └───────┬────────┘
173+
// │binding x │ │
174+
// └────────┬───────┘ ┌───────▼────────┐
175+
// │ │Drop extended │
176+
// ┌────────▼───────┐ │temporaries in │
177+
// │Drop temporaries│ │<expr> because │
178+
// │in y() │ │breaking out of │
179+
// │because breaking│ │remainder scope │
180+
// │out of <expr> │ └───────┬────────┘
181+
// │scope │ │
182+
// └────────┬───────┘ ┌───────▼────────┐
183+
// │ │Enter <else> ├────────►
184+
// ┌────────▼───────┐ │block │ return;
185+
// │Continue... │ └────────────────┘
186+
// └────────────────┘
187+
188+
let ignores_expr_result = matches!(pattern.kind, PatKind::Wild);
189+
this.block_context.push(BlockFrame::Statement { ignores_expr_result });
190+
191+
// Lower the `else` block first because its parent scope is actually
192+
// enclosing the rest of the `let .. else ..` parts.
193+
let else_block_span = this.thir[*else_block].span;
194+
// This place is not really used because this destination place
195+
// should never be used to take values at the end of the failure
196+
// block.
197+
let dummy_place = this.temp(this.tcx.types.never, else_block_span);
198+
let failure_entry = this.cfg.start_new_block();
199+
let failure_block;
200+
unpack!(
201+
failure_block = this.ast_block(
202+
dummy_place,
203+
failure_entry,
204+
*else_block,
205+
this.source_info(else_block_span),
206+
)
207+
);
208+
this.cfg.terminate(
209+
failure_block,
210+
this.source_info(else_block_span),
211+
TerminatorKind::Unreachable,
212+
);
213+
214+
// Declare the bindings, which may create a source scope.
215+
let remainder_span = remainder_scope.span(this.tcx, this.region_scope_tree);
216+
this.push_scope((*remainder_scope, source_info));
217+
let_scope_stack.push(remainder_scope);
218+
219+
let visibility_scope =
220+
Some(this.new_source_scope(remainder_span, LintLevel::Inherited, None));
221+
222+
let init = &this.thir[*initializer];
223+
let initializer_span = init.span;
224+
this.declare_bindings(
225+
visibility_scope,
226+
remainder_span,
227+
pattern,
228+
ArmHasGuard(false),
229+
Some((None, initializer_span)),
230+
);
231+
this.visit_primary_bindings(
232+
pattern,
233+
UserTypeProjections::none(),
234+
&mut |this, _, _, _, node, span, _, _| {
235+
this.storage_live_binding(block, node, span, OutsideGuard, false);
236+
},
237+
);
238+
let failure = unpack!(
239+
block = this.in_opt_scope(
240+
opt_destruction_scope.map(|de| (de, source_info)),
241+
|this| {
242+
let scope = (*init_scope, source_info);
243+
this.in_scope(scope, *lint_level, |this| {
244+
this.ast_let_else(
245+
block,
246+
init,
247+
initializer_span,
248+
*else_block,
249+
&last_remainder_scope,
250+
pattern,
251+
)
252+
})
253+
}
254+
)
255+
);
256+
this.cfg.goto(failure, source_info, failure_entry);
257+
258+
if let Some(source_scope) = visibility_scope {
259+
this.source_scope = source_scope;
260+
}
261+
last_remainder_scope = *remainder_scope;
262+
}
263+
StmtKind::Let { init_scope, initializer: None, else_block: Some(_), .. } => {
264+
span_bug!(
265+
init_scope.span(this.tcx, this.region_scope_tree),
266+
"initializer is missing, but else block is present in this let binding",
267+
)
268+
}
112269
StmtKind::Let {
113270
remainder_scope,
114271
init_scope,
115272
ref pattern,
116273
initializer,
117274
lint_level,
118-
else_block,
275+
else_block: None,
119276
} => {
120277
let ignores_expr_result = matches!(pattern.kind, PatKind::Wild);
121278
this.block_context.push(BlockFrame::Statement { ignores_expr_result });
@@ -141,27 +298,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
141298
|this| {
142299
let scope = (*init_scope, source_info);
143300
this.in_scope(scope, *lint_level, |this| {
144-
if let Some(else_block) = else_block {
145-
this.ast_let_else(
146-
block,
147-
init,
148-
initializer_span,
149-
*else_block,
150-
visibility_scope,
151-
last_remainder_scope,
152-
remainder_span,
153-
pattern,
154-
)
155-
} else {
156-
this.declare_bindings(
157-
visibility_scope,
158-
remainder_span,
159-
pattern,
160-
ArmHasGuard(false),
161-
Some((None, initializer_span)),
162-
);
163-
this.expr_into_pattern(block, pattern, init) // irrefutable pattern
164-
}
301+
this.declare_bindings(
302+
visibility_scope,
303+
remainder_span,
304+
pattern,
305+
ArmHasGuard(false),
306+
Some((None, initializer_span)),
307+
);
308+
this.expr_into_pattern(block, &pattern, init) // irrefutable pattern
165309
})
166310
},
167311
)

Diff for: compiler/rustc_mir_build/src/build/matches/mod.rs

+6-34
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
699699
self.cfg.push(block, Statement { source_info, kind: StatementKind::StorageLive(local_id) });
700700
// Although there is almost always scope for given variable in corner cases
701701
// like #92893 we might get variable with no scope.
702-
if let Some(region_scope) = self.region_scope_tree.var_scope(var.0.local_id) && schedule_drop{
702+
if let Some(region_scope) = self.region_scope_tree.var_scope(var.0.local_id) && schedule_drop {
703703
self.schedule_drop(span, region_scope, local_id, DropKind::Storage);
704704
}
705705
Place::from(local_id)
@@ -2274,23 +2274,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
22742274
init: &Expr<'tcx>,
22752275
initializer_span: Span,
22762276
else_block: BlockId,
2277-
visibility_scope: Option<SourceScope>,
2278-
remainder_scope: region::Scope,
2279-
remainder_span: Span,
2277+
let_else_scope: &region::Scope,
22802278
pattern: &Pat<'tcx>,
2281-
) -> BlockAnd<()> {
2279+
) -> BlockAnd<BasicBlock> {
22822280
let else_block_span = self.thir[else_block].span;
2283-
let (matching, failure) = self.in_if_then_scope(remainder_scope, |this| {
2281+
let (matching, failure) = self.in_if_then_scope(*let_else_scope, |this| {
22842282
let scrutinee = unpack!(block = this.lower_scrutinee(block, init, initializer_span));
22852283
let pat = Pat { ty: init.ty, span: else_block_span, kind: PatKind::Wild };
22862284
let mut wildcard = Candidate::new(scrutinee.clone(), &pat, false);
2287-
this.declare_bindings(
2288-
visibility_scope,
2289-
remainder_span,
2290-
pattern,
2291-
ArmHasGuard(false),
2292-
Some((None, initializer_span)),
2293-
);
22942285
let mut candidate = Candidate::new(scrutinee.clone(), pattern, false);
22952286
let fake_borrow_temps = this.lower_match_tree(
22962287
block,
@@ -2321,28 +2312,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
23212312
None,
23222313
None,
23232314
);
2324-
this.break_for_else(failure, remainder_scope, this.source_info(initializer_span));
2315+
this.break_for_else(failure, *let_else_scope, this.source_info(initializer_span));
23252316
matching.unit()
23262317
});
2327-
2328-
// This place is not really used because this destination place
2329-
// should never be used to take values at the end of the failure
2330-
// block.
2331-
let dummy_place = self.temp(self.tcx.types.never, else_block_span);
2332-
let failure_block;
2333-
unpack!(
2334-
failure_block = self.ast_block(
2335-
dummy_place,
2336-
failure,
2337-
else_block,
2338-
self.source_info(else_block_span),
2339-
)
2340-
);
2341-
self.cfg.terminate(
2342-
failure_block,
2343-
self.source_info(else_block_span),
2344-
TerminatorKind::Unreachable,
2345-
);
2346-
matching.unit()
2318+
matching.and(failure)
23472319
}
23482320
}

Diff for: compiler/rustc_typeck/src/check/region.rs

+27-8
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,29 @@ fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx h
126126

127127
for (i, statement) in blk.stmts.iter().enumerate() {
128128
match statement.kind {
129+
hir::StmtKind::Local(hir::Local { els: Some(els), .. }) => {
130+
// Let-else has a special lexical structure for variables.
131+
// First we take a checkpoint of the current scope context here.
132+
let mut prev_cx = visitor.cx;
133+
134+
visitor.enter_scope(Scope {
135+
id: blk.hir_id.local_id,
136+
data: ScopeData::Remainder(FirstStatementIndex::new(i)),
137+
});
138+
visitor.cx.var_parent = visitor.cx.parent;
139+
visitor.visit_stmt(statement);
140+
// We need to back out temporarily to the last enclosing scope
141+
// for the `else` block, so that even the temporaries receiving
142+
// extended lifetime will be dropped inside this block.
143+
// We are visiting the `else` block in this order so that
144+
// the sequence of visits agree with the order in the default
145+
// `hir::intravisit` visitor.
146+
mem::swap(&mut prev_cx, &mut visitor.cx);
147+
visitor.terminating_scopes.insert(els.hir_id.local_id);
148+
visitor.visit_block(els);
149+
// From now on, we continue normally.
150+
visitor.cx = prev_cx;
151+
}
129152
hir::StmtKind::Local(..) | hir::StmtKind::Item(..) => {
130153
// Each declaration introduces a subscope for bindings
131154
// introduced by the declaration; this subscope covers a
@@ -138,10 +161,10 @@ fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx h
138161
data: ScopeData::Remainder(FirstStatementIndex::new(i)),
139162
});
140163
visitor.cx.var_parent = visitor.cx.parent;
164+
visitor.visit_stmt(statement)
141165
}
142-
hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => {}
166+
hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => visitor.visit_stmt(statement),
143167
}
144-
visitor.visit_stmt(statement)
145168
}
146169
walk_list!(visitor, visit_expr, &blk.expr);
147170
}
@@ -460,7 +483,6 @@ fn resolve_local<'tcx>(
460483
visitor: &mut RegionResolutionVisitor<'tcx>,
461484
pat: Option<&'tcx hir::Pat<'tcx>>,
462485
init: Option<&'tcx hir::Expr<'tcx>>,
463-
els: Option<&'tcx hir::Block<'tcx>>,
464486
) {
465487
debug!("resolve_local(pat={:?}, init={:?})", pat, init);
466488

@@ -547,9 +569,6 @@ fn resolve_local<'tcx>(
547569
if let Some(pat) = pat {
548570
visitor.visit_pat(pat);
549571
}
550-
if let Some(els) = els {
551-
visitor.visit_block(els);
552-
}
553572

554573
/// Returns `true` if `pat` match the `P&` non-terminal.
555574
///
@@ -766,7 +785,7 @@ impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> {
766785
// (i.e., `'static`), which means that after `g` returns, it drops,
767786
// and all the associated destruction scope rules apply.
768787
self.cx.var_parent = None;
769-
resolve_local(self, None, Some(&body.value), None);
788+
resolve_local(self, None, Some(&body.value));
770789
}
771790

772791
if body.generator_kind.is_some() {
@@ -793,7 +812,7 @@ impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> {
793812
resolve_expr(self, ex);
794813
}
795814
fn visit_local(&mut self, l: &'tcx Local<'tcx>) {
796-
resolve_local(self, Some(&l.pat), l.init, l.els)
815+
resolve_local(self, Some(&l.pat), l.init)
797816
}
798817
}
799818

Diff for: src/test/ui/async-await/async-await-let-else.no-drop-tracking.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ LL | bar2(Rc::new(())).await
3535
| |
3636
| has type `Rc<()>` which is not `Send`
3737
LL | };
38-
| - `Rc::new(())` is later dropped here
38+
| - `Rc::new(())` is later dropped here
3939
note: required by a bound in `is_send`
4040
--> $DIR/async-await-let-else.rs:19:15
4141
|

0 commit comments

Comments
 (0)