]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/returns.rs
Auto merge of #3845 - euclio:unused-comments, r=phansch
[rust.git] / clippy_lints / src / returns.rs
1 use crate::utils::{in_macro, match_path_ast, snippet_opt, span_lint_and_then, span_note_and_lint};
2 use if_chain::if_chain;
3 use rustc::lint::{in_external_macro, EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass};
4 use rustc::{declare_tool_lint, lint_array};
5 use rustc_errors::Applicability;
6 use syntax::ast;
7 use syntax::source_map::Span;
8 use syntax::visit::FnKind;
9 use syntax_pos::BytePos;
10
11 declare_clippy_lint! {
12     /// **What it does:** Checks for return statements at the end of a block.
13     ///
14     /// **Why is this bad?** Removing the `return` and semicolon will make the code
15     /// more rusty.
16     ///
17     /// **Known problems:** If the computation returning the value borrows a local
18     /// variable, removing the `return` may run afoul of the borrow checker.
19     ///
20     /// **Example:**
21     /// ```rust
22     /// fn foo(x: usize) -> usize {
23     ///     return x;
24     /// }
25     /// ```
26     /// simplify to
27     /// ```rust
28     /// fn foo(x: usize) -> usize {
29     ///     x
30     /// }
31     /// ```
32     pub NEEDLESS_RETURN,
33     style,
34     "using a return statement like `return expr;` where an expression would suffice"
35 }
36
37 declare_clippy_lint! {
38     /// **What it does:** Checks for `let`-bindings, which are subsequently
39     /// returned.
40     ///
41     /// **Why is this bad?** It is just extraneous code. Remove it to make your code
42     /// more rusty.
43     ///
44     /// **Known problems:** None.
45     ///
46     /// **Example:**
47     /// ```rust
48     /// fn foo() -> String {
49     ///     let x = String::new();
50     ///     x
51     /// }
52     /// ```
53     /// instead, use
54     /// ```
55     /// fn foo() -> String {
56     ///     String::new()
57     /// }
58     /// ```
59     pub LET_AND_RETURN,
60     style,
61     "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
62 }
63
64 declare_clippy_lint! {
65     /// **What it does:** Checks for unit (`()`) expressions that can be removed.
66     ///
67     /// **Why is this bad?** Such expressions add no value, but can make the code
68     /// less readable. Depending on formatting they can make a `break` or `return`
69     /// statement look like a function call.
70     ///
71     /// **Known problems:** The lint currently misses unit return types in types,
72     /// e.g. the `F` in `fn generic_unit<F: Fn() -> ()>(f: F) { .. }`.
73     ///
74     /// **Example:**
75     /// ```rust
76     /// fn return_unit() -> () {
77     ///     ()
78     /// }
79     /// ```
80     pub UNUSED_UNIT,
81     style,
82     "needless unit expression"
83 }
84
85 #[derive(Copy, Clone)]
86 pub struct ReturnPass;
87
88 impl ReturnPass {
89     // Check the final stmt or expr in a block for unnecessary return.
90     fn check_block_return(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
91         if let Some(stmt) = block.stmts.last() {
92             match stmt.node {
93                 ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => {
94                     self.check_final_expr(cx, expr, Some(stmt.span));
95                 },
96                 _ => (),
97             }
98         }
99     }
100
101     // Check a the final expression in a block if it's a return.
102     fn check_final_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr, span: Option<Span>) {
103         match expr.node {
104             // simple return is always "bad"
105             ast::ExprKind::Ret(Some(ref inner)) => {
106                 // allow `#[cfg(a)] return a; #[cfg(b)] return b;`
107                 if !expr.attrs.iter().any(attr_is_cfg) {
108                     self.emit_return_lint(cx, span.expect("`else return` is not possible"), inner.span);
109                 }
110             },
111             // a whole block? check it!
112             ast::ExprKind::Block(ref block, _) => {
113                 self.check_block_return(cx, block);
114             },
115             // an if/if let expr, check both exprs
116             // note, if without else is going to be a type checking error anyways
117             // (except for unit type functions) so we don't match it
118             ast::ExprKind::If(_, ref ifblock, Some(ref elsexpr)) => {
119                 self.check_block_return(cx, ifblock);
120                 self.check_final_expr(cx, elsexpr, None);
121             },
122             // a match expr, check all arms
123             ast::ExprKind::Match(_, ref arms) => {
124                 for arm in arms {
125                     self.check_final_expr(cx, &arm.body, Some(arm.body.span));
126                 }
127             },
128             _ => (),
129         }
130     }
131
132     fn emit_return_lint(&mut self, cx: &EarlyContext<'_>, ret_span: Span, inner_span: Span) {
133         if in_external_macro(cx.sess(), inner_span) || in_macro(inner_span) {
134             return;
135         }
136         span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded return statement", |db| {
137             if let Some(snippet) = snippet_opt(cx, inner_span) {
138                 db.span_suggestion(
139                     ret_span,
140                     "remove `return` as shown",
141                     snippet,
142                     Applicability::MachineApplicable,
143                 );
144             }
145         });
146     }
147
148     // Check for "let x = EXPR; x"
149     fn check_let_return(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
150         let mut it = block.stmts.iter();
151
152         // we need both a let-binding stmt and an expr
153         if_chain! {
154             if let Some(retexpr) = it.next_back();
155             if let ast::StmtKind::Expr(ref retexpr) = retexpr.node;
156             if let Some(stmt) = it.next_back();
157             if let ast::StmtKind::Local(ref local) = stmt.node;
158             // don't lint in the presence of type inference
159             if local.ty.is_none();
160             if !local.attrs.iter().any(attr_is_cfg);
161             if let Some(ref initexpr) = local.init;
162             if let ast::PatKind::Ident(_, ident, _) = local.pat.node;
163             if let ast::ExprKind::Path(_, ref path) = retexpr.node;
164             if match_path_ast(path, &[&ident.as_str()]);
165             if !in_external_macro(cx.sess(), initexpr.span);
166             then {
167                     span_note_and_lint(cx,
168                                        LET_AND_RETURN,
169                                        retexpr.span,
170                                        "returning the result of a let binding from a block. \
171                                        Consider returning the expression directly.",
172                                        initexpr.span,
173                                        "this expression can be directly returned");
174             }
175         }
176     }
177 }
178
179 impl LintPass for ReturnPass {
180     fn get_lints(&self) -> LintArray {
181         lint_array!(NEEDLESS_RETURN, LET_AND_RETURN, UNUSED_UNIT)
182     }
183
184     fn name(&self) -> &'static str {
185         "Return"
186     }
187 }
188
189 impl EarlyLintPass for ReturnPass {
190     fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, decl: &ast::FnDecl, span: Span, _: ast::NodeId) {
191         match kind {
192             FnKind::ItemFn(.., block) | FnKind::Method(.., block) => self.check_block_return(cx, block),
193             FnKind::Closure(body) => self.check_final_expr(cx, body, Some(body.span)),
194         }
195         if_chain! {
196             if let ast::FunctionRetTy::Ty(ref ty) = decl.output;
197             if let ast::TyKind::Tup(ref vals) = ty.node;
198             if vals.is_empty() && !in_macro(ty.span) && get_def(span) == get_def(ty.span);
199             then {
200                 let (rspan, appl) = if let Ok(fn_source) =
201                         cx.sess().source_map()
202                                  .span_to_snippet(span.with_hi(ty.span.hi())) {
203                     if let Some(rpos) = fn_source.rfind("->") {
204                         #[allow(clippy::cast_possible_truncation)]
205                         (ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
206                             Applicability::MachineApplicable)
207                     } else {
208                         (ty.span, Applicability::MaybeIncorrect)
209                     }
210                 } else {
211                     (ty.span, Applicability::MaybeIncorrect)
212                 };
213                 span_lint_and_then(cx, UNUSED_UNIT, rspan, "unneeded unit return type", |db| {
214                     db.span_suggestion(
215                         rspan,
216                         "remove the `-> ()`",
217                         String::new(),
218                         appl,
219                     );
220                 });
221             }
222         }
223     }
224
225     fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
226         self.check_let_return(cx, block);
227         if_chain! {
228             if let Some(ref stmt) = block.stmts.last();
229             if let ast::StmtKind::Expr(ref expr) = stmt.node;
230             if is_unit_expr(expr) && !in_macro(expr.span);
231             then {
232                 let sp = expr.span;
233                 span_lint_and_then(cx, UNUSED_UNIT, sp, "unneeded unit expression", |db| {
234                     db.span_suggestion(
235                         sp,
236                         "remove the final `()`",
237                         String::new(),
238                         Applicability::MachineApplicable,
239                     );
240                 });
241             }
242         }
243     }
244
245     fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
246         match e.node {
247             ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
248                 if is_unit_expr(expr) && !in_macro(expr.span) {
249                     span_lint_and_then(cx, UNUSED_UNIT, expr.span, "unneeded `()`", |db| {
250                         db.span_suggestion(
251                             expr.span,
252                             "remove the `()`",
253                             String::new(),
254                             Applicability::MachineApplicable,
255                         );
256                     });
257                 }
258             },
259             _ => (),
260         }
261     }
262 }
263
264 fn attr_is_cfg(attr: &ast::Attribute) -> bool {
265     attr.meta_item_list().is_some() && attr.name() == "cfg"
266 }
267
268 // get the def site
269 fn get_def(span: Span) -> Option<Span> {
270     span.ctxt().outer().expn_info().and_then(|info| info.def_site)
271 }
272
273 // is this expr a `()` unit?
274 fn is_unit_expr(expr: &ast::Expr) -> bool {
275     if let ast::ExprKind::Tup(ref vals) = expr.node {
276         vals.is_empty()
277     } else {
278         false
279     }
280 }