diff options
| author | llogiq <bogusandre@gmail.com> | 2025-08-06 12:45:27 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-06 12:45:27 +0000 |
| commit | d98d7c0ee8d213c7052e19245d2a20c1f5484872 (patch) | |
| tree | b21e997e2613bcae28afbcd7fceb126966616743 | |
| parent | 7673826138e7fee1534b7bbf7bf4d8651c924166 (diff) | |
| parent | 58c00f3b7139a689492a48ee45b45b951aeb2427 (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.rs | 53 | ||||
| -rw-r--r-- | tests/ui/collapsible_else_if.fixed | 22 | ||||
| -rw-r--r-- | tests/ui/collapsible_else_if.rs | 24 | ||||
| -rw-r--r-- | tests/ui/collapsible_else_if.stderr | 11 | ||||
| -rw-r--r-- | tests/ui/collapsible_if.fixed | 18 | ||||
| -rw-r--r-- | tests/ui/collapsible_if.rs | 19 | ||||
| -rw-r--r-- | tests/ui/collapsible_if.stderr | 20 |
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 |
