Skip to content

Commit ac0f73b

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

File tree

1 file changed

+77
-76
lines changed

1 file changed

+77
-76
lines changed

Diff for: compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+77-76
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,57 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
212216
}
213217
}
214218

219+
/// Visit a nested chain of `&&`. This must call `visit_expr` on the expressions we are not
220+
/// 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+
let res_lhs = self.visit_land(&self.thir[lhs], accumulator);
232+
let res_rhs = self.visit_land_rhs(&self.thir[rhs])?;
233+
accumulator.push(res_rhs);
234+
res_lhs
235+
}
236+
_ => {
237+
let res = self.visit_land_rhs(ex)?;
238+
accumulator.push(res);
239+
Ok(())
240+
}
241+
}
242+
}
243+
244+
/// Visit the right-hand-side of a `&&`. Used for if-let chains. Returns `Some` if the
245+
/// expression was ultimately a `let ... = ...`, and `None` if it was a normal boolean
246+
/// expression. This must call `visit_expr` on the expressions we are not handling ourselves.
247+
fn visit_land_rhs(
248+
&mut self,
249+
ex: &Expr<'tcx>,
250+
) -> Result<Option<(Span, RefutableFlag)>, ErrorGuaranteed> {
251+
match ex.kind {
252+
ExprKind::Scope { value, lint_level, .. } => {
253+
self.with_lint_level(lint_level, |this| this.visit_land_rhs(&this.thir[value]))
254+
}
255+
ExprKind::Let { box ref pat, expr } => {
256+
self.with_let_source(LetSource::None, |this| {
257+
this.visit_expr(&this.thir()[expr]);
258+
});
259+
Ok(Some((ex.span, self.is_let_irrefutable(pat)?)))
260+
}
261+
_ => {
262+
self.with_let_source(LetSource::None, |this| {
263+
this.visit_expr(ex);
264+
});
265+
Ok(None)
266+
}
267+
}
268+
}
269+
215270
fn lower_pattern(
216271
&mut self,
217272
cx: &MatchCheckCtxt<'p, 'tcx>,
@@ -249,8 +304,8 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
249304
if let LetSource::PlainLet = self.let_source {
250305
self.check_binding_is_irrefutable(pat, "local binding", Some(span))
251306
} else {
252-
let Ok(irrefutable) = self.is_let_irrefutable(pat) else { return };
253-
if irrefutable {
307+
let Ok(refutability) = self.is_let_irrefutable(pat) else { return };
308+
if matches!(refutability, Irrefutable) {
254309
report_irrefutable_let_patterns(
255310
self.tcx,
256311
self.lint_level,
@@ -321,81 +376,27 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
321376
}
322377

323378
#[instrument(level = "trace", skip(self))]
324-
fn check_let_chain(&mut self, expr: &Expr<'tcx>) {
379+
fn check_let_chain(
380+
&mut self,
381+
chain_refutabilities: Vec<Option<(Span, RefutableFlag)>>,
382+
whole_chain_span: Span,
383+
) {
325384
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-
}
383385

384-
// Emit the actual warnings.
385-
if chain_refutabilities.iter().all(|r| matches!(*r, Some((_, false)))) {
386+
if chain_refutabilities.iter().all(|r| matches!(*r, Some((_, Irrefutable)))) {
386387
// The entire chain is made up of irrefutable `let` statements
387388
report_irrefutable_let_patterns(
388389
self.tcx,
389390
self.lint_level,
390391
self.let_source,
391392
chain_refutabilities.len(),
392-
top_expr_span,
393+
whole_chain_span,
393394
);
394395
return;
395396
}
396397

397398
if let Some(until) =
398-
chain_refutabilities.iter().position(|r| !matches!(*r, Some((_, false))))
399+
chain_refutabilities.iter().position(|r| !matches!(*r, Some((_, Irrefutable))))
399400
&& until > 0
400401
{
401402
// The chain has a non-zero prefix of irrefutable `let` statements.
@@ -423,7 +424,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
423424
}
424425

425426
if let Some(from) =
426-
chain_refutabilities.iter().rposition(|r| !matches!(*r, Some((_, false))))
427+
chain_refutabilities.iter().rposition(|r| !matches!(*r, Some((_, Irrefutable))))
427428
&& from != (chain_refutabilities.len() - 1)
428429
{
429430
// The chain has a non-empty suffix of irrefutable `let` statements
@@ -453,14 +454,14 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
453454
Ok((cx, report))
454455
}
455456

456-
fn is_let_irrefutable(&mut self, pat: &Pat<'tcx>) -> Result<bool, ErrorGuaranteed> {
457+
fn is_let_irrefutable(&mut self, pat: &Pat<'tcx>) -> Result<RefutableFlag, ErrorGuaranteed> {
457458
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.
459+
// Report if the pattern is unreachable, which can only occur when the type is uninhabited.
460+
// This also reports unreachable sub-patterns.
460461
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())
462+
// If the list of witnesses is empty, the match is exhaustive, i.e. the `if let` pattern is
463+
// irrefutable.
464+
Ok(if report.non_exhaustiveness_witnesses.is_empty() { Irrefutable } else { Refutable })
464465
}
465466

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

0 commit comments

Comments
 (0)