]> git.proxmox.com Git - rustc.git/blobdiff - compiler/rustc_mir_build/src/build/block.rs
New upstream version 1.65.0+dfsg1
[rustc.git] / compiler / rustc_mir_build / src / build / block.rs
index 6875600129a8ff211d41f4015954c647026bf15a..784583d94ad74a54c81a59fbc697536ee5892286 100644 (file)
@@ -1,6 +1,7 @@
 use crate::build::matches::ArmHasGuard;
 use crate::build::ForGuard::OutsideGuard;
 use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
+use rustc_middle::middle::region::Scope;
 use rustc_middle::thir::*;
 use rustc_middle::{mir::*, ty};
 use rustc_span::Span;
@@ -10,7 +11,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         &mut self,
         destination: Place<'tcx>,
         block: BasicBlock,
-        ast_block: &Block,
+        ast_block: BlockId,
         source_info: SourceInfo,
     ) -> BlockAnd<()> {
         let Block {
@@ -21,7 +22,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             expr,
             targeted_by_break,
             safety_mode,
-        } = *ast_block;
+        } = self.thir[ast_block];
         let expr = expr.map(|expr| &self.thir[expr]);
         self.in_opt_scope(opt_destruction_scope.map(|de| (de, source_info)), move |this| {
             this.in_scope((region_scope, source_info), LintLevel::Inherited, move |this| {
@@ -34,10 +35,19 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                             &stmts,
                             expr,
                             safety_mode,
+                            region_scope,
                         ))
                     })
                 } else {
-                    this.ast_block_stmts(destination, block, span, &stmts, expr, safety_mode)
+                    this.ast_block_stmts(
+                        destination,
+                        block,
+                        span,
+                        &stmts,
+                        expr,
+                        safety_mode,
+                        region_scope,
+                    )
                 }
             })
         })
@@ -51,6 +61,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         stmts: &[StmtId],
         expr: Option<&Expr<'tcx>>,
         safety_mode: BlockSafety,
+        region_scope: Scope,
     ) -> BlockAnd<()> {
         let this = self;
 
@@ -73,6 +84,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         let mut let_scope_stack = Vec::with_capacity(8);
         let outer_source_scope = this.source_scope;
         let outer_in_scope_unsafe = this.in_scope_unsafe;
+        // This scope information is kept for breaking out of the parent remainder scope in case
+        // one let-else pattern matching fails.
+        // By doing so, we can be sure that even temporaries that receive extended lifetime
+        // assignments are dropped, too.
+        let mut last_remainder_scope = region_scope;
         this.update_source_scope_for_safety_mode(span, safety_mode);
 
         let source_info = this.source_info(span);
@@ -93,15 +109,172 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                         )
                     );
                 }
