Skip to content

Commit 2987fac

Browse files
committed
diagnostic to remove trailing return
1 parent e071834 commit 2987fac

File tree

5 files changed

+318
-2
lines changed

5 files changed

+318
-2
lines changed

crates/hir-ty/src/diagnostics/expr.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub enum BodyValidationDiagnostic {
4444
match_expr: ExprId,
4545
uncovered_patterns: String,
4646
},
47+
RemoveTrailingReturn {
48+
return_expr: ExprId,
49+
},
4750
RemoveUnnecessaryElse {
4851
if_expr: ExprId,
4952
},
@@ -75,6 +78,10 @@ impl ExprValidator {
7578
let body = db.body(self.owner);
7679
let mut filter_map_next_checker = None;
7780

81+
if matches!(self.owner, DefWithBodyId::FunctionId(_)) {
82+
self.check_for_trailing_return(body.body_expr, &body);
83+
}
84+
7885
for (id, expr) in body.exprs.iter() {
7986
if let Some((variant, missed_fields, true)) =
8087
record_literal_missing_fields(db, &self.infer, id, expr)
@@ -93,12 +100,16 @@ impl ExprValidator {
93100
Expr::Call { .. } | Expr::MethodCall { .. } => {
94101
self.validate_call(db, id, expr, &mut filter_map_next_checker);
95102
}
103+
Expr::Closure { body: body_expr, .. } => {
104+
self.check_for_trailing_return(*body_expr, &body);
105+
}
96106
Expr::If { .. } => {
97107
self.check_for_unnecessary_else(id, expr, &body);
98108
}
99109
_ => {}
100110
}
101111
}
112+
102113
for (id, pat) in body.pats.iter() {
103114
if let Some((variant, missed_fields, true)) =
104115
record_pattern_missing_fields(db, &self.infer, id, pat)
@@ -244,6 +255,26 @@ impl ExprValidator {
244255
pattern
245256
}
246257

258+
fn check_for_trailing_return(&mut self, body_expr: ExprId, body: &Body) {
259+
match &body.exprs[body_expr] {
260+
Expr::Block { statements, tail, .. } => {
261+
let last_stmt = tail.or_else(|| match statements.last()? {
262+
Statement::Expr { expr, .. } => Some(*expr),
263+
_ => None,
264+
});
265+
if let Some(last_stmt) = last_stmt {
266+
self.check_for_trailing_return(last_stmt, body);
267+
}
268+
}
269+
Expr::Return { .. } => {
270+
self.diagnostics.push(BodyValidationDiagnostic::RemoveTrailingReturn {
271+
return_expr: body_expr,
272+
});
273+
}
274+
_ => (),
275+
}
276+
}
277+
247278
fn check_for_unnecessary_else(&mut self, id: ExprId, expr: &Expr, body: &Body) {
248279
if let Expr::If { condition: _, then_branch, else_branch } = expr {
249280
if else_branch.is_none() {

crates/hir/src/diagnostics.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ diagnostics![
6868
PrivateAssocItem,
6969
PrivateField,
7070
ReplaceFilterMapNextWithFindMap,
71+
RemoveTrailingReturn,
7172
RemoveUnnecessaryElse,
7273
TraitImplIncorrectSafety,
7374
TraitImplMissingAssocItems,
@@ -343,6 +344,12 @@ pub struct TraitImplRedundantAssocItems {
343344
pub assoc_item: (Name, AssocItem),
344345
}
345346

347+
#[derive(Debug)]
348+
pub struct RemoveTrailingReturn {
349+
pub file_id: HirFileId,
350+
pub return_expr: AstPtr<ast::Expr>,
351+
}
352+
346353
#[derive(Debug)]
347354
pub struct RemoveUnnecessaryElse {
348355
pub if_expr: InFile<AstPtr<ast::IfExpr>>,
@@ -450,6 +457,17 @@ impl AnyDiagnostic {
450457
Err(SyntheticSyntax) => (),
451458
}
452459
}
460+
BodyValidationDiagnostic::RemoveTrailingReturn { return_expr } => {
461+
if let Ok(source_ptr) = source_map.expr_syntax(return_expr) {
462+
return Some(
463+
RemoveTrailingReturn {
464+
file_id: source_ptr.file_id,
465+
return_expr: source_ptr.value,
466+
}
467+
.into(),
468+
);
469+
}
470+
}
453471
BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr } => {
454472
if let Ok(source_ptr) = source_map.expr_syntax(if_expr) {
455473
if let Some(ptr) = source_ptr.value.cast::<ast::IfExpr>() {
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
use hir::{db::ExpandDatabase, diagnostics::RemoveTrailingReturn, HirFileIdExt, InFile};
2+
use ide_db::{assists::Assist, source_change::SourceChange};
3+
use syntax::{ast, AstNode, SyntaxNodePtr};
4+
use text_edit::TextEdit;
5+
6+
use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
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.
12+
pub(crate) fn remove_trailing_return(
13+
ctx: &DiagnosticsContext<'_>,
14+
d: &RemoveTrailingReturn,
15+
) -> Diagnostic {
16+
let display_range = ctx.sema.diagnostics_display_range(InFile {
17+
file_id: d.file_id,
18+
value: expr_stmt(ctx, d)
19+
.as_ref()
20+
.map(|stmt| SyntaxNodePtr::new(stmt.syntax()))
21+
.unwrap_or_else(|| d.return_expr.into()),
22+
});
23+
Diagnostic::new(
24+
DiagnosticCode::Clippy("needless_return"),
25+
"replace return <expr>; with <expr>",
26+
display_range,
27+
)
28+
.with_fixes(fixes(ctx, d))
29+
}
30+
31+
fn fixes(ctx: &DiagnosticsContext<'_>, d: &RemoveTrailingReturn) -> Option<Vec<Assist>> {
32+
let return_expr = return_expr(ctx, d)?;
33+
let stmt = expr_stmt(ctx, d);
34+
35+
let range = stmt.as_ref().map_or(return_expr.syntax(), AstNode::syntax).text_range();
36+
let replacement =
37+
return_expr.expr().map_or_else(String::new, |expr| format!("{}", expr.syntax().text()));
38+
39+
let edit = TextEdit::replace(range, replacement);
40+
let source_change = SourceChange::from_text_edit(d.file_id.original_file(ctx.sema.db), edit);
41+
42+
Some(vec![fix(
43+
"remove_trailing_return",
44+
"Replace return <expr>; with <expr>",
45+
source_change,
46+
range,
47+
)])
48+
}
49+
50+
fn return_expr(ctx: &DiagnosticsContext<'_>, d: &RemoveTrailingReturn) -> Option<ast::ReturnExpr> {
51+
let root = ctx.sema.db.parse_or_expand(d.file_id);
52+
let expr = d.return_expr.to_node(&root);
53+
ast::ReturnExpr::cast(expr.syntax().clone())
54+
}
55+
56+
fn expr_stmt(ctx: &DiagnosticsContext<'_>, d: &RemoveTrailingReturn) -> Option<ast::ExprStmt> {
57+
let return_expr = return_expr(ctx, d)?;
58+
return_expr.syntax().parent().and_then(ast::ExprStmt::cast)
59+
}
60+
61+
#[cfg(test)]
62+
mod tests {
63+
use crate::tests::{check_diagnostics, check_fix};
64+
65+
#[test]
66+
fn remove_trailing_return() {
67+
check_diagnostics(
68+
r#"
69+
fn foo() -> u8 {
70+
return 2;
71+
} //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
72+
"#,
73+
);
74+
}
75+
76+
#[test]
77+
fn remove_trailing_return_inner_function() {
78+
check_diagnostics(
79+
r#"
80+
fn foo() -> u8 {
81+
fn bar() -> u8 {
82+
return 2;
83+
} //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
84+
bar()
85+
}
86+
"#,
87+
);
88+
}
89+
90+
#[test]
91+
fn remove_trailing_return_closure() {
92+
check_diagnostics(
93+
r#"
94+
fn foo() -> u8 {
95+
let bar = || return 2;
96+
bar() //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
97+
}
98+
"#,
99+
);
100+
check_diagnostics(
101+
r#"
102+
fn foo() -> u8 {
103+
let bar = || {
104+
return 2;
105+
};//^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
106+
bar()
107+
}
108+
"#,
109+
);
110+
}
111+
112+
#[test]
113+
fn remove_trailing_return_unit() {
114+
check_diagnostics(
115+
r#"
116+
fn foo() {
117+
return
118+
} //^^^^^^ 💡 weak: replace return <expr>; with <expr>
119+
"#,
120+
);
121+
}
122+
123+
#[test]
124+
fn remove_trailing_return_no_semi() {
125+
check_diagnostics(
126+
r#"
127+
fn foo() -> u8 {
128+
return 2
129+
} //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
130+
"#,
131+
);
132+
}
133+
134+
#[test]
135+
fn no_diagnostic_if_no_return_keyword() {
136+
check_diagnostics(
137+
r#"
138+
fn foo() -> u8 {
139+
3
140+
}
141+
"#,
142+
);
143+
}
144+
145+
#[test]
146+
fn no_diagnostic_if_not_last_statement() {
147+
check_diagnostics(
148+
r#"
149+
fn foo() -> u8 {
150+
if true { return 2; }
151+
3
152+
}
153+
"#,
154+
);
155+
}
156+
157+
#[test]
158+
fn replace_with_expr() {
159+
check_fix(
160+
r#"
161+
fn foo() -> u8 {
162+
return$0 2;
163+
}
164+
"#,
165+
r#"
166+
fn foo() -> u8 {
167+
2
168+
}
169+
"#,
170+
);
171+
}
172+
173+
#[test]
174+
fn replace_with_unit() {
175+
check_fix(
176+
r#"
177+
fn foo() {
178+
return$0/*ensure tidy is happy*/
179+
}
180+
"#,
181+
r#"
182+
fn foo() {
183+
/*ensure tidy is happy*/
184+
}
185+
"#,
186+
);
187+
}
188+
189+
#[test]
190+
fn replace_with_expr_no_semi() {
191+
check_fix(
192+
r#"
193+
fn foo() -> u8 {
194+
return$0 2
195+
}
196+
"#,
197+
r#"
198+
fn foo() -> u8 {
199+
2
200+
}
201+
"#,
202+
);
203+
}
204+
205+
#[test]
206+
fn replace_in_inner_function() {
207+
check_fix(
208+
r#"
209+
fn foo() -> u8 {
210+
fn bar() -> u8 {
211+
return$0 2;
212+
}
213+
bar()
214+
}
215+
"#,
216+
r#"
217+
fn foo() -> u8 {
218+
fn bar() -> u8 {
219+
2
220+
}
221+
bar()
222+
}
223+
"#,
224+
);
225+
}
226+
227+
#[test]
228+
fn replace_in_closure() {
229+
check_fix(
230+
r#"
231+
fn foo() -> u8 {
232+
let bar = || return$0 2;
233+
bar()
234+
}
235+
"#,
236+
r#"
237+
fn foo() -> u8 {
238+
let bar = || 2;
239+
bar()
240+
}
241+
"#,
242+
);
243+
check_fix(
244+
r#"
245+
fn foo() -> u8 {
246+
let bar = || {
247+
return$0 2;
248+
};
249+
bar()
250+
}
251+
"#,
252+
r#"
253+
fn foo() -> u8 {
254+
let bar = || {
255+
2
256+
};
257+
bar()
258+
}
259+
"#,
260+
);
261+
}
262+
}

crates/ide-diagnostics/src/handlers/type_mismatch.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,9 @@ fn str_ref_to_owned(
186186

187187
#[cfg(test)]
188188
mod tests {
189-
use crate::tests::{check_diagnostics, check_fix, check_no_fix};
189+
use crate::tests::{
190+
check_diagnostics, check_diagnostics_with_disabled, check_fix, check_no_fix,
191+
};
190192

191193
#[test]
192194
fn missing_reference() {
@@ -718,7 +720,7 @@ struct Bar {
718720

719721
#[test]
720722
fn return_no_value() {
721-
check_diagnostics(
723+
check_diagnostics_with_disabled(
722724
r#"
723725
fn f() -> i32 {
724726
return;
@@ -727,6 +729,7 @@ fn f() -> i32 {
727729
}
728730
fn g() { return; }
729731
"#,
732+
std::iter::once("needless_return".to_string()),
730733
);
731734
}
732735

0 commit comments

Comments
 (0)