Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

false positives fixes of implicit_return #4274

Merged
merged 3 commits into from
Jul 18, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 100 additions & 65 deletions clippy_lints/src/implicit_return.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
use crate::utils::{in_macro_or_desugar, is_expn_of, snippet_opt, span_lint_and_then};
use rustc::hir::{intravisit::FnKind, Body, ExprKind, FnDecl, HirId, MatchSource};
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};
use crate::utils::{
in_macro_or_desugar, match_def_path,
paths::{BEGIN_PANIC, BEGIN_PANIC_FMT},
resolve_node, snippet_opt, span_lint_and_then,
};
use if_chain::if_chain;
use rustc::{
declare_lint_pass, declare_tool_lint,
hir::{intravisit::FnKind, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind},
lint::{LateContext, LateLintPass, LintArray, LintPass},
};
use rustc_errors::Applicability;
use syntax::source_map::Span;
use syntax_pos::Span;

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

declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);

impl ImplicitReturn {
fn lint(cx: &LateContext<'_, '_>, outer_span: syntax_pos::Span, inner_span: syntax_pos::Span, msg: &str) {
span_lint_and_then(cx, IMPLICIT_RETURN, outer_span, "missing return statement", |db| {
if let Some(snippet) = snippet_opt(cx, inner_span) {
db.span_suggestion(
outer_span,
msg,
format!("return {}", snippet),
Applicability::MachineApplicable,
);
}
});
fn lint(cx: &LateContext<'_, '_>, outer_span: Span, inner_span: Span, msg: &str) {
let outer_span = span_to_outer_expn(outer_span);
let inner_span = span_to_outer_expn(inner_span);

span_lint_and_then(cx, IMPLICIT_RETURN, outer_span, "missing return statement", |db| {
if let Some(snippet) = snippet_opt(cx, inner_span) {
db.span_suggestion(
outer_span,
msg,
format!("return {}", snippet),
Applicability::MachineApplicable,
);
}
});
}

fn span_to_outer_expn(span: Span) -> Span {
if let Some(expr) = span.ctxt().outer_expn_info() {
span_to_outer_expn(expr.call_site)
} else {
span
}
}

fn expr_match(cx: &LateContext<'_, '_>, expr: &rustc::hir::Expr) {
match &expr.node {
// loops could be using `break` instead of `return`
ExprKind::Block(block, ..) | ExprKind::Loop(block, ..) => {
if let Some(expr) = &block.expr {
Self::expr_match(cx, expr);
}
// only needed in the case of `break` with `;` at the end
else if let Some(stmt) = block.stmts.last() {
if let rustc::hir::StmtKind::Semi(expr, ..) = &stmt.node {
// make sure it's a break, otherwise we want to skip
if let ExprKind::Break(.., break_expr) = &expr.node {
if let Some(break_expr) = break_expr {
Self::lint(cx, expr.span, break_expr.span, "change `break` to `return` as shown");
}
}
fn expr_match(cx: &LateContext<'_, '_>, expr: &Expr, ret_ty_is_never: bool) {
match &expr.node {
// loops could be using `break` instead of `return`
ExprKind::Block(block, ..) | ExprKind::Loop(block, ..) => {
if let Some(expr) = &block.expr {
expr_match(cx, expr, ret_ty_is_never);
}
// only needed in the case of `break` with `;` at the end
else if let Some(stmt) = block.stmts.last() {
if_chain! {
if let StmtKind::Semi(expr, ..) = &stmt.node;
// make sure it's a break, otherwise we want to skip
if let ExprKind::Break(.., break_expr) = &expr.node;
if let Some(break_expr) = break_expr;
then {
lint(cx, expr.span, break_expr.span, "change `break` to `return` as shown");
}
}
},
// use `return` instead of `break`
ExprKind::Break(.., break_expr) => {
if let Some(break_expr) = break_expr {
Self::lint(cx, expr.span, break_expr.span, "change `break` to `return` as shown");
}
},
ExprKind::Match(.., arms, source) => {
let check_all_arms = match source {
MatchSource::IfLetDesugar {
contains_else_clause: has_else,
} => *has_else,
_ => true,
};
}
},
// use `return` instead of `break`
ExprKind::Break(.., break_expr) => {
if let Some(break_expr) = break_expr {
lint(cx, expr.span, break_expr.span, "change `break` to `return` as shown");
}
},
ExprKind::Match(.., arms, source) => {
let check_all_arms = match source {
MatchSource::IfLetDesugar {
contains_else_clause: has_else,
} => *has_else,
_ => true,
};

if check_all_arms {
for arm in arms {
Self::expr_match(cx, &arm.body);
if check_all_arms {
for arm in arms {
expr_match(cx, &arm.body, ret_ty_is_never);
}
} else {
expr_match(
cx,
&arms.first().expect("if let doesn't have a single arm").body,
ret_ty_is_never,
);
}
},
// skip if it already has a return statement
ExprKind::Ret(..) => (),
// make sure it's not a call that panics unless we intend to return a panic
ExprKind::Call(expr, ..) => {
if_chain! {
if let ExprKind::Path(qpath) = &expr.node;
if let Some(path_def_id) = resolve_node(cx, qpath, expr.hir_id).opt_def_id();
if match_def_path(cx, path_def_id, &BEGIN_PANIC) ||
match_def_path(cx, path_def_id, &BEGIN_PANIC_FMT);
then {
// only put `return` on panics if the return type of the function/closure is a panic
if ret_ty_is_never {
lint(cx, expr.span, expr.span, "add `return` as shown")
}
} else {
Self::expr_match(cx, &arms.first().expect("if let doesn't have a single arm").body);
}
},
// skip if it already has a return statement
ExprKind::Ret(..) => (),
// everything else is missing `return`
_ => {
// make sure it's not just an unreachable expression
if is_expn_of(expr.span, "unreachable").is_none() {
Self::lint(cx, expr.span, expr.span, "add `return` as shown")
else {
lint(cx, expr.span, expr.span, "add `return` as shown")
}
},
}
}
},
// everything else is missing `return`
_ => lint(cx, expr.span, expr.span, "add `return` as shown"),
}
}

Expand All @@ -110,16 +144,17 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitReturn {
_: FnKind<'tcx>,
_: &'tcx FnDecl,
body: &'tcx Body,
span: Span,
span: syntax::source_map::Span,
_: HirId,
) {
let def_id = cx.tcx.hir().body_owner_def_id(body.id());
let mir = cx.tcx.optimized_mir(def_id);
let ret_ty = mir.return_ty();

// checking return type through MIR, HIR is not able to determine inferred closure return types
// make sure it's not a macro
if !mir.return_ty().is_unit() && !in_macro_or_desugar(span) {
Self::expr_match(cx, &body.value);
if !ret_ty.is_unit() && !in_macro_or_desugar(span) {
expr_match(cx, &body.value, ret_ty.is_never());
}
}
}
14 changes: 14 additions & 0 deletions tests/ui/implicit_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ fn test_end_of_fn() -> bool {
// no error!
return true;
}

true
}

Expand Down Expand Up @@ -76,6 +77,18 @@ fn test_closure() {
let _ = || true;
}

fn test_return_never() -> ! {
panic!()
}

fn test_panic() -> bool {
panic!()
}

fn test_return_macro() -> String {
format!("test {}", "test")
}

fn main() {
let _ = test_end_of_fn();
let _ = test_if_block();
Expand All @@ -86,4 +99,5 @@ fn main() {
let _ = test_loop_with_nests();
let _ = test_loop_with_if_let();
test_closure();
let _ = test_return_macro();
}
34 changes: 23 additions & 11 deletions tests/ui/implicit_return.stderr
Original file line number Diff line number Diff line change
@@ -1,64 +1,76 @@
error: missing return statement
--> $DIR/implicit_return.rs:8:5
--> $DIR/implicit_return.rs:9:5
|
LL | true
| ^^^^ help: add `return` as shown: `return true`
|
= note: `-D clippy::implicit-return` implied by `-D warnings`

error: missing return statement
--> $DIR/implicit_return.rs:14:9
--> $DIR/implicit_return.rs:15:9
|
LL | true
| ^^^^ help: add `return` as shown: `return true`

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

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

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

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

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

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

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

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

error: aborting due to 10 previous errors
error: missing return statement
--> $DIR/implicit_return.rs:81:5
|
LL | panic!()
| ^^^^^^^^ help: add `return` as shown: `return panic!()`

error: missing return statement
--> $DIR/implicit_return.rs:89:5
|
LL | format!("test {}", "test")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")`

error: aborting due to 12 previous errors