Skip to content

Commit 1e1ba7c

Browse files
authored
Rollup merge of #79051 - LeSeulArtichaut:if-let-guard, r=matthewjasper
Implement if-let match guards Implements rust-lang/rfcs#2294 (tracking issue: #51114). I probably should do a few more things before this can be merged: - [x] Add tests (added basic tests, more advanced tests could be done in the future?) - [x] Add lint for exhaustive if-let guard (comparable to normal if-let statements) - [x] Fix clippy However since this is a nightly feature maybe it's fine to land this and do those steps in follow-up PRs. Thanks a lot `@matthewjasper` ❤️ for helping me with lowering to MIR! Would you be interested in reviewing this? r? `@ghost` for now
2 parents b32e6e6 + 0917260 commit 1e1ba7c

File tree

27 files changed

+360
-81
lines changed

27 files changed

+360
-81
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -505,14 +505,19 @@ impl<'hir> LoweringContext<'_, 'hir> {
505505
}
506506

507507
fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> {
508+
let pat = self.lower_pat(&arm.pat);
509+
let guard = arm.guard.as_ref().map(|cond| {
510+
if let ExprKind::Let(ref pat, ref scrutinee) = cond.kind {
511+
hir::Guard::IfLet(self.lower_pat(pat), self.lower_expr(scrutinee))
512+
} else {
513+
hir::Guard::If(self.lower_expr(cond))
514+
}
515+
});
508516
hir::Arm {
509517
hir_id: self.next_id(),
510518
attrs: self.lower_attrs(&arm.attrs),
511-
pat: self.lower_pat(&arm.pat),
512-
guard: match arm.guard {
513-
Some(ref x) => Some(hir::Guard::If(self.lower_expr(x))),
514-
_ => None,
515-
},
519+
pat,
520+
guard,
516521
body: self.lower_expr(&arm.body),
517522
span: arm.span,
518523
}

compiler/rustc_hir/src/hir.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,7 @@ pub struct Arm<'hir> {
11601160
#[derive(Debug, HashStable_Generic)]
11611161
pub enum Guard<'hir> {
11621162
If(&'hir Expr<'hir>),
1163+
IfLet(&'hir Pat<'hir>, &'hir Expr<'hir>),
11631164
}
11641165

