Skip to content

Commit d98d7c0

Browse files
authored
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 #15303 I'm sure this is a bit dirty, but don't currently see a better way.
2 parents 7673826 + 58c00f3 commit d98d7c0

File tree

7 files changed

+163
-4
lines changed

7 files changed

+163
-4
lines changed

clippy_lints/src/collapsible_if.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ impl CollapsibleIf {
136136
return;
137137
}
138138

139+
// Peel off any parentheses.
140+
let (_, else_block_span, _) = peel_parens(cx.tcx.sess.source_map(), else_.span);
141+
139142
// Prevent "elseif"
140143
// Check that the "else" is followed by whitespace
141144
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
@@ -152,7 +155,7 @@ impl CollapsibleIf {
152155
if requires_space { " " } else { "" },
153156
snippet_block_with_applicability(
154157
cx,
155-
else_.span,
158+
else_block_span,
156159
"..",
157160
Some(else_block.span),
158161
&mut applicability
@@ -187,7 +190,8 @@ impl CollapsibleIf {
187190
.with_leading_whitespace(cx)
188191
.into_span()
189192
};
190-
let inner_if = inner.span.split_at(2).0;
193+
let (paren_start, inner_if_span, paren_end) = peel_parens(cx.tcx.sess.source_map(), inner.span);
194+
let inner_if = inner_if_span.split_at(2).0;
191195
let mut sugg = vec![
192196
// Remove the outer then block `{`
193197
(then_open_bracket, String::new()),
@@ -196,6 +200,17 @@ impl CollapsibleIf {
196200
// Replace inner `if` by `&&`
197201
(inner_if, String::from("&&")),
198202
];
203+
204+
if !paren_start.is_empty() {
205+
// Remove any leading parentheses '('
206+
sugg.push((paren_start, String::new()));
207+
}
208+
209+
if !paren_end.is_empty() {
210+
// Remove any trailing parentheses ')'
211+
sugg.push((paren_end, String::new()));
212+
}
213+
199214
sugg.extend(parens_around(check));
200215
sugg.extend(parens_around(check_inner));
201216

@@ -285,3 +300,37 @@ fn span_extract_keyword(sm: &SourceMap, span: Span, keyword: &str) -> Option<Spa
285300
})
286301
.next()
287302
}
303+
304+
/// Peel the parentheses from an `if` expression, e.g. `((if true {} else {}))`.
305+
fn peel_parens(sm: &SourceMap, mut span: Span) -> (Span, Span, Span) {
306+
use crate::rustc_span::Pos;
307+
308+
let start = span.shrink_to_lo();
309+
let end = span.shrink_to_hi();
310+
311+
let snippet = sm.span_to_snippet(span).unwrap();
312+
if let Some((trim_start, _, trim_end)) = peel_parens_str(&snippet) {
313+
let mut data = span.data();
314+
data.lo = data.lo + BytePos::from_usize(trim_start);
315+
data.hi = data.hi - BytePos::from_usize(trim_end);
316+
span = data.span();
317+
}
318+
319+
(start.with_hi(span.lo()), span, end.with_lo(span.hi()))
320+
}
321+
322+
fn peel_parens_str(snippet: &str) -> Option<(usize, &str, usize)> {
323+
let trimmed = snippet.trim();
324+
if !(trimmed.starts_with('(') && trimmed.ends_with(')')) {
325+
return None;
326+
}
327+
328+
let trim_start = (snippet.len() - snippet.trim_start().len()) + 1;
329+
let trim_end = (snippet.len() - snippet.trim_end().len()) + 1;
330+
331+
let inner = snippet.get(trim_start..snippet.len() - trim_end)?;
332+
Some(match peel_parens_str(inner) {
333+
None => (trim_start, inner, trim_end),
334+
Some((start, inner, end)) => (trim_start + start, inner, trim_end + end),
335+
})
336+
}

