]> git.lizzy.rs Git - rust.git/commitdiff
Implement lowering of if-let guards to MIR
authorLeSeulArtichaut <leseulartichaut@gmail.com>
Fri, 13 Nov 2020 17:27:27 +0000 (18:27 +0100)
committerLeSeulArtichaut <leseulartichaut@gmail.com>
Sun, 6 Dec 2020 12:42:24 +0000 (13:42 +0100)
compiler/rustc_hir/src/hir.rs
compiler/rustc_mir_build/src/build/matches/mod.rs
compiler/rustc_mir_build/src/build/scope.rs
compiler/rustc_mir_build/src/thir/cx/expr.rs
compiler/rustc_mir_build/src/thir/pattern/check_match.rs
compiler/rustc_passes/src/check_const.rs
src/tools/clippy/clippy_lints/src/utils/author.rs

index 03cb4b76dc0e7e045c897c1ffd8b500d07a2399e..f850295e62f5bddbfc5b827e2e1eaa2fda546a81 100644 (file)
@@ -1722,6 +1722,8 @@ pub enum MatchSource {
     IfDesugar { contains_else_clause: bool },
     /// An `if let _ = _ { .. }` (optionally with `else { .. }`).
     IfLetDesugar { contains_else_clause: bool },
+    /// An `if let _ = _ => { .. }` match guard.
+    IfLetGuardDesugar,
     /// A `while _ { .. }` (which was desugared to a `loop { match _ { .. } }`).
     WhileDesugar,
     /// A `while let _ = _ { .. }` (which was desugared to a
@@ -1740,7 +1742,7 @@ pub fn name(self) -> &'static str {
         use MatchSource::*;
         match self {
             Normal => "match",
-            IfDesugar { .. } | IfLetDesugar { .. } => "if",
+            IfDesugar { .. } | IfLetDesugar { .. } | IfLetGuardDesugar => "if",
             WhileDesugar | WhileLetDesugar => "while",
             ForLoopDesugar => "for",
             TryDesugar => "?",
index 7ffdb7e33fb1e019b497cf84bcc282d209f1a97f..d4715b836ea0febb21cbcad35a17eae1982dd244 100644 (file)
@@ -228,6 +228,7 @@ fn lower_match_tree<'pat>(
         guard: Option<&Guard<'tcx>>,
         fake_borrow_temps: &Vec<(Place<'tcx>, Local)>,
         scrutinee_span: Span,
+        arm_span: Option<Span>,
         arm_scope: Option<region::Scope>,
     ) -> BasicBlock {
         if candidate.subcandidates.is_empty() {
@@ -239,6 +240,7 @@ fn lower_match_tree<'pat>(
                 guard,
                 fake_borrow_temps,
                 scrutinee_span,
+                arm_span,
                 true,
             )
         } else {
@@ -274,6 +276,7 @@ fn lower_match_tree<'pat>(
                         guard,
                         &fake_borrow_temps,
                         scrutinee_span,
+                        arm_span,
                         schedule_drops,
                     );
                     if arm_scope.is_none() {
@@ -436,6 +439,7 @@ pub(super) fn expr_into_pattern(
             &fake_borrow_temps,
             irrefutable_pat.span,
             None,
+            None,
         )
         .unit()
     }
@@ -817,11 +821,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
     /// For an example of a case where we set `otherwise_block`, even for an
     /// exhaustive match consider:
     ///
+    /// ```rust
     /// match x {
     ///     (true, true) => (),
     ///     (_, false) => (),
     ///     (false, true) => (),
     /// }
+    /// ```
     ///
     /// For this match, we check if `x.0` matches `true` (for the first
     /// arm). If that's false, we check `x.1`. If it's `true` we check if
@@ -935,11 +941,13 @@ fn match_simplified_candidates(
     /// Link up matched candidates. For example, if we have something like
     /// this:
     ///
+    /// ```rust
     /// ...
     /// Some(x) if cond => ...
     /// Some(x) => ...
     /// Some(x) if cond => ...
     /// ...
+    /// ```
     ///
     /// We generate real edges from:
     /// * `start_block` to the `prebinding_block` of the first pattern,
@@ -1517,7 +1525,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
     /// Initializes each of the bindings from the candidate by
     /// moving/copying/ref'ing the source as appropriate. Tests the guard, if
     /// any, and then branches to the arm. Returns the block for the case where
-    /// the guard fails.
+    /// the guard succeeds.
     ///
     /// Note: we do not check earlier that if there is a guard,
     /// there cannot be move bindings. We avoid a use-after-move by only
@@ -1529,6 +1537,7 @@ fn bind_and_guard_matched_candidate<'pat>(
         guard: Option<&Guard<'tcx>>,
         fake_borrows: &Vec<(Place<'tcx>, Local)>,
         scrutinee_span: Span,
+        arm_span: Option<Span>,
         schedule_drops: bool,
     ) -> BasicBlock {
         debug!("bind_and_guard_matched_candidate(candidate={:?})", candidate);
@@ -1659,15 +1668,42 @@ fn bind_and_guard_matched_candidate<'pat>(
                 self.cfg.push_assign(block, scrutinee_source_info, Place::from(temp), borrow);
             }
 
-            // the block to branch to if the guard fails; if there is no
-            // guard, this block is simply unreachable
-            let guard = match guard {
-                Guard::If(e) => self.hir.mirror(e.clone()),
+            let (guard_span, (post_guard_block, otherwise_post_guard_block)) = match guard {
+                Guard::If(e) => {
+                    let e = self.hir.mirror(e.clone());
+                    let source_info = self.source_info(e.span);
+                    (e.span, self.test_bool(block, e, source_info))
+                },
+                Guard::IfLet(pat, scrutinee) => {
+                    let scrutinee_span = scrutinee.span();
+                    let scrutinee_place = unpack!(block = self.lower_scrutinee(block, scrutinee.clone(), scrutinee_span));
+                    let mut guard_candidate = Candidate::new(scrutinee_place, &pat, false);
+                    let wildcard = Pat::wildcard_from_ty(pat.ty);
+                    let mut otherwise_candidate = Candidate::new(scrutinee_place, &wildcard, false);
+                    let fake_borrow_temps =
+                        self.lower_match_tree(block, pat.span, false, &mut [&mut guard_candidate, &mut otherwise_candidate]);
+                    self.declare_bindings(
+                        None,
+                        pat.span.to(arm_span.unwrap()),
+                        pat,
+                        ArmHasGuard(false),
+                        Some((Some(&scrutinee_place), scrutinee.span())),
+                    );
+                    let post_guard_block = self.bind_pattern(
+                        self.source_info(pat.span),
+                        guard_candidate,
+                        None,
+                        &fake_borrow_temps,
+                        scrutinee.span(),
+                        None,
+                        None,
+                    );
+                    let otherwise_post_guard_block = otherwise_candidate.pre_binding_block.unwrap();
+                    (scrutinee_span, (post_guard_block, otherwise_post_guard_block))
+                }
             };
-            let source_info = self.source_info(guard.span);
-            let guard_end = self.source_info(tcx.sess.source_map().end_point(guard.span));
-            let (post_guard_block, otherwise_post_guard_block) =
-                self.test_bool(block, guard, source_info);
+            let source_info = self.source_info(guard_span);
+            let guard_end = self.source_info(tcx.sess.source_map().end_point(guard_span));
             let guard_frame = self.guard_context.pop().unwrap();
             debug!("Exiting guard building context with locals: {:?}", guard_frame);
 
index 468b3484ca5ea653a19aaa7a73ce8ce4301c8da3..0f38c30818fd572b40790cbc6f83c227c2fb9146 100644 (file)
@@ -1220,6 +1220,7 @@ fn diverge_cleanup(&mut self) -> DropIdx {
                         arm.guard.as_ref(),
                         &fake_borrow_temps,
                         scrutinee_span,
+                        Some(arm.span),
                         Some(arm.scope),
                     );
 
index 310673f5efc852e9594648e17c8d6a62c527ef66..aac1e505b653b748916f64cbc42eaacde83a2cc4 100644 (file)
@@ -778,7 +778,7 @@ fn convert_arm<'tcx>(cx: &mut Cx<'_, 'tcx>, arm: &'tcx hir::Arm<'tcx>) -> Arm<'t
         pattern: cx.pattern_from_hir(&arm.pat),
         guard: arm.guard.as_ref().map(|g| match g {
             hir::Guard::If(ref e) => Guard::If(e.to_ref()),
-            hir::Guard::IfLet(ref pat, ref e) => Guard::IfLet(cx.pattern_from_hir(pat), e.to_ref())
+            hir::Guard::IfLet(ref pat, ref e) => Guard::IfLet(cx.pattern_from_hir(pat), e.to_ref()),
         }),
         body: arm.body.to_ref(),
         lint_level: LintLevel::Explicit(arm.hir_id),
index 97edbd83b89cece6bcc8654445c785c376ee8dc3..29b7e176b0e1ad076bf76570d8500a52e2576abc 100644 (file)
@@ -164,10 +164,20 @@ fn check_match(
         for arm in arms {
             // Check the arm for some things unrelated to exhaustiveness.
             self.check_patterns(&arm.pat);
+            if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard {
+                self.check_patterns(pat);
+            }
         }
 
         let mut cx = self.new_cx(scrut.hir_id);
 
+        for arm in arms {
+            if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard {
+                let tpat = self.lower_pattern(&mut cx, pat, &mut false).0;
+                check_if_let_guard(&mut cx, &tpat, pat.hir_id);
+            }
+        }
+
         let mut have_errors = false;
 
         let arms: Vec<_> = arms
@@ -360,12 +370,28 @@ fn irrefutable_let_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, source: hir::
         let msg = match source {
             hir::MatchSource::IfLetDesugar { .. } => "irrefutable if-let pattern",
             hir::MatchSource::WhileLetDesugar => "irrefutable while-let pattern",
+            hir::MatchSource::IfLetGuardDesugar => "irrefutable if-let guard",
             _ => bug!(),
         };
         lint.build(msg).emit()
     });
 }
 
+fn check_if_let_guard<'p, 'tcx>(
+    cx: &mut MatchCheckCtxt<'p, 'tcx>,
+    pat: &'p super::Pat<'tcx>,
+    pat_id: HirId,
+) {
+    let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }];
+    let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty);
+    report_arm_reachability(&cx, &report, hir::MatchSource::IfLetGuardDesugar);
+
+    if report.non_exhaustiveness_witnesses.is_empty() {
+        // The match is exhaustive, i.e. the if let pattern is irrefutable.
+        irrefutable_let_pattern(cx.tcx, pat.span, pat_id, hir::MatchSource::IfLetGuardDesugar)
+    }
+}
+
 /// Report unreachable arms, if any.
 fn report_arm_reachability<'p, 'tcx>(
     cx: &MatchCheckCtxt<'p, 'tcx>,
@@ -390,6 +416,11 @@ fn report_arm_reachability<'p, 'tcx>(
                         }
                     }
 