11651166
#[derive(Debug, HashStable_Generic)]
@@ -1721,6 +1722,8 @@ pub enum MatchSource {
17211722
IfDesugar { contains_else_clause: bool },
17221723
/// An `if let _ = _ { .. }` (optionally with `else { .. }`).
17231724
IfLetDesugar { contains_else_clause: bool },
1725+
/// An `if let _ = _ => { .. }` match guard.
1726+
IfLetGuardDesugar,
17241727
/// A `while _ { .. }` (which was desugared to a `loop { match _ { .. } }`).
17251728
WhileDesugar,
17261729
/// A `while let _ = _ { .. }` (which was desugared to a
@@ -1739,7 +1742,7 @@ impl MatchSource {
17391742
use MatchSource::*;
17401743
match self {
17411744
Normal => "match",
1742-
IfDesugar { .. } | IfLetDesugar { .. } => "if",
1745+
IfDesugar { .. } | IfLetDesugar { .. } | IfLetGuardDesugar => "if",
17431746
WhileDesugar | WhileLetDesugar => "while",
17441747
ForLoopDesugar => "for",
17451748
TryDesugar => "?",

compiler/rustc_hir/src/intravisit.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,10 @@ pub fn walk_arm<'v, V: Visitor<'v>>(visitor: &mut V, arm: &'v Arm<'v>) {
12281228
if let Some(ref g) = arm.guard {
12291229
match g {
12301230
Guard::If(ref e) => visitor.visit_expr(e),
1231+
Guard::IfLet(ref pat, ref e) => {
1232+
visitor.visit_pat(pat);
1233+
visitor.visit_expr(e);
1234+
}
12311235
}
12321236
}
12331237
visitor.visit_expr(&arm.body);

compiler/rustc_hir_pretty/src/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -2002,6 +2002,15 @@ impl<'a> State<'a> {
20022002
self.print_expr(&e);
20032003
self.s.space();
20042004
}
2005+
hir::Guard::IfLet(pat, e) => {
2006+
self.word_nbsp("if");
2007+
self.word_nbsp("let");
2008+
self.print_pat(&pat);
2009+
self.s.space();
2010+
self.word_space("=");
2011+
self.print_expr(&e);
2012+
self.s.space();
2013+
}
20052014
}
20062015
}
20072016
self.word_space("=>");

compiler/rustc_mir_build/src/build/matches/mod.rs

+45-9
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
228228
guard: Option<&Guard<'tcx>>,
229229
fake_borrow_temps: &Vec<(Place<'tcx>, Local)>,
230230
scrutinee_span: Span,
231+
arm_span: Option<Span>,
231232
arm_scope: Option<region::Scope>,
232233
) -> BasicBlock {
233234
if candidate.subcandidates.is_empty() {
@@ -239,6 +240,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
239240
guard,
240241
fake_borrow_temps,
241242
scrutinee_span,
243+
arm_span,
242244
true,
243245
)
244246
} else {
@@ -274,6 +276,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
274276
guard,
275277
&fake_borrow_temps,
276278
scrutinee_span,
279+
arm_span,
277280
schedule_drops,
278281
);
279282
if arm_scope.is_none() {
@@ -436,6 +439,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
436439
&fake_borrow_temps,
437440
irrefutable_pat.span,
438441
None,
442+
None,
439443
)
440444
.unit()
441445
}
@@ -817,11 +821,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
817821
/// For an example of a case where we set `otherwise_block`, even for an
818822
/// exhaustive match consider:
819823
///
824+
/// ```rust
820825
/// match x {
821826
/// (true, true) => (),
822827
/// (_, false) => (),
823828
/// (false, true) => (),
824829
/// }
830+
/// ```
825831
///
826832
/// For this match, we check if `x.0` matches `true` (for the first
827833
/// 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> {
935941
/// Link up matched candidates. For example, if we have something like
936942
/// this:
937943
///
944+
/// ```rust
938945
/// ...
939946
/// Some(x) if cond => ...
940947
/// Some(x) => ...
941948
/// Some(x) if cond => ...
942949
/// ...
950+
/// ```
943951
///
944952
/// We generate real edges from:
945953
/// * `start_block` to the `prebinding_block` of the first pattern,
@@ -1517,7 +1525,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
15171525
/// Initializes each of the bindings from the candidate by
15181526
/// moving/copying/ref'ing the source as appropriate. Tests the guard, if
15191527
/// any, and then branches to the arm. Returns the block for the case where
1520-
/// the guard fails.
1528+
/// the guard succeeds.
15211529
///
15221530
/// Note: we do not check earlier that if there is a guard,
15231531
/// there cannot be move bindings. We avoid a use-after-move by only
@@ -1529,6 +1537,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
15291537
guard: Option<&Guard<'tcx>>,
15301538
fake_borrows: &Vec<(Place<'tcx>, Local)>,
15311539
scrutinee_span: Span,
1540+
arm_span: Option<Span>,
15321541
schedule_drops: bool,
15331542
) -> BasicBlock {
15341543
debug!("bind_and_guard_matched_candidate(candidate={:?})", candidate);
@@ -1659,15 +1668,42 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16591668
self.cfg.push_assign(block, scrutinee_source_info, Place::from(temp), borrow);
16601669
}
16611670

1662-
// the block to branch to if the guard fails; if there is no
1663-
// guard, this block is simply unreachable
1664-
let guard = match guard {
1665-
Guard::If(e) => self.hir.mirror(e.clone()),
1671+
let (guard_span, (post_guard_block, otherwise_post_guard_block)) = match guard {
1672+
Guard::If(e) => {
1673+
let e = self.hir.mirror(e.clone());
1674+
let source_info = self.source_info(e.span);
1675+
(e.span, self.test_bool(block, e, source_info))
1676+
},
1677+
Guard::IfLet(pat, scrutinee) => {
1678+
let scrutinee_span = scrutinee.span();
1679+
let scrutinee_place = unpack!(block = self.lower_scrutinee(block, scrutinee.clone(), scrutinee_span));
1680+
let mut guard_candidate = Candidate::new(scrutinee_place, &pat, false);
1681+
let wildcard = Pat::wildcard_from_ty(pat.ty);
1682+
let mut otherwise_candidate = Candidate::new(scrutinee_place, &wildcard, false);
1683+
let fake_borrow_temps =
1684+
self.lower_match_tree(block, pat.span, false, &mut [&mut guard_candidate, &mut otherwise_candidate]);
1685+
self.declare_bindings(
1686+
None,
1687+
pat.span.to(arm_span.unwrap()),
1688+
pat,
1689+
ArmHasGuard(false),
1690+
Some((Some(&scrutinee_place), scrutinee.span())),
1691+
);
1692+
let post_guard_block = self.bind_pattern(
1693+
self.source_info(pat.span),
1694+
guard_candidate,
1695+
None,
1696+
&fake_borrow_temps,
1697+
scrutinee.span(),
1698+
None,
1699+
None,
1700+
);
1701+
let otherwise_post_guard_block = otherwise_candidate.pre_binding_block.unwrap();
1702+
(scrutinee_span, (post_guard_block, otherwise_post_guard_block))
1703+
}
16661704
};
1667-
let source_info = self.source_info(guard.span);
1668-
let guard_end = self.source_info(tcx.sess.source_map().end_point(guard.span));
1669-
let (post_guard_block, otherwise_post_guard_block) =
1670-
self.test_bool(block, guard, source_info);
1705+
let source_info = self.source_info(guard_span);
1706+
let guard_end = self.source_info(tcx.sess.source_map().end_point(guard_span));
16711707
let guard_frame = self.guard_context.pop().unwrap();
16721708
debug!("Exiting guard building context with locals: {:?}", guard_frame);
16731709

compiler/rustc_mir_build/src/build/scope.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
11971197
arm.guard.as_ref(),
11981198
&fake_borrow_temps,
11991199
scrutinee_span,
1200+
Some(arm.span),
12001201
Some(arm.scope),
12011202
);
12021203

compiler/rustc_mir_build/src/thir/cx/expr.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -776,10 +776,10 @@ impl ToBorrowKind for hir::Mutability {
776776
fn convert_arm<'tcx>(cx: &mut Cx<'_, 'tcx>, arm: &'tcx hir::Arm<'tcx>) -> Arm<'tcx> {
777777
Arm {
778778
pattern: cx.pattern_from_hir(&arm.pat),
779-
guard: match arm.guard {
780-
Some(hir::Guard::If(ref e)) => Some(Guard::If(e.to_ref())),
781-
_ => None,
782-
},
779+
guard: arm.guard.as_ref().map(|g| match g {
780+
hir::Guard::If(ref e) => Guard::If(e.to_ref()),
781+
hir::Guard::IfLet(ref pat, ref e) => Guard::IfLet(cx.pattern_from_hir(pat), e.to_ref()),
782+
}),
783783
body: arm.body.to_ref(),
784784
lint_level: LintLevel::Explicit(arm.hir_id),
785785
scope: region::Scope { id: arm.hir_id.local_id, data: region::ScopeData::Node },

compiler/rustc_mir_build/src/thir/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ crate struct Arm<'tcx> {
344344
#[derive(Clone, Debug)]
345345
crate enum Guard<'tcx> {
346346
If(ExprRef<'tcx>),
347+
IfLet(Pat<'tcx>, ExprRef<'tcx>),
347348
}
348349

349350
#[derive(Copy, Clone, Debug)]

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+31
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,20 @@ impl<'tcx> MatchVisitor<'_, 'tcx> {
164164
for arm in arms {
165165
// Check the arm for some things unrelated to exhaustiveness.
166166
self.check_patterns(&arm.pat);
167+
if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard {
168+
self.check_patterns(pat);
169+
}
167170
}
168171

169172
let mut cx = self.new_cx(scrut.hir_id);
170173

174+
for arm in arms {
175+
if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard {
176+
let tpat = self.lower_pattern(&mut cx, pat, &mut false).0;
177+
check_if_let_guard(&mut cx, &tpat, pat.hir_id);
178+
}
179+
}
180+
171181
let mut have_errors = false;
172182

173183
let arms: Vec<_> = arms
@@ -360,12 +370,28 @@ fn irrefutable_let_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, source: hir::
360370
let msg = match source {
361371
hir::MatchSource::IfLetDesugar { .. } => "irrefutable if-let pattern",
362372
hir::MatchSource::WhileLetDesugar => "irrefutable while-let pattern",
373+
hir::MatchSource::IfLetGuardDesugar => "irrefutable if-let guard",
363374
_ => bug!(),
364375
};
365376
lint.build(msg).emit()
366377
});
367378
}
368379

380+
fn check_if_let_guard<'p, 'tcx>(
381+
cx: &mut MatchCheckCtxt<'p, 'tcx>,
382+
pat: &'p super::Pat<'tcx>,
383+
pat_id: HirId,
384+
) {
385+
let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }];
386+
let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty);
387+
report_arm_reachability(&cx, &report, hir::MatchSource::IfLetGuardDesugar);
388+
389+
if report.non_exhaustiveness_witnesses.is_empty() {
390+
// The match is exhaustive, i.e. the if let pattern is irrefutable.
391+
irrefutable_let_pattern(cx.tcx, pat.span, pat_id, hir::MatchSource::IfLetGuardDesugar)
392+
}
393+
}
394+
369395
/// Report unreachable arms, if any.
370396
fn report_arm_reachability<'p, 'tcx>(
371397
cx: &MatchCheckCtxt<'p, 'tcx>,
@@ -390,6 +416,11 @@ fn report_arm_reachability<'p, 'tcx>(
390416
}
391417
}
392418