tests/ui/collapsible_else_if.fixed

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,25 @@ fn issue14799() {
104104
}
105105
}
106106
}
107+
108+
fn in_parens() {
109+
let x = "hello";
110+
let y = "world";
111+
112+
if x == "hello" {
113+
print!("Hello ");
114+
} else if y == "world" { println!("world") } else { println!("!") }
115+
//~^^^ collapsible_else_if
116+
}
117+
118+
fn in_brackets() {
119+
let x = "hello";
120+
let y = "world";
121+
122+
// There is no lint when the inner `if` is in a block.
123+
if x == "hello" {
124+
print!("Hello ");
125+
} else {
126+
{ if y == "world" { println!("world") } else { println!("!") } }
127+
}
128+
}

tests/ui/collapsible_else_if.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,27 @@ fn issue14799() {
120120
}
121121
}
122122
}
123+
124+
fn in_parens() {
125+
let x = "hello";
126+
let y = "world";
127+
128+
if x == "hello" {
129+
print!("Hello ");
130+
} else {
131+
(if y == "world" { println!("world") } else { println!("!") })
132+
}
133+
//~^^^ collapsible_else_if
134+
}
135+
136+
fn in_brackets() {
137+
let x = "hello";
138+
let y = "world";
139+
140+
// There is no lint when the inner `if` is in a block.
141+
if x == "hello" {
142+
print!("Hello ");
143+
} else {
144+
{ if y == "world" { println!("world") } else { println!("!") } }
145+
}
146+
}

tests/ui/collapsible_else_if.stderr

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,14 @@ LL | | if false {}
150150
LL | | }
151151
| |_____^ help: collapse nested if block: `if false {}`
152152

153-
error: aborting due to 8 previous errors
153+
error: this `else { if .. }` block can be collapsed
154+
--> tests/ui/collapsible_else_if.rs:130:12
155+
|
156+
LL | } else {
157+
| ____________^
158+
LL | | (if y == "world" { println!("world") } else { println!("!") })
159+
LL | | }
160+
| |_____^ help: collapse nested if block: `if y == "world" { println!("world") } else { println!("!") }`
161+
162+
error: aborting due to 9 previous errors
154163

tests/ui/collapsible_if.fixed

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,21 @@ fn issue14799() {
163163
if true {}
164164
};
165165
}
166+
167+
fn in_parens() {
168+
if true
169+
&& true {
170+
println!("In parens, linted");
171+
}
172+
//~^^^^^ collapsible_if
173+
}
174+
175+
fn in_brackets() {
176+
if true {
177+
{
178+
if true {
179+
println!("In brackets, not linted");
180+
}
181+
}
182+
}
183+
}

tests/ui/collapsible_if.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,22 @@ fn issue14799() {
173173
if true {}
174174
};
175175
}
176+
177+
fn in_parens() {
178+
if true {
179+
(if true {
180+
println!("In parens, linted");
181+
})
182+
}
183+
//~^^^^^ collapsible_if
184+
}
185+
186+
fn in_brackets() {
187+
if true {
188+
{
189+
if true {
190+
println!("In brackets, not linted");
191+
}
192+
}
193+
}
194+
}

tests/ui/collapsible_if.stderr

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,23 @@ LL | // This is a comment, do not collapse code to it
190190
LL ~ ; 3
191191
|
192192

193-
error: aborting due to 11 previous errors
193+
error: this `if` statement can be collapsed
194+
--> tests/ui/collapsible_if.rs:178:5
195+
|
196+
LL | / if true {
197+
LL | | (if true {
198+
LL | | println!("In parens, linted");
199+
LL | | })
200+
LL | | }
201+
| |_____^
202+
|
203+
help: collapse nested if block
204+
|
205+
LL ~ if true
206+
LL ~ && true {
207+
LL | println!("In parens, linted");
208+
LL ~ }
209+
|
210+
211+
error: aborting due to 12 previous errors
194212

0 commit comments

Comments
 (0)