+                    hir::MatchSource::IfLetGuardDesugar => {
+                        assert_eq!(arm_index, 0);
+                        unreachable_pattern(cx.tcx, arm.pat.span, arm.hir_id, None);
+                    }
+
                     hir::MatchSource::ForLoopDesugar | hir::MatchSource::Normal => {
                         unreachable_pattern(cx.tcx, arm.pat.span, arm.hir_id, catchall);
                     }
index e37c6418eb81caace69acc2f6fe2216715c50d12..2d6bbff460d7fb6ea61eb613b91f2dfc44996957 100644 (file)
@@ -45,6 +45,8 @@ fn required_feature_gates(self) -> Option<&'static [Symbol]> {
                 return None;
             }
 
+            Self::Match(IfLetGuardDesugar) => bug!("if-let guard outside a `match` expression"),
+
             // All other expressions are allowed.
             Self::Loop(Loop | While | WhileLet)
             | Self::Match(
index 7250de3a41c0430c71579b057e4e97314e9a583b..135f289a2b3f5c09f109c99f9279e3677a958b0c 100644 (file)
@@ -730,6 +730,7 @@ fn desugaring_name(des: hir::MatchSource) -> String {
             "MatchSource::IfLetDesugar {{ contains_else_clause: {} }}",
             contains_else_clause
         ),
+        hir::MatchSource::IfLetGuardDesugar => "MatchSource::IfLetGuardDesugar".to_string(),
         hir::MatchSource::IfDesugar { contains_else_clause } => format!(
             "MatchSource::IfDesugar {{ contains_else_clause: {} }}",
             contains_else_clause