diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 03cb4b76dc0e7..f850295e62f5b 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -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 @@ impl MatchSource { use MatchSource::*; match self { Normal => "match", - IfDesugar { .. } | IfLetDesugar { .. } => "if", + IfDesugar { .. } | IfLetDesugar { .. } | IfLetGuardDesugar => "if", WhileDesugar | WhileLetDesugar => "while", ForLoopDesugar => "for", TryDesugar => "?", diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 7ffdb7e33fb1e..d4715b836ea0f 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -228,6 +228,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { guard: Option<&Guard<'tcx>>, fake_borrow_temps: &Vec<(Place<'tcx>, Local)>, scrutinee_span: Span, + arm_span: Option, arm_scope: Option, ) -> BasicBlock { if candidate.subcandidates.is_empty() { @@ -239,6 +240,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { guard, fake_borrow_temps, scrutinee_span, + arm_span, true, ) } else { @@ -274,6 +276,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { guard, &fake_borrow_temps, scrutinee_span, + arm_span, schedule_drops, ); if arm_scope.is_none() { @@ -436,6 +439,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { &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 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// 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 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { guard: Option<&Guard<'tcx>>, fake_borrows: &Vec<(Place<'tcx>, Local)>, scrutinee_span: Span, + arm_span: Option, schedule_drops: bool, ) -> BasicBlock { debug!("bind_and_guard_matched_candidate(candidate={:?})", candidate); @@ -1659,15 +1668,42 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { 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); diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 468b3484ca5ea..0f38c30818fd5 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -1220,6 +1220,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { arm.guard.as_ref(), &fake_borrow_temps, scrutinee_span, + Some(arm.span), Some(arm.scope), ); diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 310673f5efc85..aac1e505b653b 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -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), diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index 97edbd83b89ce..29b7e176b0e1a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -164,10 +164,20 @@ impl<'tcx> MatchVisitor<'_, 'tcx> { 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); } diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs index e37c6418eb81c..2d6bbff460d7f 100644 --- a/compiler/rustc_passes/src/check_const.rs +++ b/compiler/rustc_passes/src/check_const.rs @@ -45,6 +45,8 @@ impl NonConstExpr { 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( diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index 7250de3a41c04..135f289a2b3f5 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -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