ide_diagnostics/handlers/
remove_unnecessary_else.rs

1use hir::{db::ExpandDatabase, diagnostics::RemoveUnnecessaryElse};
2use ide_db::text_edit::TextEdit;
3use ide_db::{assists::Assist, source_change::SourceChange};
4use itertools::Itertools;
5use syntax::{
6    AstNode, SyntaxToken, TextRange,
7    ast::{
8        self,
9        edit::{AstNodeEdit, IndentLevel},
10    },
11};
12
13use crate::{
14    Diagnostic, DiagnosticCode, DiagnosticsContext, Severity, adjusted_display_range, fix,
15};
16
17// Diagnostic: remove-unnecessary-else
18//
19// This diagnostic is triggered when there is an `else` block for an `if` expression whose
20// then branch diverges (e.g. ends with a `return`, `continue`, `break` e.t.c).
21pub(crate) fn remove_unnecessary_else(
22    ctx: &DiagnosticsContext<'_>,
23    d: &RemoveUnnecessaryElse,
24) -> Option<Diagnostic> {
25    if d.if_expr.file_id.macro_file().is_some() {
26        // FIXME: Our infra can't handle allow from within macro expansions rn
27        return None;
28    }
29
30    let display_range = adjusted_display_range(ctx, d.if_expr, &|if_expr| {
31        if_expr.else_token().as_ref().map(SyntaxToken::text_range)
32    });
33    Some(
34        Diagnostic::new(
35            DiagnosticCode::Ra("remove-unnecessary-else", Severity::WeakWarning),
36            "remove unnecessary else block",
37            display_range,
38        )
39        .with_fixes(fixes(ctx, d)),
40    )
41}
42
43fn fixes(ctx: &DiagnosticsContext<'_>, d: &RemoveUnnecessaryElse) -> Option<Vec<Assist>> {
44    let root = ctx.sema.db.parse_or_expand(d.if_expr.file_id);
45    let if_expr = d.if_expr.value.to_node(&root);
46    let if_expr = ctx.sema.original_ast_node(if_expr)?;
47
48    let mut indent = IndentLevel::from_node(if_expr.syntax());
49    let has_parent_if_expr = if_expr.syntax().parent().and_then(ast::IfExpr::cast).is_some();
50    if has_parent_if_expr {
51        indent = indent + 1;
52    }
53    let else_replacement = match if_expr.else_branch()? {
54        ast::ElseBranch::Block(block) => block
55            .statements()
56            .map(|stmt| format!("\n{indent}{stmt}"))
57            .chain(block.tail_expr().map(|tail| format!("\n{indent}{tail}")))
58            .join(""),
59        ast::ElseBranch::IfExpr(mut nested_if_expr) => {
60            if has_parent_if_expr {
61                nested_if_expr = nested_if_expr.indent(IndentLevel(1))
62            }
63            format!("\n{indent}{nested_if_expr}")
64        }
65    };
66    let (replacement, range) = if has_parent_if_expr {
67        let base_indent = IndentLevel::from_node(if_expr.syntax());
68        let then_indent = base_indent + 1;
69        let then_child_indent = then_indent + 1;
70
71        let condition = if_expr.condition()?;
72        let then_stmts = if_expr
73            .then_branch()?
74            .statements()
75            .map(|stmt| format!("\n{then_child_indent}{stmt}"))
76            .join("");
77        let then_replacement =
78            format!("\n{then_indent}if {condition} {{{then_stmts}\n{then_indent}}}",);
79        let replacement = format!("{{{then_replacement}{else_replacement}\n{base_indent}}}");
80        (replacement, if_expr.syntax().text_range())
81    } else {
82        (
83            else_replacement,
84            TextRange::new(
85                if_expr.then_branch()?.syntax().text_range().end(),
86                if_expr.syntax().text_range().end(),
87            ),
88        )
89    };
90
91    let edit = TextEdit::replace(range, replacement);
92    let source_change = SourceChange::from_text_edit(
93        d.if_expr.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db),
94        edit,
95    );
96
97    Some(vec![fix(
98        "remove_unnecessary_else",
99        "Remove unnecessary else block",
100        source_change,
101        range,
102    )])
103}
104
105#[cfg(test)]
106mod tests {
107    use crate::tests::{check_diagnostics_with_disabled, check_fix};
108
109    #[test]
110    fn remove_unnecessary_else_for_return() {
111        check_diagnostics_with_disabled(
112            r#"
113fn test() {
114    if foo {
115        return bar;
116    } else {
117    //^^^^ 💡 weak: remove unnecessary else block
118        do_something_else();
119    }
120}
121"#,
122            &["needless_return", "E0425"],
123        );
124        check_fix(
125            r#"
126fn test() {
127    if foo {
128        return bar;
129    } else$0 {
130        do_something_else();
131    }
132}
133"#,
134            r#"
135fn test() {
136    if foo {
137        return bar;
138    }
139    do_something_else();
140}
141"#,
142        );
143    }
144
145    #[test]
146    fn remove_unnecessary_else_for_return2() {
147        check_diagnostics_with_disabled(
148            r#"
149fn test() {
150    if foo {
151        return bar;
152    } else if qux {
153    //^^^^ 💡 weak: remove unnecessary else block
154        do_something_else();
155    } else {
156        do_something_else2();
157    }
158}
159"#,
160            &["needless_return", "E0425"],
161        );
162        check_fix(
163            r#"
164fn test() {
165    if foo {
166        return bar;
167    } else$0 if qux {
168        do_something_else();
169    } else {
170        do_something_else2();
171    }
172}
173"#,
174            r#"
175fn test() {
176    if foo {
177        return bar;
178    }
179    if qux {
180        do_something_else();
181    } else {
182        do_something_else2();
183    }
184}
185"#,
186        );
187    }
188
189    #[test]
190    fn remove_unnecessary_else_for_return3() {
191        check_diagnostics_with_disabled(
192            r#"
193fn test(a: bool) -> i32 {
194    if a {
195        return 1;
196    } else {
197    //^^^^ 💡 weak: remove unnecessary else block
198        0
199    }
200}
201"#,
202            &["needless_return", "E0425"],
203        );
204        check_fix(
205            r#"
206fn test(a: bool) -> i32 {
207    if a {
208        return 1;
209    } else$0 {
210        0
211    }
212}
213"#,
214            r#"
215fn test(a: bool) -> i32 {
216    if a {
217        return 1;
218    }
219    0
220}
221"#,
222        );
223    }
224
225    #[test]
226    fn remove_unnecessary_else_for_return_in_child_if_expr() {
227        check_diagnostics_with_disabled(
228            r#"
229fn test() {
230    if foo {
231        do_something();
232    } else if qux {
233        return bar;
234    } else {
235    //^^^^ 💡 weak: remove unnecessary else block
236        do_something_else();
237    }
238}
239"#,
240            &["needless_return", "E0425"],
241        );
242        check_fix(
243            r#"
244fn test() {
245    if foo {
246        do_something();
247    } else if qux {
248        return bar;
249    } else$0 {
250        do_something_else();
251    }
252}
253"#,
254            r#"
255fn test() {
256    if foo {
257        do_something();
258    } else {
259        if qux {
260            return bar;
261        }
262        do_something_else();
263    }
264}
265"#,
266        );
267    }
268
269    #[test]
270    fn remove_unnecessary_else_for_return_in_child_if_expr2() {
271        check_fix(
272            r#"
273fn test() {
274    if foo {
275        do_something();
276    } else if qux {
277        return bar;
278    } else$0 if quux {
279        do_something_else();
280    } else {
281        do_something_else2();
282    }
283}
284"#,
285            r#"
286fn test() {
287    if foo {
288        do_something();
289    } else {
290        if qux {
291            return bar;
292        }
293        if quux {
294            do_something_else();
295        } else {
296            do_something_else2();
297        }
298    }
299}
300"#,
301        );
302    }
303
304    #[test]
305    fn remove_unnecessary_else_for_break() {
306        check_diagnostics_with_disabled(
307            r#"
308fn test() {
309    loop {
310        if foo {
311            break;
312        } else {
313        //^^^^ 💡 weak: remove unnecessary else block
314            do_something_else();
315        }
316    }
317}
318"#,
319            &["E0425"],
320        );
321        check_fix(
322            r#"
323fn test() {
324    loop {
325        if foo {
326            break;
327        } else$0 {
328            do_something_else();
329        }
330    }
331}
332"#,
333            r#"
334fn test() {
335    loop {
336        if foo {
337            break;
338        }
339        do_something_else();
340    }
341}
342"#,
343        );
344    }
345
346    #[test]
347    fn remove_unnecessary_else_for_continue() {
348        check_diagnostics_with_disabled(
349            r#"
350fn test() {
351    loop {
352        if foo {
353            continue;
354        } else {
355        //^^^^ 💡 weak: remove unnecessary else block
356            do_something_else();
357        }
358    }
359}
360"#,
361            &["E0425"],
362        );
363        check_fix(
364            r#"
365fn test() {
366    loop {
367        if foo {
368            continue;
369        } else$0 {
370            do_something_else();
371        }
372    }
373}
374"#,
375            r#"
376fn test() {
377    loop {
378        if foo {
379            continue;
380        }
381        do_something_else();
382    }
383}
384"#,
385        );
386    }
387
388    #[test]
389    fn remove_unnecessary_else_for_never() {
390        check_diagnostics_with_disabled(
391            r#"
392fn test() {
393    if foo {
394        never();
395    } else {
396    //^^^^ 💡 weak: remove unnecessary else block
397        do_something_else();
398    }
399}
400
401fn never() -> ! {
402    loop {}
403}
404"#,
405            &["E0425"],
406        );
407        check_fix(
408            r#"
409fn test() {
410    if foo {
411        never();
412    } else$0 {
413        do_something_else();
414    }
415}
416
417fn never() -> ! {
418    loop {}
419}
420"#,
421            r#"
422fn test() {
423    if foo {
424        never();
425    }
426    do_something_else();
427}
428
429fn never() -> ! {
430    loop {}
431}
432"#,
433        );
434    }
435
436    #[test]
437    fn no_diagnostic_if_no_else_branch() {
438        check_diagnostics_with_disabled(
439            r#"
440fn test() {
441    if foo {
442        return bar;
443    }
444
445    do_something_else();
446}
447"#,
448            &["E0425"],
449        );
450    }
451
452    #[test]
453    fn no_diagnostic_if_no_divergence() {
454        check_diagnostics_with_disabled(
455            r#"
456fn test() {
457    if foo {
458        do_something();
459    } else {
460        do_something_else();
461    }
462}
463"#,
464            &["E0425"],
465        );
466    }
467
468    #[test]
469    fn no_diagnostic_if_no_divergence_in_else_branch() {
470        check_diagnostics_with_disabled(
471            r#"
472fn test() {
473    if foo {
474        do_something();
475    } else {
476        return bar;
477    }
478}
479"#,
480            &["needless_return", "E0425"],
481        );
482    }
483
484    #[test]
485    fn no_diagnostic_if_not_expr_stmt() {
486        check_diagnostics_with_disabled(
487            r#"
488fn test1() {
489    let _x = if a {
490        return;
491    } else {
492        1
493    };
494}
495
496fn test2() {
497    let _x = if a {
498        return;
499    } else if b {
500        return;
501    } else if c {
502        1
503    } else {
504        return;
505    };
506}
507"#,
508            &["needless_return", "E0425"],
509        );
510        check_diagnostics_with_disabled(
511            r#"
512fn test3() -> u8 {
513    foo(if a { return 1 } else { 0 })
514}
515"#,
516            &["E0425"],
517        );
518    }
519}