about summary refs log tree commit diff
diff options
context:
space:
mode:
authorllogiq <bogusandre@gmail.com>2025-08-06 12:45:27 +0000
committerGitHub <noreply@github.com>2025-08-06 12:45:27 +0000
commitd98d7c0ee8d213c7052e19245d2a20c1f5484872 (patch)
treeb21e997e2613bcae28afbcd7fceb126966616743
parent7673826138e7fee1534b7bbf7bf4d8651c924166 (diff)
parent58c00f3b7139a689492a48ee45b45b951aeb2427 (diff)
Fix suggestion for `collapsible_if` and `collapsible_else_if` when the inner `if` is enclosed in parentheses (#15304)
changelog: [`collapsible_else_if`]: fix suggestion when inner `if` as
wrapped in parentheses
changelog: [`collapsible_if`]: fix suggestion when inner `if` as wrapped
in parentheses

fixes https://github.com/rust-lang/rust-clippy/issues/15303

I'm sure this is a bit dirty, but don't currently see a better way.
-rw-r--r--clippy_lints/src/collapsible_if.rs53
-rw-r--r--tests/ui/collapsible_else_if.fixed22
-rw-r--r--tests/ui/collapsible_else_if.rs24
-rw-r--r--tests/ui/collapsible_else_if.stderr11
-rw-r--r--tests/ui/collapsible_if.fixed18
-rw-r--r--tests/ui/collapsible_if.rs19
-rw-r--r--tests/ui/collapsible_if.stderr20
7 files changed, 163 insertions, 4 deletions
diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs
index 1854d86c53b..e3103e2d301 100644
--- a/clippy_lints/src/collapsible_if.rs
+++ b/clippy_lints/src/collapsible_if.rs
@@ -136,6 +136,9 @@ impl CollapsibleIf {
                         return;
                     }
 
+                    // Peel off any parentheses.
+                    let (_, else_block_span, _) = peel_parens(cx.tcx.sess.source_map(), else_.span);
+
                     // Prevent "elseif"
                     // Check that the "else" is followed by whitespace
                     let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
@@ -152,7 +155,7 @@ impl CollapsibleIf {
                             if requires_space { " " } else { "" },
                             snippet_block_with_applicability(
                                 cx,
-                                else_.span,
+                                else_block_span,
                                 "..",
                                 Some(else_block.span),
                                 &mut applicability
@@ -187,7 +190,8 @@ impl CollapsibleIf {
                             .with_leading_whitespace(cx)
                             .into_span()
                     };
-                    let inner_if = inner.span.split_at(2).0;
+                    let (paren_start, inner_if_span, paren_end) = peel_parens(cx.tcx.sess.source_map(), inner.span);
+                    let inner_if = inner_if_span.split_at(2).0;
                     let mut sugg = vec![
                         // Remove the outer then block `{`
                         (then_open_bracket, String::new()),
@@ -196,6 +200,17 @@ impl CollapsibleIf {
                         // Replace inner `if` by `&&`
                         (inner_if, String::from("&&")),
                     ];
+
+                    if !paren_start.is_empty() {
+                        // Remove any leading parentheses '('
+                        sugg.push((paren_start, String::new()));
+                    }
+
+                    if !paren_end.is_empty() {
+                        // Remove any trailing parentheses ')'
+                        sugg.push((paren_end, String::new()));
+                    }
+
                     sugg.extend(parens_around(check));
                     sugg.extend(parens_around(check_inner));
 
@@ -285,3 +300,37 @@ fn span_extract_keyword(sm: &SourceMap, span: Span, keyword: &str) -> Option<Spa
         })
         .next()
 }
+
+/// Peel the parentheses from an `if` expression, e.g. `((if true {} else {}))`.
+fn peel_parens(sm: &SourceMap, mut span: Span) -> (Span, Span, Span) {
+    use crate::rustc_span::Pos;
+
+    let start = span.shrink_to_lo();
+    let end = span.shrink_to_hi();
+
+    let snippet = sm.span_to_snippet(span).unwrap();
+    if let Some((trim_start, _, trim_end)) = peel_parens_str(&snippet) {
+        let mut data = span.data();
+        data.lo = data.lo + BytePos::from_usize(trim_start);
+        data.hi = data.hi - BytePos::from_usize(trim_end);
+        span = data.span();
+    }
+
+    (start.with_hi(span.lo()), span, end.with_lo(span.hi()))
+}
+
+fn peel_parens_str(snippet: &str) -> Option<(usize, &str, usize)> {
+    let trimmed = snippet.trim();
+    if !(trimmed.starts_with('(') && trimmed.ends_with(')')) {
+        return None;
+    }
+
+    let trim_start = (snippet.len() - snippet.trim_start().len()) + 1;
+    let trim_end = (snippet.len() - snippet.trim_end().len()) + 1;
+
+    let inner = snippet.get(trim_start..snippet.len() - trim_end)?;
+    Some(match peel_parens_str(inner) {
+        None => (trim_start, inner, trim_end),
+        Some((start, inner, end)) => (trim_start + start, inner, trim_end + end),
+    })
+}
diff --git a/tests/ui/collapsible_else_if.fixed b/tests/ui/collapsible_else_if.fixed
index fed75244c6f..3d709fe9b8e 100644
--- a/tests/ui/collapsible_else_if.fixed
+++ b/tests/ui/collapsible_else_if.fixed
@@ -104,3 +104,25 @@ fn issue14799() {
         }
     }
 }
