Skip to content

Add matches_if_let lint for if matches! conditions#16739

Open
Taym95 wants to merge 2 commits intorust-lang:masterfrom
Taym95:matches_if_let
Open

Add matches_if_let lint for if matches! conditions#16739
Taym95 wants to merge 2 commits intorust-lang:masterfrom
Taym95:matches_if_let

Conversation

@Taym95
Copy link
Copy Markdown
Contributor

@Taym95 Taym95 commented Mar 20, 2026

fixes #16660

changelog: [matches_if_let]: add a new restriction lint that suggests replacing if matches!(...) with if let when the rewrite is safe, including guarded cases when if let chains are available.

  • Followed lint naming conventions
  • Added passing UI tests (including committed .stderr file)
  • cargo test passes locally
  • Executed cargo dev update_lints
  • Added lint documentation
  • Run cargo dev fmt

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>
@rustbot rustbot added needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Mar 20, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 20, 2026

r? @Jarcho

rustbot has assigned @Jarcho.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: 7 candidates
  • 7 candidates expanded to 7 candidates
  • Random selection from Jarcho, dswij, llogiq, samueltardieu

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 20, 2026

Lintcheck changes for 94a3813

Lint Added Removed Changed
clippy::matches_if_let 42 0 0

This comment will be updated if you push new changes

Copy link
Copy Markdown
Contributor

@Jarcho Jarcho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for the lint seems to be fine other than the one note about checking the parent SyntaxContext. Can you also switch this to using if chains rather than a bunch of returns.

View changes since this review

Comment on lines +29 to +31
if !is_matches_expansion(first_arm, second_arm) {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed. We already know this is from the matches macro.

cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'tcx>,
arms: &'tcx [Arm<'tcx>],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be taking the single arm of interest.

Comment on lines +33 to +39
let Some(if_expr) = get_parent_expr(cx, expr) else {
return;
};

let ExprKind::If(cond, then, _) = if_expr.kind else {
return;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will catch if matches!(), but not if x && matches!().

This won't block the PR, but this should be handled.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I tried to fix it part of this RR, please have another look.

Comment on lines +51 to +54
let mut uses_binding_name_in_body = false;
first_arm.pat.each_binding_or_first(&mut |_, _, _, ident| {
uses_binding_name_in_body |= contains_name(ident.name, then, cx);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be collecting the binding IDs then walking the body once. SsoHashSet would be appropriate here.

let pat = snippet_with_context(cx, first_arm.pat.span, ctxt, "..", &mut app).0;
let scrutinee = Sugg::hir_with_context(cx, scrutinee, ctxt, "..", &mut app)
.maybe_paren()
.into_string();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sugg implements Display. No need to call into_string.

let suggestion = if let Some(guard) = first_arm.guard {
let guard = Sugg::hir_with_context(cx, guard, ctxt, "..", &mut app)
.maybe_paren()
.into_string();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sugg implements Display. No need to call into_string.

MATCHES_IF_LET,
matches_span,
"this `matches!` can be written as an `if let`",
"consider using `if let`",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the action to take here so just "use `if let`".

cx,
MATCHES_IF_LET,
matches_span,
"this `matches!` can be written as an `if let`",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be better as something like "`matches!` used as an 'if' condition". All matches can technically be replaced.

return;
};

if cond.hir_id != expr.hir_id || if_expr.span.from_expansion() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be checking that the if is from the same context as the matches call.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Mar 23, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 23, 2026

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@Jarcho
Copy link
Copy Markdown
Contributor

Jarcho commented Mar 23, 2026

@rustbot label lint-nominated

@rustbot rustbot added the lint-nominated Create an FCP-thread on Zulip for this PR label Mar 23, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 23, 2026

This lint has been nominated for inclusion.

A FCP topic has been created on Zulip.

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>
Copy link
Copy Markdown
Contributor

@Jarcho Jarcho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please use let chains for the main check function. There's no need to have multiple returns spread throughout the function.

View changes since this review

Comment on lines +102 to +107
if op.node == BinOpKind::And && (left.hir_id == child_id || right.hir_id == child_id) =>
{
child_id = parent_id;
needs_if_let_chain = true;
},
ExprKind::DropTemps(inner) if inner.hir_id == child_id => child_id = parent_id,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checks for the child ID on both arms are always true. No need to check.

}

let mut visitor = BodyUsesBindingName { cx, binding_names };
visitor.visit_expr(then).is_break()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs to visit every expression in the condition in the if after the matches.

Comment on lines +101 to +106
ExprKind::Binary(op, left, right)
if op.node == BinOpKind::And && (left.hir_id == child_id || right.hir_id == child_id) =>
{
child_id = parent_id;
needs_if_let_chain = true;
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to record the rhs expression every time you come from the lhs for the binding use check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lint-nominated Create an FCP-thread on Zulip for this PR needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use if let instead of if matches!

3 participants