Skip to content

Commit 335156c

Browse files
committed
Accumulate let chains alongside the visit
1 parent 3760d91 commit 335156c

File tree

1 file changed

+78
-76
lines changed

1 file changed

+78
-76
lines changed

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

Lines changed: 78 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ fn create_e0004(
5656
struct_span_err!(sess, sp, E0004, "{}", &error_message)
5757
}
5858

59-
#[derive(Copy, Clone, PartialEq)]
59+
#[derive(Debug, Copy, Clone, PartialEq)]
6060
enum RefutableFlag {
6161
Irrefutable,
6262
Refutable,
@@ -151,18 +151,22 @@ impl<'thir, 'tcx> Visitor<'thir, 'tcx> for MatchVisitor<'thir, '_, 'tcx> {
151151
};
152152
self.check_match(scrutinee, arms, source, ex.span);
153153
}
154-
ExprKind::Let { box ref pat, expr } if !matches!(self.let_source, LetSource::None) => {
154+
ExprKind::Let { box ref pat, expr } => {
155155
self.check_let(pat, Some(expr), ex.span);
156156
}
157157
ExprKind::LogicalOp { op: LogicalOp::And, .. }
158158
if !matches!(self.let_source, LetSource::None) =>
159159
{
160-
self.check_let_chain(ex);
160+
let mut chain_refutabilities = Vec::new();
161+
let Ok(()) = self.visit_land(ex, &mut chain_refutabilities) else { return };
162+
// If at least one of the operands is a `let ... = ...`.
163+
if chain_refutabilities.iter().any(|x| x.is_some()) {
164+
self.check_let_chain(chain_refutabilities, ex.span);
165+
}
166+
return;
161167
}
162168
_ => {}
163169
};
164-
// If we got e.g. `let pat1 = x1 && let pat2 = x2` above, we will now traverse the two
165-
// `let`s. In order not to check them twice we set `LetSource::None`.
166170
self.with_let_source(LetSource::None, |this| visit::walk_expr(this, ex));
167171
}
168172

@@ -212,6 +216,58 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
212216
}
213217
}
214218