+                StmtKind::Let {
+                    remainder_scope,
+                    init_scope,
+                    pattern,
+                    initializer: Some(initializer),
+                    lint_level,
+                    else_block: Some(else_block),
+                } => {
+                    // When lowering the statement `let <pat> = <expr> else { <else> };`,
+                    // the `<else>` block is nested in the parent scope enclosing this statment.
+                    // That scope is usually either the enclosing block scope,
+                    // or the remainder scope of the last statement.
+                    // This is to make sure that temporaries instantiated in `<expr>` are dropped
+                    // as well.
+                    // In addition, even though bindings in `<pat>` only come into scope if
+                    // the pattern matching passes, in the MIR building the storages for them
+                    // are declared as live any way.
+                    // This is similar to `let x;` statements without an initializer expression,
+                    // where the value of `x` in this example may or may be assigned,
+                    // because the storage for their values may not be live after all due to
+                    // failure in pattern matching.
+                    // For this reason, we declare those storages as live but we do not schedule
+                    // any drop yet- they are scheduled later after the pattern matching.
+                    // The generated MIR will have `StorageDead` whenever the control flow breaks out
+                    // of the parent scope, regardless of the result of the pattern matching.
+                    // However, the drops are inserted in MIR only when the control flow breaks out of
+                    // the scope of the remainder scope associated with this `let .. else` statement.
+                    // Pictorial explanation of the scope structure:
+                    // ┌─────────────────────────────────┐
+                    // │  Scope of the enclosing block,  │
+                    // │  or the last remainder scope    │
+                    // │  ┌───────────────────────────┐  │
+                    // │  │  Scope for <else> block   │  │
+                    // │  └───────────────────────────┘  │
+                    // │  ┌───────────────────────────┐  │
+                    // │  │  Remainder scope of       │  │
+                    // │  │  this let-else statement  │  │
+                    // │  │  ┌─────────────────────┐  │  │
+                    // │  │  │ <expr> scope        │  │  │
+                    // │  │  └─────────────────────┘  │  │
+                    // │  │  extended temporaries in  │  │
+                    // │  │  <expr> lives in this     │  │
+                    // │  │  scope                    │  │
+                    // │  │  ┌─────────────────────┐  │  │
+                    // │  │  │ Scopes for the rest │  │  │
+                    // │  │  └─────────────────────┘  │  │
+                    // │  └───────────────────────────┘  │
+                    // └─────────────────────────────────┘
+                    // Generated control flow:
+                    //          │ let Some(x) = y() else { return; }
+                    //          │
+                    // ┌────────▼───────┐
+                    // │ evaluate y()   │
+                    // └────────┬───────┘
+                    //          │              ┌────────────────┐
+                    // ┌────────▼───────┐      │Drop temporaries│
+                    // │Test the pattern├──────►in y()          │
+                    // └────────┬───────┘      │because breaking│
+                    //          │              │out of <expr>   │
+                    // ┌────────▼───────┐      │scope           │
+                    // │Move value into │      └───────┬────────┘
+                    // │binding x       │              │
+                    // └────────┬───────┘      ┌───────▼────────┐
+                    //          │              │Drop extended   │
+                    // ┌────────▼───────┐      │temporaries in  │
+                    // │Drop temporaries│      │<expr> because  │
+                    // │in y()          │      │breaking out of │
+                    // │because breaking│      │remainder scope │
+                    // │out of <expr>   │      └───────┬────────┘
+                    // │scope           │              │
+                    // └────────┬───────┘      ┌───────▼────────┐
+                    //          │              │Enter <else>    ├────────►
+                    // ┌────────▼───────┐      │block           │ return;
+                    // │Continue...     │      └────────────────┘
+                    // └────────────────┘
+
+                    let ignores_expr_result = matches!(pattern.kind, PatKind::Wild);
+                    this.block_context.push(BlockFrame::Statement { ignores_expr_result });
+
+                    // Lower the `else` block first because its parent scope is actually
+                    // enclosing the rest of the `let .. else ..` parts.
+                    let else_block_span = this.thir[*else_block].span;
+                    // This place is not really used because this destination place
+                    // should never be used to take values at the end of the failure
+                    // block.
+                    let dummy_place = this.temp(this.tcx.types.never, else_block_span);
+                    let failure_entry = this.cfg.start_new_block();
+                    let failure_block;
+                    unpack!(
+                        failure_block = this.ast_block(
+                            dummy_place,
+                            failure_entry,
+                            *else_block,
+                            this.source_info(else_block_span),
+                        )
+                    );
+                    this.cfg.terminate(
+                        failure_block,
+                        this.source_info(else_block_span),
+                        TerminatorKind::Unreachable,
+                    );
+
+                    // Declare the bindings, which may create a source scope.
+                    let remainder_span = remainder_scope.span(this.tcx, this.region_scope_tree);
+                    this.push_scope((*remainder_scope, source_info));
+                    let_scope_stack.push(remainder_scope);
+
+                    let visibility_scope =
+                        Some(this.new_source_scope(remainder_span, LintLevel::Inherited, None));
+
+                    let init = &this.thir[*initializer];
+                    let initializer_span = init.span;
+                    this.declare_bindings(
+                        visibility_scope,
+                        remainder_span,
+                        pattern,
+                        ArmHasGuard(false),
+                        Some((None, initializer_span)),
+                    );
+                    this.visit_primary_bindings(
+                        pattern,
+                        UserTypeProjections::none(),
+                        &mut |this, _, _, _, node, span, _, _| {
+                            this.storage_live_binding(block, node, span, OutsideGuard, true);
+                        },
+                    );
+                    let failure = unpack!(
+                        block = this.in_opt_scope(
+                            opt_destruction_scope.map(|de| (de, source_info)),
+                            |this| {
+                                let scope = (*init_scope, source_info);
+                                this.in_scope(scope, *lint_level, |this| {
+                                    this.ast_let_else(
+                                        block,
+                                        init,
+                                        initializer_span,
+                                        *else_block,
+                                        &last_remainder_scope,
+                                        pattern,
+                                    )
+                                })
+                            }
+                        )
+                    );
+                    this.cfg.goto(failure, source_info, failure_entry);
+
+                    if let Some(source_scope) = visibility_scope {
+                        this.source_scope = source_scope;
+                    }
+                    last_remainder_scope = *remainder_scope;
+                }
+                StmtKind::Let { init_scope, initializer: None, else_block: Some(_), .. } => {
+                    span_bug!(
+                        init_scope.span(this.tcx, this.region_scope_tree),
+                        "initializer is missing, but else block is present in this let binding",
+                    )
+                }
                 StmtKind::Let {
                     remainder_scope,
                     init_scope,
                     ref pattern,
                     initializer,
                     lint_level,
-                    else_block,
+                    else_block: None,
                 } => {
-                    let ignores_expr_result = matches!(*pattern.kind, PatKind::Wild);
+                    let ignores_expr_result = matches!(pattern.kind, PatKind::Wild);
                     this.block_context.push(BlockFrame::Statement { ignores_expr_result });
 
                     // Enter the remainder scope, i.e., the bindings' destruction scope.
@@ -125,27 +298,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                                 |this| {
                                     let scope = (*init_scope, source_info);
                                     this.in_scope(scope, *lint_level, |this| {
-                                        if let Some(else_block) = else_block {
-                                            this.ast_let_else(
-                                                block,
-                                                init,
-                                                initializer_span,
-                                                else_block,
-                                                visibility_scope,
-                                                *remainder_scope,
-                                                remainder_span,
-                                                pattern,
-                                            )
-                                        } else {
-                                            this.declare_bindings(
-                                                visibility_scope,
-                                                remainder_span,
-                                                pattern,
-                                                ArmHasGuard(false),
-                                                Some((None, initializer_span)),
-                                            );
-                                            this.expr_into_pattern(block, pattern.clone(), init) // irrefutable pattern
-                                        }
+                                        this.declare_bindings(
+                                            visibility_scope,
+                                            remainder_span,
+                                            pattern,
+                                            ArmHasGuard(false),
+                                            Some((None, initializer_span)),
+                                        );
+                                        this.expr_into_pattern(block, &pattern, init) // irrefutable pattern
                                     })
                                 },
                             )
@@ -178,6 +338,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     if let Some(source_scope) = visibility_scope {
                         this.source_scope = source_scope;
                     }
+                    last_remainder_scope = *remainder_scope;
                 }
             }