+
+fn in_parens() {
+    let x = "hello";
+    let y = "world";
+
+    if x == "hello" {
+        print!("Hello ");
+    } else if y == "world" { println!("world") } else { println!("!") }
+    //~^^^ collapsible_else_if
+}
+
+fn in_brackets() {
+    let x = "hello";
+    let y = "world";
+
+    // There is no lint when the inner `if` is in a block.
+    if x == "hello" {
+        print!("Hello ");
+    } else {
+        { if y == "world" { println!("world") } else { println!("!") } }
+    }
+}
diff --git a/tests/ui/collapsible_else_if.rs b/tests/ui/collapsible_else_if.rs
index e50e781fb69..51868e03908 100644
--- a/tests/ui/collapsible_else_if.rs
+++ b/tests/ui/collapsible_else_if.rs
@@ -120,3 +120,27 @@ fn issue14799() {
         }
     }
 }
+
+fn in_parens() {
+    let x = "hello";
+    let y = "world";
+
+    if x == "hello" {
+        print!("Hello ");
+    } else {
+        (if y == "world" { println!("world") } else { println!("!") })
+    }
+    //~^^^ collapsible_else_if
+}
+
+fn in_brackets() {
+    let x = "hello";
+    let y = "world";
+
+    // There is no lint when the inner `if` is in a block.
+    if x == "hello" {
+        print!("Hello ");
+    } else {
+        { if y == "world" { println!("world") } else { println!("!") } }
+    }
+}
diff --git a/tests/ui/collapsible_else_if.stderr b/tests/ui/collapsible_else_if.stderr
index 7d80894cadb..1a7bcec7fd5 100644
--- a/tests/ui/collapsible_else_if.stderr
+++ b/tests/ui/collapsible_else_if.stderr
@@ -150,5 +150,14 @@ LL | |         if false {}
 LL | |     }
    | |_____^ help: collapse nested if block: `if false {}`
 
-error: aborting due to 8 previous errors
+error: this `else { if .. }` block can be collapsed
+  --> tests/ui/collapsible_else_if.rs:130:12
+   |
+LL |       } else {
+   |  ____________^
+LL | |         (if y == "world" { println!("world") } else { println!("!") })
+LL | |     }
+   | |_____^ help: collapse nested if block: `if y == "world" { println!("world") } else { println!("!") }`
+
+error: aborting due to 9 previous errors
 
diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed
index 77bc791ea8e..78354c2d7cf 100644
--- a/tests/ui/collapsible_if.fixed
+++ b/tests/ui/collapsible_if.fixed
@@ -163,3 +163,21 @@ fn issue14799() {
         if true {}
     };
 }
+
+fn in_parens() {
+    if true
+        && true {
+            println!("In parens, linted");
+        }
+    //~^^^^^ collapsible_if
+}
+
+fn in_brackets() {
+    if true {
+        {
+            if true {
+                println!("In brackets, not linted");
+            }
+        }
+    }
+}
diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs
index d30df157d5e..5d9afa10956 100644
--- a/tests/ui/collapsible_if.rs
+++ b/tests/ui/collapsible_if.rs
@@ -173,3 +173,22 @@ fn issue14799() {
         if true {}
     };
 }
+
+fn in_parens() {
+    if true {
+        (if true {
+            println!("In parens, linted");
+        })
+    }
+    //~^^^^^ collapsible_if
+}
+
+fn in_brackets() {
+    if true {
+        {
+            if true {
+                println!("In brackets, not linted");
+            }
+        }
+    }
+}
diff --git a/tests/ui/collapsible_if.stderr b/tests/ui/collapsible_if.stderr
index 32c6b019403..a685cc2e929 100644
--- a/tests/ui/collapsible_if.stderr
+++ b/tests/ui/collapsible_if.stderr
@@ -190,5 +190,23 @@ LL |         // This is a comment, do not collapse code to it
 LL ~     ; 3
    |
 
-error: aborting due to 11 previous errors
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if.rs:178:5
+   |
+LL | /     if true {
+LL | |         (if true {
+LL | |             println!("In parens, linted");
+LL | |         })
+LL | |     }
+   | |_____^
+   |
+help: collapse nested if block
+   |
+LL ~     if true
+LL ~         && true {
+LL |             println!("In parens, linted");
+LL ~         }
+   |
+
+error: aborting due to 12 previous errors