Skip to content

redundant_pattern_matching: the auto-fix for a guarded matches! drops parentheses and silently changes behavior #17286

Description

@shulaoda

Summary

redundant_pattern_matching suggests turning matches!(x, Pattern if guard) into x.is_some() && guard, but it doesn't put that replacement in parentheses. So when the matches!(...) is used somewhere that binds tighter than &&, running cargo clippy --fix breaks the code. After a ! or in a == false comparison it still compiles but now computes something different; as the receiver of a method call or in an as cast it stops compiling.

Parentheses are already added around the receiver and around the guard on their own (#11175, #13906, #17116), but never around the whole x.is_some() && guard, so the surrounding operator ends up applying to just the x.is_some() part.

Reproducer

Code:

fn f(x: Option<i32>, cond: bool) -> bool {
    !matches!(x, Some(_) if cond)
}

fn main() {
    // `matches!(Some(1), Some(_) if false)` is `false`, so this prints `true`.
    println!("{}", f(Some(1), false));
}

Running cargo clippy --fix rewrites it to:

fn f(x: Option<i32>, cond: bool) -> bool {
    !x.is_some() && cond
}

which now reads as (!x.is_some()) && cond, so f(Some(1), false) returns false instead of true. The fix was applied automatically and silently changed the result.

Current output (the help: try: text replaces the highlighted matches!(…)):

warning: redundant pattern matching, consider using `is_some()`
 --> src/main.rs:2:6
  |
2 |     !matches!(x, Some(_) if cond)
  |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_some() && cond`

Putting that back where the matches!(…) was gives !x.is_some() && cond, which is no longer the same expression.

Desired output. The whole && should be parenthesized when the surrounding expression binds tighter than &&:

  |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!(x.is_some() && cond)`

The same thing happens in every context that binds tighter than &&, e.g.:

let _ = matches!(x, Some(_) if cond) == false;   // → x.is_some() && cond == false    (still compiles, now means `&& (cond == false)`)
let _ = matches!(x, Some(_) if cond).then(|| 1);  // → x.is_some() && cond.then(|| 1)   (no longer compiles: `bool && Option<_>`)
let _ = matches!(x, Some(_) if cond) as u8;       // → x.is_some() && cond as u8        (no longer compiles: `bool && u8`)

The other way round, a plain statement, an if/while condition, a function argument, or an operand of &&/|| is fine as it is and shouldn't get extra parentheses (e.g. let _ = matches!(x, Some(_) if cond); should stay x.is_some() && cond).

Version

rustc 1.98.0-nightly (485ec3fbc 2026-06-10)
binary: rustc
commit-hash: 485ec3fbcc12fa14ef6596dabb125ad710499c9e
commit-date: 2026-06-10
host: aarch64-apple-darwin
release: 1.98.0-nightly
LLVM version: 22.1.6

Additional Labels

@rustbot label +I-suggestion-causes-error
@rustbot claim

Metadata

Metadata

Assignees

Labels

C-bugCategory: Clippy is not doing the correct thingI-suggestion-causes-errorIssue: The suggestions provided by this Lint cause an ICE/error when applied

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions