1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use rustc::lint::*;
use syntax::ast;
use syntax::codemap::{Span, Spanned};
use syntax::visit::FnKind;

use utils::{in_external_macro, in_macro, match_path_ast, snippet_opt, span_lint_and_then, span_note_and_lint};

/// **What it does:** Checks for return statements at the end of a block.
///
/// **Why is this bad?** Removing the `return` and semicolon will make the code
/// more rusty.
///
/// **Known problems:** If the computation returning the value borrows a local
/// variable, removing the `return` may run afoul of the borrow checker.
///
/// **Example:**
/// ```rust
/// fn foo(x: usize) { return x; }
/// ```
declare_clippy_lint! {
    pub NEEDLESS_RETURN,
    style,
    "using a return statement like `return expr;` where an expression would suffice"
}

/// **What it does:** Checks for `let`-bindings, which are subsequently
/// returned.
///
/// **Why is this bad?** It is just extraneous code. Remove it to make your code
/// more rusty.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// { let x = ..; x }
/// ```
declare_clippy_lint! {
    pub LET_AND_RETURN,
    style,
    "creating a let-binding and then immediately returning it like `let x = expr; x` at \
     the end of a block"
}

#[derive(Copy, Clone)]
pub struct ReturnPass;

impl ReturnPass {
    // Check the final stmt or expr in a block for unnecessary return.
    fn check_block_return(&mut self, cx: &EarlyContext, block: &ast::Block) {
        if let Some(stmt) = block.stmts.last() {
            match stmt.node {
                ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => {
                    self.check_final_expr(cx, expr, Some(stmt.span));
                },
                _ => (),
            }
        }
    }

    // Check a the final expression in a block if it's a return.
    fn check_final_expr(&mut self, cx: &EarlyContext, expr: &ast::Expr, span: Option<Span>) {
        match expr.node {
            // simple return is always "bad"
            ast::ExprKind::Ret(Some(ref inner)) => {
                // allow `#[cfg(a)] return a; #[cfg(b)] return b;`
                if !expr.attrs.iter().any(attr_is_cfg) {
                    self.emit_return_lint(cx, span.expect("`else return` is not possible"), inner.span);
                }
            },
            // a whole block? check it!
            ast::ExprKind::Block(ref block) => {
                self.check_block_return(cx, block);
            },
            // an if/if let expr, check both exprs
            // note, if without else is going to be a type checking error anyways
            // (except for unit type functions) so we don't match it
            ast::ExprKind::If(_, ref ifblock, Some(ref elsexpr)) => {
                self.check_block_return(cx, ifblock);
                self.check_final_expr(cx, elsexpr, None);
            },
            // a match expr, check all arms
            ast::ExprKind::Match(_, ref arms) => for arm in arms {
                self.check_final_expr(cx, &arm.body, Some(arm.body.span));
            },
            _ => (),
        }
    }

    fn emit_return_lint(&mut self, cx: &EarlyContext, ret_span: Span, inner_span: Span) {
        if in_external_macro(cx, inner_span) || in_macro(inner_span) {
            return;
        }
        span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded return statement", |db| {
            if let Some(snippet) = snippet_opt(cx, inner_span) {
                db.span_suggestion(ret_span, "remove `return` as shown", snippet);
            }
        });
    }

    // Check for "let x = EXPR; x"
    fn check_let_return(&mut self, cx: &EarlyContext, block: &ast::Block) {
        let mut it = block.stmts.iter();

        // we need both a let-binding stmt and an expr
        if_chain! {
            if let Some(retexpr) = it.next_back();
            if let ast::StmtKind::Expr(ref retexpr) = retexpr.node;
            if let Some(stmt) = it.next_back();
            if let ast::StmtKind::Local(ref local) = stmt.node;
            // don't lint in the presence of type inference
            if local.ty.is_none();
            if !local.attrs.iter().any(attr_is_cfg);
            if let Some(ref initexpr) = local.init;
            if let ast::PatKind::Ident(_, Spanned { node: id, .. }, _) = local.pat.node;
            if let ast::ExprKind::Path(_, ref path) = retexpr.node;
            if match_path_ast(path, &[&id.name.as_str()]);
            if !in_external_macro(cx, initexpr.span);
            then {
                    span_note_and_lint(cx,
                                       LET_AND_RETURN,
                                       retexpr.span,
                                       "returning the result of a let binding from a block. \
                                       Consider returning the expression directly.",
                                       initexpr.span,
                                       "this expression can be directly returned");
            }
        }
    }
}

impl LintPass for ReturnPass {
    fn get_lints(&self) -> LintArray {
        lint_array!(NEEDLESS_RETURN, LET_AND_RETURN)
    }
}

impl EarlyLintPass for ReturnPass {
    fn check_fn(&mut self, cx: &EarlyContext, kind: FnKind, _: &ast::FnDecl, _: Span, _: ast::NodeId) {
        match kind {
            FnKind::ItemFn(.., block) | FnKind::Method(.., block) => self.check_block_return(cx, block),
            FnKind::Closure(body) => self.check_final_expr(cx, body, Some(body.span)),
        }
    }

    fn check_block(&mut self, cx: &EarlyContext, block: &ast::Block) {
        self.check_let_return(cx, block);
    }
}

fn attr_is_cfg(attr: &ast::Attribute) -> bool {
    attr.meta_item_list().is_some() && attr.name().map_or(false, |n| n == "cfg")
}