Skip to content

Conversation

ada4a
Copy link
Contributor

@ada4a ada4a commented Jun 5, 2025

Fixes #14971

WIP because:

  • is there a way to remove the block braces from the suggestion?
  • I guess the "Known problems" section is not really true, since eq_expr_value takes care of impure function calls?
  • it would be good to support the recently stabilized if-let guards

changelog: new lint: [duplicate_match_guards]

@rustbot
Copy link
Collaborator

rustbot commented Jun 5, 2025

r? @y21

rustbot has assigned @y21.
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

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Jun 5, 2025
@ada4a ada4a force-pushed the duplicate_match_guard branch 4 times, most recently from bd9de96 to c7f0188 Compare June 6, 2025 10:22
@ada4a ada4a force-pushed the duplicate_match_guard branch from c7f0188 to 71c4b42 Compare June 24, 2025 17:33
@ada4a ada4a force-pushed the duplicate_match_guard branch 2 times, most recently from 8bd7cbe to a86397c Compare July 2, 2025 17:20
@ada4a ada4a changed the title WIP: New lint: duplicate_match_guard WIP: New lint: duplicate_match_guards Jul 2, 2025
@ada4a ada4a force-pushed the duplicate_match_guard branch 5 times, most recently from fab6b13 to 1a5e31b Compare July 2, 2025 22:49
@ada4a ada4a force-pushed the duplicate_match_guard branch from 1a5e31b to d6e58a9 Compare August 7, 2025 21:52
};

if let ExprKind::If(cond, then, None) = arm_body_expr.kind
&& eq_expr_value(cx, guard, cond.peel_drop_temps())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the lint is directed at copy-paste errors, maybe we should make this stricter, and make sure that the expr are "syntactically" equal, and don't just evaluate to the result.

Copy link
Member

Choose a reason for hiding this comment

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

eq_expr_value already does that (it uses SpanlessEq, which checks for syntactic equality). Or how do you mean this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No yes, that's what I meant. If that's the case though, how is the following able to pass?

// not _identical_, but the meaning is the same
0 if a > b => {
if b < a {
//~^ duplicate_match_guards
return;
}
},

@ada4a ada4a force-pushed the duplicate_match_guard branch from d6e58a9 to 3e4ace9 Compare August 22, 2025 13:36
@rustbot

This comment has been minimized.

};

if let ExprKind::If(cond, then, None) = arm_body_expr.kind
&& eq_expr_value(cx, guard, cond.peel_drop_temps())
Copy link
Member

Choose a reason for hiding this comment

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

eq_expr_value already does that (it uses SpanlessEq, which checks for syntactic equality). Or how do you mean this?

Comment on lines 20 to 21
/// False negatives: if the condition is an impure function, it could've been called twice on
/// purpose for its side effects
Copy link
Member

Choose a reason for hiding this comment

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

I'm slightly confused by this section. This says "false negative", but then describes what sounds like would be a false positive ("the lint falsely warns if the condition has impure function calls which would change semantics"), but this case is actually handled by the lint anyway given that there's a test for it (so there's no issue at all)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're absolutely right. I think that's a remnant of another lint I took inspiration from.

/// ```
#[clippy::version = "1.89.0"]
pub DUPLICATE_MATCH_GUARDS,
nursery,
Copy link
Member

Choose a reason for hiding this comment

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

I think suspicious would be fine for the category

(arm.body, false)
};

if let ExprKind::If(cond, then, None) = arm_body_expr.kind
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason to not also lint if there's an else? It still seems just as (arguably even more) wrong if there's an else {} because the block will additionally just be unreachable, wouldn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think there is, no. It's just that my motivating case in particular didn't have an else

Copy link
Contributor Author

@ada4a ada4a Sep 28, 2025

Choose a reason for hiding this comment

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

(arguably even more) wrong

Given this, maybe it would make sense to lint the else is Some case with a separate lint, placed into the correctness group?

Comment on lines +63 to +61
if block.stmts.is_empty()
&& let Some(trailing_expr) = block.expr
Copy link
Member

Choose a reason for hiding this comment

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

I see this is already making sure that there are no other statements, but we might also want to check that there's no #[cfg]'d out code in between. So e.g. I assume the following is currently a false positive:

match () {
  () if condition {
    #[cfg(...)]
    condition = true;
    if condition {
      
    }
  }
}

/// }
/// ```
#[clippy::version = "1.89.0"]
pub DUPLICATE_MATCH_GUARDS,
Copy link
Member

Choose a reason for hiding this comment

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

Couldn't this lint in principle apply to regular if conditions too? E.g. this currently has no warning

let x = 1;
if x > 1 {
  if x > 1 {
    // always executes
  } else {
    // this block of code is simply unreachable
    todo!()
  }
} else { todo!() }

I'm not saying that this PR should implement that, but it makes me wonder if we should make the lint name a bit more general, like duplicate_nested_conditions or something so it could be extended in the future without needing a name change?

@ada4a ada4a force-pushed the duplicate_match_guard branch from 3e4ace9 to e7acebc Compare October 6, 2025 22:28
@rustbot
Copy link
Collaborator

rustbot commented Oct 6, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@ada4a ada4a force-pushed the duplicate_match_guard branch from e7acebc to 568f730 Compare October 6, 2025 22:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New lint: duplicate_match_guards
3 participants