rustc_mir_build/builder/block.rs
1use rustc_middle::middle::region::Scope;
2use rustc_middle::mir::*;
3use rustc_middle::thir::*;
4use rustc_middle::{span_bug, ty};
5use rustc_span::Span;
6use tracing::debug;
7
8use crate::builder::ForGuard::OutsideGuard;
9use crate::builder::matches::{DeclareLetBindings, EmitStorageLive, ScheduleDrops};
10use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
11
12impl<'a, 'tcx> Builder<'a, 'tcx> {
13 pub(crate) fn ast_block(
14 &mut self,
15 destination: Place<'tcx>,
16 block: BasicBlock,
17 ast_block: BlockId,
18 source_info: SourceInfo,
19 ) -> BlockAnd<()> {
20 let Block { region_scope, span, ref stmts, expr, targeted_by_break, safety_mode: _ } =
21 self.thir[ast_block];
22 self.in_scope((region_scope, source_info), LintLevel::Inherited, move |this| {
23 if targeted_by_break {
24 this.in_breakable_scope(None, destination, span, |this| {
25 Some(this.ast_block_stmts(destination, block, span, stmts, expr, region_scope))
26 })
27 } else {
28 this.ast_block_stmts(destination, block, span, stmts, expr, region_scope)
29 }
30 })
31 }
32
33 fn ast_block_stmts(
34 &mut self,
35 destination: Place<'tcx>,
36 mut block: BasicBlock,
37 span: Span,
38 stmts: &[StmtId],
39 expr: Option<ExprId>,
40 region_scope: Scope,
41 ) -> BlockAnd<()> {
42 let this = self;
43
44 // This convoluted structure is to avoid using recursion as we walk down a list
45 // of statements. Basically, the structure we get back is something like:
46 //
47 // let x = <init> in {
48 // expr1;
49 // let y = <init> in {
50 // expr2;
51 // expr3;
52 // ...
53 // }
54 // }
55 //
56 // The let bindings are valid till the end of block so all we have to do is to pop all
57 // the let-scopes at the end.
58 //
59 // First we build all the statements in the block.
60 let mut let_scope_stack = Vec::with_capacity(8);
61 let outer_source_scope = this.source_scope;
62 // This scope information is kept for breaking out of the parent remainder scope in case
63 // one let-else pattern matching fails.
64 // By doing so, we can be sure that even temporaries that receive extended lifetime
65 // assignments are dropped, too.
66 let mut last_remainder_scope = region_scope;
67
68 let source_info = this.source_info(span);
69 for stmt in stmts {
70 let Stmt { ref kind } = this.thir[*stmt];
71 match kind {
72 StmtKind::Expr { scope, expr } => {
73 this.block_context.push(BlockFrame::Statement { ignores_expr_result: true });
74 let si = (*scope, source_info);
75 block = this
76 .in_scope(si, LintLevel::Inherited, |this| {
77 this.stmt_expr(block, *expr, Some(*scope))
78 })
79 .into_block();
80 }
81 StmtKind::Let {
82 remainder_scope,
83 init_scope,
84 pattern,
85 initializer: Some(initializer),
86 lint_level,
87 else_block: Some(else_block),
88 span: _,
89 } => {
90 // When lowering the statement `let <pat> = <expr> else { <else> };`,
91 // the `<else>` block is nested in the parent scope enclosing this statement.
92 // That scope is usually either the enclosing block scope,
93 // or the remainder scope of the last statement.
94 // This is to make sure that temporaries instantiated in `<expr>` are dropped
95 // as well.
96 // In addition, even though bindings in `<pat>` only come into scope if
97 // the pattern matching passes, in the MIR building the storages for them
98 // are declared as live any way.
99 // This is similar to `let x;` statements without an initializer expression,
100 // where the value of `x` in this example may or may be assigned,
101 // because the storage for their values may not be live after all due to
102 // failure in pattern matching.
103 // For this reason, we declare those storages as live but we do not schedule
104 // any drop yet- they are scheduled later after the pattern matching.
105 // The generated MIR will have `StorageDead` whenever the control flow breaks out
106 // of the parent scope, regardless of the result of the pattern matching.
107 // However, the drops are inserted in MIR only when the control flow breaks out of
108 // the scope of the remainder scope associated with this `let .. else` statement.
109 // Pictorial explanation of the scope structure:
110 // ┌─────────────────────────────────┐
111 // │ Scope of the enclosing block, │
112 // │ or the last remainder scope │
113 // │ ┌───────────────────────────┐ │
114 // │ │ Scope for <else> block │ │
115 // │ └───────────────────────────┘ │
116 // │ ┌───────────────────────────┐ │
117 // │ │ Remainder scope of │ │
118 // │ │ this let-else statement │ │
119 // │ │ ┌─────────────────────┐ │ │
120 // │ │ │ <expr> scope │ │ │
121 // │ │ └─────────────────────┘ │ │
122 // │ │ extended temporaries in │ │
123 // │ │ <expr> lives in this │ │
124 // │ │ scope │ │
125 // │ │ ┌─────────────────────┐ │ │
126 // │ │ │ Scopes for the rest │ │ │
127 // │ │ └─────────────────────┘ │ │
128 // │ └───────────────────────────┘ │
129 // └─────────────────────────────────┘
130 // Generated control flow:
131 // │ let Some(x) = y() else { return; }
132 // │
133 // ┌────────▼───────┐
134 // │ evaluate y() │
135 // └────────┬───────┘
136 // │ ┌────────────────┐
137 // ┌────────▼───────┐ │Drop temporaries│
138 // │Test the pattern├──────►in y() │
139 // └────────┬───────┘ │because breaking│
140 // │ │out of <expr> │
141 // ┌────────▼───────┐ │scope │
142 // │Move value into │ └───────┬────────┘
143 // │binding x │ │
144 // └────────┬───────┘ ┌───────▼────────┐
145 // │ │Drop extended │
146 // ┌────────▼───────┐ │temporaries in │
147 // │Drop temporaries│ │<expr> because │
148 // │in y() │ │breaking out of │
149 // │because breaking│ │remainder scope │
150 // │out of <expr> │ └───────┬────────┘
151 // │scope │ │
152 // └────────┬───────┘ ┌───────▼────────┐
153 // │ │Enter <else> ├────────►
154 // ┌────────▼───────┐ │block │ return;
155 // │Continue... │ └────────────────┘
156 // └────────────────┘
157
158 let ignores_expr_result = matches!(pattern.kind, PatKind::Wild);
159 this.block_context.push(BlockFrame::Statement { ignores_expr_result });
160
161 // Lower the `else` block first because its parent scope is actually
162 // enclosing the rest of the `let .. else ..` parts.
163 let else_block_span = this.thir[*else_block].span;
164 // This place is not really used because this destination place
165 // should never be used to take values at the end of the failure
166 // block.
167 let dummy_place = this.temp(this.tcx.types.never, else_block_span);
168 let failure_entry = this.cfg.start_new_block();
169 let failure_block;
170 failure_block = this
171 .ast_block(
172 dummy_place,
173 failure_entry,
174 *else_block,
175 this.source_info(else_block_span),
176 )
177 .into_block();
178 this.cfg.terminate(
179 failure_block,
180 this.source_info(else_block_span),
181 TerminatorKind::Unreachable,
182 );
183
184 // Declare the bindings, which may create a source scope.
185 let remainder_span = remainder_scope.span(this.tcx, this.region_scope_tree);
186 this.push_scope((*remainder_scope, source_info));
187 let_scope_stack.push(remainder_scope);
188
189 let visibility_scope =
190 Some(this.new_source_scope(remainder_span, LintLevel::Inherited));
191
192 let initializer_span = this.thir[*initializer].span;
193 let scope = (*init_scope, source_info);
194 let failure_and_block = this.in_scope(scope, *lint_level, |this| {
195 this.declare_bindings(
196 visibility_scope,
197 remainder_span,
198 pattern,
199 None,
200 Some((Some(&destination), initializer_span)),
201 );
202 this.visit_primary_bindings(
203 pattern,
204 UserTypeProjections::none(),
205 &mut |this, _, _, node, span, _, _| {
206 this.storage_live_binding(
207 block,
208 node,
209 span,
210 OutsideGuard,
211 ScheduleDrops::Yes,
212 );
213 },
214 );
215 let else_block_span = this.thir[*else_block].span;
216 let (matching, failure) =
217 this.in_if_then_scope(last_remainder_scope, else_block_span, |this| {
218 this.lower_let_expr(
219 block,
220 *initializer,
221 pattern,
222 None,
223 initializer_span,
224 DeclareLetBindings::No,
225 EmitStorageLive::No,
226 )
227 });
228 matching.and(failure)
229 });
230 let failure = unpack!(block = failure_and_block);
231 this.cfg.goto(failure, source_info, failure_entry);
232
233 if let Some(source_scope) = visibility_scope {
234 this.source_scope = source_scope;
235 }
236 last_remainder_scope = *remainder_scope;
237 }
238 StmtKind::Let { init_scope, initializer: None, else_block: Some(_), .. } => {
239 span_bug!(
240 init_scope.span(this.tcx, this.region_scope_tree),
241 "initializer is missing, but else block is present in this let binding",
242 )
243 }
244 StmtKind::Let {
245 remainder_scope,
246 init_scope,
247 ref pattern,
248 initializer,
249 lint_level,
250 else_block: None,
251 span: _,
252 } => {
253 let ignores_expr_result = matches!(pattern.kind, PatKind::Wild);
254 this.block_context.push(BlockFrame::Statement { ignores_expr_result });
255
256 // Enter the remainder scope, i.e., the bindings' destruction scope.
257 this.push_scope((*remainder_scope, source_info));
258 let_scope_stack.push(remainder_scope);
259
260 // Declare the bindings, which may create a source scope.
261 let remainder_span = remainder_scope.span(this.tcx, this.region_scope_tree);
262
263 let visibility_scope =
264 Some(this.new_source_scope(remainder_span, LintLevel::Inherited));
265
266 // Evaluate the initializer, if present.
267 if let Some(init) = *initializer {
268 let initializer_span = this.thir[init].span;
269 let scope = (*init_scope, source_info);
270
271 block = this
272 .in_scope(scope, *lint_level, |this| {
273 this.declare_bindings(
274 visibility_scope,
275 remainder_span,
276 pattern,
277 None,
278 Some((None, initializer_span)),
279 );
280 this.expr_into_pattern(block, &pattern, init)
281 // irrefutable pattern
282 })
283 .into_block();
284 } else {
285 let scope = (*init_scope, source_info);
286 let _: BlockAnd<()> = this.in_scope(scope, *lint_level, |this| {
287 this.declare_bindings(
288 visibility_scope,
289 remainder_span,
290 pattern,
291 None,
292 None,
293 );
294 block.unit()
295 });
296
297 debug!("ast_block_stmts: pattern={:?}", pattern);
298 this.visit_primary_bindings(
299 pattern,
300 UserTypeProjections::none(),
301 &mut |this, _, _, node, span, _, _| {
302 this.storage_live_binding(
303 block,
304 node,
305 span,
306 OutsideGuard,
307 ScheduleDrops::Yes,
308 );
309 this.schedule_drop_for_binding(node, span, OutsideGuard);
310 },
311 )
312 }
313
314 // Enter the visibility scope, after evaluating the initializer.
315 if let Some(source_scope) = visibility_scope {
316 this.source_scope = source_scope;
317 }
318 last_remainder_scope = *remainder_scope;
319 }
320 }
321
322 let popped = this.block_context.pop();
323 assert!(popped.is_some_and(|bf| bf.is_statement()));
324 }
325
326 // Then, the block may have an optional trailing expression which is a “return” value
327 // of the block, which is stored into `destination`.
328 let tcx = this.tcx;
329 let destination_ty = destination.ty(&this.local_decls, tcx).ty;
330 if let Some(expr_id) = expr {
331 let expr = &this.thir[expr_id];
332 let tail_result_is_ignored =
333 destination_ty.is_unit() || this.block_context.currently_ignores_tail_results();
334 this.block_context
335 .push(BlockFrame::TailExpr { tail_result_is_ignored, span: expr.span });
336
337 block = this.expr_into_dest(destination, block, expr_id).into_block();
338 let popped = this.block_context.pop();
339
340 assert!(popped.is_some_and(|bf| bf.is_tail_expr()));
341 } else {
342 // If a block has no trailing expression, then it is given an implicit return type.
343 // This return type is usually `()`, unless the block is diverging, in which case the
344 // return type is `!`. For the unit type, we need to actually return the unit, but in
345 // the case of `!`, no return value is required, as the block will never return.
346 // Opaque types of empty bodies also need this unit assignment, in order to infer that their
347 // type is actually unit. Otherwise there will be no defining use found in the MIR.
348 if destination_ty.is_unit()
349 || matches!(destination_ty.kind(), ty::Alias(ty::Opaque, ..))
350 {
351 // We only want to assign an implicit `()` as the return value of the block if the
352 // block does not diverge. (Otherwise, we may try to assign a unit to a `!`-type.)
353 this.cfg.push_assign_unit(block, source_info, destination, this.tcx);
354 }
355 }
356 // Finally, we pop all the let scopes before exiting out from the scope of block
357 // itself.
358 for scope in let_scope_stack.into_iter().rev() {
359 block = this.pop_scope((*scope, source_info), block).into_block();
360 }
361 // Restore the original source scope.
362 this.source_scope = outer_source_scope;
363 block.unit()
364 }
365}