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