219+
/// Visit a nested chain of `&&`. Used for if-let chains. This must call `visit_expr` on the
220+
/// subexpressions we are not handling ourselves.
221+
fn visit_land(
222+
&mut self,
223+
ex: &Expr<'tcx>,
224+
accumulator: &mut Vec<Option<(Span, RefutableFlag)>>,
225+
) -> Result<(), ErrorGuaranteed> {
226+
match ex.kind {
227+
ExprKind::Scope { value, lint_level, .. } => self.with_lint_level(lint_level, |this| {
228+
this.visit_land(&this.thir[value], accumulator)
229+
}),
230+
ExprKind::LogicalOp { op: LogicalOp::And, lhs, rhs } => {
231+
// We recurse into the lhs only, because `&&` chains associate to the left.
232+
let res_lhs = self.visit_land(&self.thir[lhs], accumulator);
233+
let res_rhs = self.visit_land_rhs(&self.thir[rhs])?;
234+
accumulator.push(res_rhs);
235+
res_lhs
236+
}
237+
_ => {
238+
let res = self.visit_land_rhs(ex)?;
239+
accumulator.push(res);
240+
Ok(())
241+
}
242+
}
243+
}
244+
245+
/// Visit the right-hand-side of a `&&`. Used for if-let chains. Returns `Some` if the
246+
/// expression was ultimately a `let ... = ...`, and `None` if it was a normal boolean
247+
/// expression. This must call `visit_expr` on the subexpressions we are not handling ourselves.
248+
fn visit_land_rhs(
249+
&mut self,
250+
ex: &Expr<'tcx>,
251+
) -> Result<Option<(Span, RefutableFlag)>, ErrorGuaranteed> {
252+
match ex.kind {
253+
ExprKind::Scope { value, lint_level, .. } => {
254+
self.with_lint_level(lint_level, |this| this.visit_land_rhs(&this.thir[value]))
255+
}
256+
ExprKind::Let { box ref pat, expr } => {
257+
self.with_let_source(LetSource::None, |this| {
258+
this.visit_expr(&this.thir()[expr]);
259+
});
260+
Ok(Some((ex.span, self.is_let_irrefutable(pat)?)))
261+
}
262+
_ => {
263+
self.with_let_source(LetSource::None, |this| {
264+
this.visit_expr(ex);
265+
});
266+
Ok(None)
267+
}
268+
}
269+
}
270+
215271
fn lower_pattern(
216272
&mut self,
217273
cx: &MatchCheckCtxt<'p, 'tcx>,
@@ -249,8 +305,8 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
249305
if let LetSource::PlainLet = self.let_source {
250306
self.check_binding_is_irrefutable(pat, "local binding", Some(span))
251307
} else {
252-
let Ok(irrefutable) = self.is_let_irrefutable(pat) else { return };
253-
if irrefutable {
308+
let Ok(refutability) = self.is_let_irrefutable(pat) else { return };
309+
if matches!(refutability, Irrefutable) {
254310
report_irrefutable_let_patterns(
255311
self.tcx,
256312
self.lint_level,
@@ -321,81 +377,27 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
321377
}
322378

323379
#[instrument(level = "trace", skip(self))]
324-
fn check_let_chain(&mut self, expr: &Expr<'tcx>) {
380+
fn check_let_chain(
381+
&mut self,
382+
chain_refutabilities: Vec<Option<(Span, RefutableFlag)>>,
383+
whole_chain_span: Span,
384+
) {
325385
assert!(self.let_source != LetSource::None);
326-
let top_expr_span = expr.span;
327-
328-
// Lint level enclosing `next_expr`.
329-
let mut next_expr_lint_level = self.lint_level;
330-
331-
// Obtain the refutabilities of all exprs in the chain,
332-
// and record chain members that aren't let exprs.
333-
let mut chain_refutabilities = Vec::new();
334-
335-
let mut got_error = false;
336-
let mut next_expr = Some(expr);
337-
while let Some(mut expr) = next_expr {
338-
while let ExprKind::Scope { value, lint_level, .. } = expr.kind {
339-
if let LintLevel::Explicit(hir_id) = lint_level {
340-
next_expr_lint_level = hir_id
341-
}
342-
expr = &self.thir[value];
343-
}
344-
if let ExprKind::LogicalOp { op: LogicalOp::And, lhs, rhs } = expr.kind {
345-
expr = &self.thir[rhs];
346-
// Let chains recurse on the left, so we recurse into the lhs.
347-
next_expr = Some(&self.thir[lhs]);
348-
} else {
349-
next_expr = None;
350-
}
351-
352-
// Lint level enclosing `expr`.
353-
let mut expr_lint_level = next_expr_lint_level;
354-
// Fast-forward through scopes.
355-
while let ExprKind::Scope { value, lint_level, .. } = expr.kind {
356-
if let LintLevel::Explicit(hir_id) = lint_level {
357-
expr_lint_level = hir_id
358-
}
359-
expr = &self.thir[value];
360-
}
361-
let value = match expr.kind {
362-
ExprKind::Let { box ref pat, expr: _ } => {
363-
self.with_lint_level(LintLevel::Explicit(expr_lint_level), |this| {
364-
match this.is_let_irrefutable(pat) {
365-
Ok(irrefutable) => Some((expr.span, !irrefutable)),
366-
Err(_) => {
367-
got_error = true;
368-
None
369-
}
370-
}
371-
})
372-
}
373-
_ => None,
374-
};
375-
chain_refutabilities.push(value);
376-
}
377-
debug!(?chain_refutabilities);
378-
chain_refutabilities.reverse();
379-
380-
if got_error {
381-
return;
382-
}
383386

384-
// Emit the actual warnings.
385-
if chain_refutabilities.iter().all(|r| matches!(*r, Some((_, false)))) {
387+
if chain_refutabilities.iter().all(|r| matches!(*r, Some((_, Irrefutable)))) {
386388
// The entire chain is made up of irrefutable `let` statements
387389
report_irrefutable_let_patterns(
388390
self.tcx,
389391
self.lint_level,
390392
self.let_source,
391393
chain_refutabilities.len(),
392-
top_expr_span,
394+
whole_chain_span,
393395
);
394396
return;
395397
}
396398

397399
if let Some(until) =
398-
chain_refutabilities.iter().position(|r| !matches!(*r, Some((_, false))))
400+
chain_refutabilities.iter().position(|r| !matches!(*r, Some((_, Irrefutable))))
399401
&& until > 0
400402
{
401403
// The chain has a non-zero prefix of irrefutable `let` statements.
@@ -423,7 +425,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
423425
}
424426

425427
if let Some(from) =
426-
chain_refutabilities.iter().rposition(|r| !matches!(*r, Some((_, false))))
428+
chain_refutabilities.iter().rposition(|r| !matches!(*r, Some((_, Irrefutable))))
427429
&& from != (chain_refutabilities.len() - 1)
428430
{
429431
// The chain has a non-empty suffix of irrefutable `let` statements
@@ -453,14 +455,14 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
453455
Ok((cx, report))
454456
}
455457

456-
fn is_let_irrefutable(&mut self, pat: &Pat<'tcx>) -> Result<bool, ErrorGuaranteed> {
458+
fn is_let_irrefutable(&mut self, pat: &Pat<'tcx>) -> Result<RefutableFlag, ErrorGuaranteed> {
457459
let (cx, report) = self.analyze_binding(pat, Refutable)?;
458-
// Report if the pattern is unreachable, which can only occur when the type is
459-
// uninhabited. This also reports unreachable sub-patterns.
460+
// Report if the pattern is unreachable, which can only occur when the type is uninhabited.
461+
// This also reports unreachable sub-patterns.
460462
report_arm_reachability(&cx, &report);
461-
// If the list of witnesses is empty, the match is exhaustive,
462-
// i.e. the `if let` pattern is irrefutable.
463-
Ok(report.non_exhaustiveness_witnesses.is_empty())
463+
// If the list of witnesses is empty, the match is exhaustive, i.e. the `if let` pattern is
464+
// irrefutable.
465+
Ok(if report.non_exhaustiveness_witnesses.is_empty() { Irrefutable } else { Refutable })
464466
}
465467

466468
#[instrument(level = "trace", skip(self))]

0 commit comments

Comments
 (0)