Skip to content

Commit 6c067bf

Browse files
committed
false positives fixes of implicit_return
- Handle returning macro statements properly - Handle functions that return never type - Handle functions that panic but do not return never type
1 parent 36fb646 commit 6c067bf

File tree

3 files changed

+137
-76
lines changed

3 files changed

+137
-76
lines changed

clippy_lints/src/implicit_return.rs

Lines changed: 100 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
use crate::utils::{in_macro_or_desugar, is_expn_of, snippet_opt, span_lint_and_then};
2-
use rustc::hir::{intravisit::FnKind, Body, ExprKind, FnDecl, HirId, MatchSource};
3-
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
4-
use rustc::{declare_lint_pass, declare_tool_lint};
1+
use crate::utils::{
2+
in_macro_or_desugar, match_def_path,
3+
paths::{BEGIN_PANIC, BEGIN_PANIC_FMT},
4+
resolve_node, snippet_opt, span_lint_and_then,
5+
};
6+
use if_chain::if_chain;
7+
use rustc::{
8+
declare_lint_pass, declare_tool_lint,
9+
hir::{intravisit::FnKind, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind},
10+
lint::{LateContext, LateLintPass, LintArray, LintPass},
11+
};
512
use rustc_errors::Applicability;
6-
use syntax::source_map::Span;
13+
use syntax_pos::Span;
714