419+
hir::MatchSource::IfLetGuardDesugar => {
420+
assert_eq!(arm_index, 0);
421+
unreachable_pattern(cx.tcx, arm.pat.span, arm.hir_id, None);
422+
}
423+
393424
hir::MatchSource::ForLoopDesugar | hir::MatchSource::Normal => {
394425
unreachable_pattern(cx.tcx, arm.pat.span, arm.hir_id, catchall);
395426
}

compiler/rustc_passes/src/check_const.rs

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ impl NonConstExpr {
4545
return None;
4646
}
4747

48+
Self::Match(IfLetGuardDesugar) => bug!("if-let guard outside a `match` expression"),
49+
4850
// All other expressions are allowed.
4951
Self::Loop(Loop | While | WhileLet)
5052
| Self::Match(

compiler/rustc_passes/src/liveness.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,9 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
360360

361361
fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
362362
self.add_from_pat(&arm.pat);
363+
if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard {
364+
self.add_from_pat(pat);
365+
}
363366
intravisit::walk_arm(self, arm);
364367
}
365368

@@ -866,10 +869,13 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
866869
for arm in arms {
867870
let body_succ = self.propagate_through_expr(&arm.body, succ);
868871

869-
let guard_succ = self.propagate_through_opt_expr(
870-
arm.guard.as_ref().map(|hir::Guard::If(e)| *e),
871-
body_succ,
872-
);
872+
let guard_succ = arm.guard.as_ref().map_or(body_succ, |g| match g {
873+
hir::Guard::If(e) => self.propagate_through_expr(e, body_succ),
874+
hir::Guard::IfLet(pat, e) => {
875+
let let_bind = self.define_bindings_in_pat(pat, body_succ);
876+
self.propagate_through_expr(e, let_bind)
877+
}
878+
});
873879
let arm_succ = self.define_bindings_in_pat(&arm.pat, guard_succ);
874880
self.merge_from_succ(ln, arm_succ);
875881
}

0 commit comments

Comments
 (0)