Skip to main content

ide_diagnostics/handlers/
remove_trailing_return.rs

1use hir::{FileRange, db::ExpandDatabase, diagnostics::RemoveTrailingReturn};
2use ide_db::text_edit::TextEdit;
3use ide_db::{assists::Assist, source_change::SourceChange};
4use syntax::{AstNode, ast};
5
6use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range, fix};
7
8// Diagnostic: remove-trailing-return
9//
10// This diagnostic is triggered when there is a redundant `return` at the end of a function
11// or closure.
12pub(crate) fn remove_trailing_return(
13    ctx: &DiagnosticsContext<'_, '_>,
14    d: &RemoveTrailingReturn,
15) -> Option<Diagnostic> {
16    if d.return_expr.file_id.macro_file().is_some() {
17        // FIXME: Our infra can't handle allow from within macro expansions rn
18        return None;
19    }
20
21    let display_range = adjusted_display_range(ctx, d.return_expr, &|return_expr| {
22        return_expr
23            .syntax()
24            .parent()
25            .and_then(ast::ExprStmt::cast)
26            .map(|stmt| stmt.syntax().text_range())
27    });
28    Some(
29        Diagnostic::new(
30            DiagnosticCode::Clippy("needless_return"),
31            "replace return <expr>; with <expr>",
32            display_range,
33        )
34        .stable()
35        .with_fixes(fixes(ctx, d)),
36    )
37}
38
39fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &RemoveTrailingReturn) -> Option<Vec<Assist>> {
40    let root = ctx.sema.db.parse_or_expand(d.return_expr.file_id);
41    let return_expr = d.return_expr.value.to_node(&root);
42    let stmt = return_expr.syntax().parent().and_then(ast::ExprStmt::cast);
43
44    let FileRange { range, file_id } =
45        ctx.sema.original_range_opt(stmt.as_ref().map_or(return_expr.syntax(), AstNode::syntax))?;
46    if Some(file_id) != d.return_expr.file_id.file_id() {
47        return None;
48    }
49
50    let replacement =
51        return_expr.expr().map_or_else(String::new, |expr| format!("{}", expr.syntax().text()));
52    let edit = TextEdit::replace(range, replacement);
53    let source_change = SourceChange::from_text_edit(file_id.file_id(ctx.sema.db), edit);
54
55    Some(vec![fix(
56        "remove_trailing_return",
57        "Replace return <expr>; with <expr>",
58        source_change,
59        range,
60    )])
61}
62
63#[cfg(test)]
64mod tests {
65    use crate::tests::{
66        check_diagnostics, check_diagnostics_with_disabled, check_fix, check_fix_with_disabled,
67    };
68
69    #[test]
70    fn remove_trailing_return() {
71        check_diagnostics(
72            r#"
73fn foo() -> u8 {
74    return 2;
75} //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
76"#,
77        );
78    }
79
80    #[test]
81    fn remove_trailing_return_inner_function() {
82        check_diagnostics(
83            r#"
84fn foo() -> u8 {
85    fn bar() -> u8 {
86        return 2;
87    } //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
88    bar()
89}
90"#,
91        );
92    }
93
94    #[test]
95    fn remove_trailing_return_closure() {
96        check_diagnostics(
97            r#"
98//- minicore: fn
99fn foo() -> u8 {
100    let bar = || return 2;
101    bar()      //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
102}
103"#,
104        );
105        check_diagnostics(
106            r#"
107//- minicore: fn
108fn foo() -> u8 {
109    let bar = || {
110        return 2;
111    };//^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
112    bar()
113}
114"#,
115        );
116    }
117
118    #[test]
119    fn remove_trailing_return_unit() {
120        check_diagnostics(
121            r#"
122fn foo() {
123    return
124} //^^^^^^ 💡 weak: replace return <expr>; with <expr>
125"#,
126        );
127    }
128
129    #[test]
130    fn remove_trailing_return_no_semi() {
131        check_diagnostics(
132            r#"
133fn foo() -> u8 {
134    return 2
135} //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
136"#,
137        );
138    }
139
140    #[test]
141    fn remove_trailing_return_in_if() {
142        check_diagnostics_with_disabled(
143            r#"
144fn foo(x: usize) -> u8 {
145    if x > 0 {
146        return 1;
147      //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
148    } else {
149        return 0;
150    } //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
151}
152"#,
153            &["remove-unnecessary-else"],
154        );
155    }
156
157    #[test]
158    fn remove_trailing_return_in_match() {
159        check_diagnostics(
160            r#"
161fn foo<T, E>(x: Result<T, E>) -> u8 {
162    match x {
163        Ok(_) => return 1,
164               //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
165        Err(_) => return 0,
166    }           //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
167}
168"#,
169        );
170    }
171
172    #[test]
173    fn no_diagnostic_if_no_return_keyword() {
174        check_diagnostics(
175            r#"
176fn foo() -> u8 {
177    3
178}
179"#,
180        );
181    }
182
183    #[test]
184    fn no_diagnostic_if_not_last_statement() {
185        check_diagnostics(
186            r#"
187fn foo() -> u8 {
188    if true { return 2; }
189    3
190}
191"#,
192        );
193    }
194
195    #[test]
196    fn no_diagnostic_if_not_last_statement2() {
197        check_diagnostics(
198            r#"
199fn foo() -> u8 {
200    return 2;
201    fn bar() {}
202}
203"#,
204        );
205    }
206
207    #[test]
208    fn replace_with_expr() {
209        check_fix(
210            r#"
211fn foo() -> u8 {
212    return$0 2;
213}
214"#,
215            r#"
216fn foo() -> u8 {
217    2
218}
219"#,
220        );
221    }
222
223    #[test]
224    fn replace_with_unit() {
225        check_fix(
226            r#"
227fn foo() {
228    return$0/*ensure tidy is happy*/
229}
230"#,
231            r#"
232fn foo() {
233    /*ensure tidy is happy*/
234}
235"#,
236        );
237    }
238
239    #[test]
240    fn replace_with_expr_no_semi() {
241        check_fix(
242            r#"
243fn foo() -> u8 {
244    return$0 2
245}
246"#,
247            r#"
248fn foo() -> u8 {
249    2
250}
251"#,
252        );
253    }
254
255    #[test]
256    fn replace_in_inner_function() {
257        check_fix(
258            r#"
259fn foo() -> u8 {
260    fn bar() -> u8 {
261        return$0 2;
262    }
263    bar()
264}
265"#,
266            r#"
267fn foo() -> u8 {
268    fn bar() -> u8 {
269        2
270    }
271    bar()
272}
273"#,
274        );
275    }
276
277    #[test]
278    fn replace_in_closure() {
279        check_fix(
280            r#"
281//- minicore: fn
282fn foo() -> u8 {
283    let bar = || return$0 2;
284    bar()
285}
286"#,
287            r#"
288fn foo() -> u8 {
289    let bar = || 2;
290    bar()
291}
292"#,
293        );
294        check_fix(
295            r#"
296//- minicore: fn
297fn foo() -> u8 {
298    let bar = || {
299        return$0 2;
300    };
301    bar()
302}
303"#,
304            r#"
305fn foo() -> u8 {
306    let bar = || {
307        2
308    };
309    bar()
310}
311"#,
312        );
313    }
314
315    #[test]
316    fn replace_in_if() {
317        check_fix_with_disabled(
318            r#"
319fn foo(x: usize) -> u8 {
320    if x > 0 {
321        return$0 1;
322    } else {
323        0
324    }
325}
326"#,
327            r#"
328fn foo(x: usize) -> u8 {
329    if x > 0 {
330        1
331    } else {
332        0
333    }
334}
335"#,
336            &["remove-unnecessary-else"],
337        );
338        check_fix(
339            r#"
340fn foo(x: usize) -> u8 {
341    if x > 0 {
342        1
343    } else {
344        return$0 0;
345    }
346}
347"#,
348            r#"
349fn foo(x: usize) -> u8 {
350    if x > 0 {
351        1
352    } else {
353        0
354    }
355}
356"#,
357        );
358    }
359
360    #[test]
361    fn replace_in_match() {
362        check_fix(
363            r#"
364fn foo<T, E>(x: Result<T, E>) -> u8 {
365    match x {
366        Ok(_) => return$0 1,
367        Err(_) => 0,
368    }
369}
370"#,
371            r#"
372fn foo<T, E>(x: Result<T, E>) -> u8 {
373    match x {
374        Ok(_) => 1,
375        Err(_) => 0,
376    }
377}
378"#,
379        );
380        check_fix(
381            r#"
382fn foo<T, E>(x: Result<T, E>) -> u8 {
383    match x {
384        Ok(_) => 1,
385        Err(_) => return$0 0,
386    }
387}
388"#,
389            r#"
390fn foo<T, E>(x: Result<T, E>) -> u8 {
391    match x {
392        Ok(_) => 1,
393        Err(_) => 0,
394    }
395}
396"#,
397        );
398    }
399}