815
declare_clippy_lint! {
916
/// **What it does:** Checks for missing return statements at the end of a block.
@@ -35,71 +42,98 @@ declare_clippy_lint! {
3542

3643
declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
3744

38-
impl ImplicitReturn {
39-
fn lint(cx: &LateContext<'_, '_>, outer_span: syntax_pos::Span, inner_span: syntax_pos::Span, msg: &str) {
40-
span_lint_and_then(cx, IMPLICIT_RETURN, outer_span, "missing return statement", |db| {
41-
if let Some(snippet) = snippet_opt(cx, inner_span) {
42-
db.span_suggestion(
43-
outer_span,
44-
msg,
45-
format!("return {}", snippet),
46-
Applicability::MachineApplicable,
47-
);
48-
}
49-
});
45+
fn lint(cx: &LateContext<'_, '_>, outer_span: Span, inner_span: Span, msg: &str) {
46+
let outer_span = span_to_outer_expn(outer_span);
47+
let inner_span = span_to_outer_expn(inner_span);
48+
49+
span_lint_and_then(cx, IMPLICIT_RETURN, outer_span, "missing return statement", |db| {
50+
if let Some(snippet) = snippet_opt(cx, inner_span) {
51+
db.span_suggestion(
52+
outer_span,
53+
msg,
54+
format!("return {}", snippet),
55+
Applicability::MachineApplicable,
56+
);
57+
}
58+
});
59+
}
60+
61+
fn span_to_outer_expn(span: Span) -> Span {
62+
if let Some(expr) = span.ctxt().outer_expn_info() {
63+
span_to_outer_expn(expr.call_site)
64+
} else {
65+
span
5066
}
67+
}
5168

52-
fn expr_match(cx: &LateContext<'_, '_>, expr: &rustc::hir::Expr) {
53-
match &expr.node {
54-
// loops could be using `break` instead of `return`
55-
ExprKind::Block(block, ..) | ExprKind::Loop(block, ..) => {
56-
if let Some(expr) = &block.expr {
57-
Self::expr_match(cx, expr);
58-
}
59-
// only needed in the case of `break` with `;` at the end
60-
else if let Some(stmt) = block.stmts.last() {
61-
if let rustc::hir::StmtKind::Semi(expr, ..) = &stmt.node {
62-
// make sure it's a break, otherwise we want to skip
63-
if let ExprKind::Break(.., break_expr) = &expr.node {
64-
if let Some(break_expr) = break_expr {
65-
Self::lint(cx, expr.span, break_expr.span, "change `break` to `return` as shown");
66-
}
67-
}
69+
fn expr_match(cx: &LateContext<'_, '_>, expr: &Expr, ret_ty_is_never: bool) {
70+
match &expr.node {
71+
// loops could be using `break` instead of `return`
72+
ExprKind::Block(block, ..) | ExprKind::Loop(block, ..) => {
73+
if let Some(expr) = &block.expr {
74+
expr_match(cx, expr, ret_ty_is_never);
75+
}
76+
// only needed in the case of `break` with `;` at the end
77+
else if let Some(stmt) = block.stmts.last() {
78+
if_chain! {
79+
if let StmtKind::Semi(expr, ..) = &stmt.node;
80+
// make sure it's a break, otherwise we want to skip
81+
if let ExprKind::Break(.., break_expr) = &expr.node;
82+
if let Some(break_expr) = break_expr;
83+
then {
84+
lint(cx, expr.span, break_expr.span, "change `break` to `return` as shown");
6885
}
6986
}
70-
},
71-
// use `return` instead of `break`
72-
ExprKind::Break(.., break_expr) => {
73-
if let Some(break_expr) = break_expr {
74-
Self::lint(cx, expr.span, break_expr.span, "change `break` to `return` as shown");
75-
}
76-
},
77-
ExprKind::Match(.., arms, source) => {
78-
let check_all_arms = match source {
79-
MatchSource::IfLetDesugar {
80-
contains_else_clause: has_else,
81-
} => *has_else,
82-
_ => true,
83-
};
87+
}
88+
},
89+
// use `return` instead of `break`
90+
ExprKind::Break(.., break_expr) => {
91+
if let Some(break_expr) = break_expr {
92+
lint(cx, expr.span, break_expr.span, "change `break` to `return` as shown");
93+
}
94+
},
95+
ExprKind::Match(.., arms, source) => {
96+
let check_all_arms = match source {
97+
MatchSource::IfLetDesugar {
98+
contains_else_clause: has_else,
99+
} => *has_else,
100+
_ => true,
101+
};
84102

85-
if check_all_arms {
86-
for arm in arms {
87-
Self::expr_match(cx, &arm.body);
103+
if check_all_arms {
104+
for arm in arms {
105+
expr_match(cx, &arm.body, ret_ty_is_never);
106+
}
107+
} else {
108+
expr_match(
109+
cx,
110+
&arms.first().expect("if let doesn't have a single arm").body,
111+
ret_ty_is_never,
112+
);
113+
}
114+
},
115+
// skip if it already has a return statement
116+
ExprKind::Ret(..) => (),
117+
// make sure it's not a call that panics unless we intend to return a panic
118+
ExprKind::Call(expr, ..) => {
119+
if_chain! {
120+
if let ExprKind::Path(qpath) = &expr.node;
121+
if let Some(path_def_id) = resolve_node(cx, qpath, expr.hir_id).opt_def_id();
122+
if match_def_path(cx, path_def_id, &BEGIN_PANIC) ||
123+
match_def_path(cx, path_def_id, &BEGIN_PANIC_FMT);
124+
then {
125+
// only put `return` on panics if the return type of the function/closure is a panic
126+
if ret_ty_is_never {
127+
lint(cx, expr.span, expr.span, "add `return` as shown")
88128
}
89-
} else {
90-
Self::expr_match(cx, &arms.first().expect("if let doesn't have a single arm").body);
91129
}
92-
},
93-
// skip if it already has a return statement
94-
ExprKind::Ret(..) => (),
95-
// everything else is missing `return`
96-
_ => {
97-
// make sure it's not just an unreachable expression
98-
if is_expn_of(expr.span, "unreachable").is_none() {
99-
Self::lint(cx, expr.span, expr.span, "add `return` as shown")
130+
else {
131+
lint(cx, expr.span, expr.span, "add `return` as shown")
100132
}
101-
},
102-
}
133+
}
134+
},
135+
// everything else is missing `return`
136+
_ => lint(cx, expr.span, expr.span, "add `return` as shown"),
103137
}
104138
}
105139

@@ -110,16 +144,17 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitReturn {
110144
_: FnKind<'tcx>,
111145
_: &'tcx FnDecl,
112146
body: &'tcx Body,
113-
span: Span,
147+
span: syntax::source_map::Span,
114148
_: HirId,
115149
) {
116150
let def_id = cx.tcx.hir().body_owner_def_id(body.id());
117151
let mir = cx.tcx.optimized_mir(def_id);
152+
let ret_ty = mir.return_ty();
118153

119154
// checking return type through MIR, HIR is not able to determine inferred closure return types
120155
// make sure it's not a macro
121-
if !mir.return_ty().is_unit() && !in_macro_or_desugar(span) {
122-
Self::expr_match(cx, &body.value);
156+
if !ret_ty.is_unit() && !in_macro_or_desugar(span) {
157+
expr_match(cx, &body.value, ret_ty.is_never());
123158
}
124159
}
125160
}

tests/ui/implicit_return.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ fn test_end_of_fn() -> bool {
55
// no error!
66
return true;
77
}
8+
89
true
910
}
1011

@@ -76,6 +77,18 @@ fn test_closure() {
7677
let _ = || true;
7778
}
7879

80+
fn test_return_never() -> ! {
81+
panic!()
82+
}
83+
84+
fn test_panic() -> bool {
85+
panic!()
86+
}
87+
88+
fn test_return_macro() -> String {
89+
format!("test {}", "test")
90+
}
91+
7992
fn main() {
8093
let _ = test_end_of_fn();
8194
let _ = test_if_block();
@@ -86,4 +99,5 @@ fn main() {
8699
let _ = test_loop_with_nests();
87100
let _ = test_loop_with_if_let();
88101
test_closure();
102+
let _ = test_return_macro();
89103
}

tests/ui/implicit_return.stderr

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,76 @@
11
error: missing return statement
2-
--> $DIR/implicit_return.rs:8:5
2+
--> $DIR/implicit_return.rs:9:5
33
|
44
LL | true
55
| ^^^^ help: add `return` as shown: `return true`
66
|
77
= note: `-D clippy::implicit-return` implied by `-D warnings`
88

99
error: missing return statement
10-
--> $DIR/implicit_return.rs:14:9
10+
--> $DIR/implicit_return.rs:15:9
1111
|
1212
LL | true
1313
| ^^^^ help: add `return` as shown: `return true`
1414

1515
error: missing return statement
16-
--> $DIR/implicit_return.rs:16:9
16+
--> $DIR/implicit_return.rs:17:9
1717
|
1818
LL | false
1919
| ^^^^^ help: add `return` as shown: `return false`
2020

2121
error: missing return statement
22-
--> $DIR/implicit_return.rs:24:17
22+
--> $DIR/implicit_return.rs:25:17
2323
|
2424
LL | true => false,
2525
| ^^^^^ help: add `return` as shown: `return false`
2626

2727
error: missing return statement
28-
--> $DIR/implicit_return.rs:25:20
28+
--> $DIR/implicit_return.rs:26:20
2929
|
3030
LL | false => { true },
3131
| ^^^^ help: add `return` as shown: `return true`
3232

3333
error: missing return statement
34-
--> $DIR/implicit_return.rs:40:9
34+
--> $DIR/implicit_return.rs:41:9
3535
|
3636
LL | break true;
3737
| ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
3838

3939
error: missing return statement
40-
--> $DIR/implicit_return.rs:48:13
40+
--> $DIR/implicit_return.rs:49:13
4141
|
4242
LL | break true;
4343
| ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
4444

4545
error: missing return statement
46-
--> $DIR/implicit_return.rs:57:13
46+
--> $DIR/implicit_return.rs:58:13
4747
|
4848
LL | break true;
4949
| ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
5050

5151
error: missing return statement
52-
--> $DIR/implicit_return.rs:75:18
52+
--> $DIR/implicit_return.rs:76:18
5353
|
5454
LL | let _ = || { true };
5555
| ^^^^ help: add `return` as shown: `return true`
5656

5757
error: missing return statement
58-
--> $DIR/implicit_return.rs:76:16
58+
--> $DIR/implicit_return.rs:77:16
5959
|
6060
LL | let _ = || true;
6161
| ^^^^ help: add `return` as shown: `return true`
6262

63-
error: aborting due to 10 previous errors
63+
error: missing return statement
64+
--> $DIR/implicit_return.rs:81:5
65+
|
66+
LL | panic!()
67+
| ^^^^^^^^ help: add `return` as shown: `return panic!()`
68+
69+
error: missing return statement
70+
--> $DIR/implicit_return.rs:89:5
71+
|
72+
LL | format!("test {}", "test")
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")`
74+
75+
error: aborting due to 12 previous errors
6476

0 commit comments

Comments
 (0)