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