]>
Commit | Line | Data |
---|---|---|
17df50a5 XL |
1 | use clippy_utils::{ |
2 | diagnostics::span_lint_and_sugg, | |
3 | get_async_fn_body, is_async_fn, | |
4 | source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}, | |
5 | visitors::visit_break_exprs, | |
6 | }; | |
f20569fa XL |
7 | use rustc_errors::Applicability; |
8 | use rustc_hir::intravisit::FnKind; | |
17df50a5 XL |
9 | use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId}; |
10 | use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
11 | use rustc_middle::lint::in_external_macro; | |
f20569fa | 12 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
17df50a5 | 13 | use rustc_span::{Span, SyntaxContext}; |
f20569fa XL |
14 | |
15 | declare_clippy_lint! { | |
94222f64 XL |
16 | /// ### What it does |
17 | /// Checks for missing return statements at the end of a block. | |
f20569fa | 18 | /// |
94222f64 XL |
19 | /// ### Why is this bad? |
20 | /// Actually omitting the return keyword is idiomatic Rust code. Programmers | |
f20569fa XL |
21 | /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss |
22 | /// the last returning statement because the only difference is a missing `;`. Especially in bigger | |
23 | /// code with multiple return paths having a `return` keyword makes it easier to find the | |
24 | /// corresponding statements. | |
25 | /// | |
94222f64 | 26 | /// ### Example |
f20569fa XL |
27 | /// ```rust |
28 | /// fn foo(x: usize) -> usize { | |
29 | /// x | |
30 | /// } | |
31 | /// ``` | |
32 | /// add return | |
33 | /// ```rust | |
34 | /// fn foo(x: usize) -> usize { | |
35 | /// return x; | |
36 | /// } | |
37 | /// ``` | |
38 | pub IMPLICIT_RETURN, | |
39 | restriction, | |
40 | "use a return statement like `return expr` instead of an expression" | |
41 | } | |
42 | ||
43 | declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); | |
44 | ||
17df50a5 XL |
45 | fn lint_return(cx: &LateContext<'_>, span: Span) { |
46 | let mut app = Applicability::MachineApplicable; | |
47 | let snip = snippet_with_applicability(cx, span, "..", &mut app); | |
48 | span_lint_and_sugg( | |
49 | cx, | |
50 | IMPLICIT_RETURN, | |
51 | span, | |
52 | "missing `return` statement", | |
53 | "add `return` as shown", | |
54 | format!("return {}", snip), | |
55 | app, | |
56 | ); | |
57 | } | |
58 | ||
59 | fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) { | |
60 | let mut app = Applicability::MachineApplicable; | |
61 | let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0; | |
62 | span_lint_and_sugg( | |
63 | cx, | |
64 | IMPLICIT_RETURN, | |
65 | break_span, | |
66 | "missing `return` statement", | |
67 | "change `break` to `return` as shown", | |
68 | format!("return {}", snip), | |
69 | app, | |
70 | ); | |
71 | } | |
72 | ||
73 | #[derive(Clone, Copy, PartialEq, Eq)] | |
74 | enum LintLocation { | |
75 | /// The lint was applied to a parent expression. | |
76 | Parent, | |
77 | /// The lint was applied to this expression, a child, or not applied. | |
78 | Inner, | |
79 | } | |
80 | impl LintLocation { | |
81 | fn still_parent(self, b: bool) -> Self { | |
82 | if b { self } else { Self::Inner } | |
83 | } | |
84 | ||
85 | fn is_parent(self) -> bool { | |
86 | self == Self::Parent | |
87 | } | |
88 | } | |
89 | ||
90 | // Gets the call site if the span is in a child context. Otherwise returns `None`. | |
91 | fn get_call_site(span: Span, ctxt: SyntaxContext) -> Option<Span> { | |
92 | (span.ctxt() != ctxt).then(|| walk_span_to_context(span, ctxt).unwrap_or(span)) | |
f20569fa XL |
93 | } |
94 | ||
17df50a5 XL |
95 | fn lint_implicit_returns( |
96 | cx: &LateContext<'tcx>, | |
97 | expr: &'tcx Expr<'_>, | |
98 | // The context of the function body. | |
99 | ctxt: SyntaxContext, | |
100 | // Whether the expression is from a macro expansion. | |
101 | call_site_span: Option<Span>, | |
102 | ) -> LintLocation { | |
f20569fa | 103 | match expr.kind { |
17df50a5 XL |
104 | ExprKind::Block( |
105 | Block { | |
106 | expr: Some(block_expr), .. | |
107 | }, | |
108 | _, | |
109 | ) => lint_implicit_returns( | |
110 | cx, | |
111 | block_expr, | |
112 | ctxt, | |
113 | call_site_span.or_else(|| get_call_site(block_expr.span, ctxt)), | |
114 | ) | |
115 | .still_parent(call_site_span.is_some()), | |
116 | ||
117 | ExprKind::If(_, then_expr, Some(else_expr)) => { | |
118 | // Both `then_expr` or `else_expr` are required to be blocks in the same context as the `if`. Don't | |
119 | // bother checking. | |
120 | let res = lint_implicit_returns(cx, then_expr, ctxt, call_site_span).still_parent(call_site_span.is_some()); | |
121 | if res.is_parent() { | |
122 | // The return was added as a parent of this if expression. | |
123 | return res; | |
f20569fa | 124 | } |
17df50a5 | 125 | lint_implicit_returns(cx, else_expr, ctxt, call_site_span).still_parent(call_site_span.is_some()) |
f20569fa | 126 | }, |
f20569fa | 127 | |
17df50a5 XL |
128 | ExprKind::Match(_, arms, _) => { |
129 | for arm in arms { | |
130 | let res = lint_implicit_returns( | |
131 | cx, | |
132 | arm.body, | |
133 | ctxt, | |
134 | call_site_span.or_else(|| get_call_site(arm.body.span, ctxt)), | |
135 | ) | |
136 | .still_parent(call_site_span.is_some()); | |
137 | if res.is_parent() { | |
138 | // The return was added as a parent of this match expression. | |
139 | return res; | |
140 | } | |
f20569fa | 141 | } |
17df50a5 | 142 | LintLocation::Inner |
f20569fa | 143 | }, |
17df50a5 XL |
144 | |
145 | ExprKind::Loop(block, ..) => { | |
146 | let mut add_return = false; | |
147 | visit_break_exprs(block, |break_expr, dest, sub_expr| { | |
148 | if dest.target_id.ok() == Some(expr.hir_id) { | |
149 | if call_site_span.is_none() && break_expr.span.ctxt() == ctxt { | |
150 | // At this point sub_expr can be `None` in async functions which either diverge, or return the | |
151 | // unit type. | |
152 | if let Some(sub_expr) = sub_expr { | |
153 | lint_break(cx, break_expr.span, sub_expr.span); | |
154 | } | |
155 | } else { | |
156 | // the break expression is from a macro call, add a return to the loop | |
157 | add_return = true; | |
158 | } | |
159 | } | |
160 | }); | |
161 | if add_return { | |
162 | #[allow(clippy::option_if_let_else)] | |
163 | if let Some(span) = call_site_span { | |
164 | lint_return(cx, span); | |
165 | LintLocation::Parent | |
166 | } else { | |
167 | lint_return(cx, expr.span); | |
168 | LintLocation::Inner | |
f20569fa XL |
169 | } |
170 | } else { | |
17df50a5 | 171 | LintLocation::Inner |
f20569fa XL |
172 | } |
173 | }, | |
17df50a5 XL |
174 | |
175 | // If expressions without an else clause, and blocks without a final expression can only be the final expression | |
176 | // if they are divergent, or return the unit type. | |
177 | ExprKind::If(_, _, None) | ExprKind::Block(Block { expr: None, .. }, _) | ExprKind::Ret(_) => { | |
178 | LintLocation::Inner | |
179 | }, | |
180 | ||
181 | // Any divergent expression doesn't need a return statement. | |
182 | ExprKind::MethodCall(..) | |
183 | | ExprKind::Call(..) | |
184 | | ExprKind::Binary(..) | |
185 | | ExprKind::Unary(..) | |
186 | | ExprKind::Index(..) | |
187 | if cx.typeck_results().expr_ty(expr).is_never() => | |
188 | { | |
189 | LintLocation::Inner | |
190 | }, | |
191 | ||
192 | _ => | |
193 | { | |
194 | #[allow(clippy::option_if_let_else)] | |
195 | if let Some(span) = call_site_span { | |
196 | lint_return(cx, span); | |
197 | LintLocation::Parent | |
198 | } else { | |
199 | lint_return(cx, expr.span); | |
200 | LintLocation::Inner | |
f20569fa XL |
201 | } |
202 | }, | |
f20569fa XL |
203 | } |
204 | } | |
205 | ||
206 | impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { | |
207 | fn check_fn( | |
208 | &mut self, | |
209 | cx: &LateContext<'tcx>, | |
17df50a5 XL |
210 | kind: FnKind<'tcx>, |
211 | decl: &'tcx FnDecl<'_>, | |
f20569fa XL |
212 | body: &'tcx Body<'_>, |
213 | span: Span, | |
214 | _: HirId, | |
215 | ) { | |
17df50a5 XL |
216 | if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_))) |
217 | || span.ctxt() != body.value.span.ctxt() | |
218 | || in_external_macro(cx.sess(), span) | |
219 | { | |
f20569fa XL |
220 | return; |
221 | } | |
17df50a5 XL |
222 | |
223 | let res_ty = cx.typeck_results().expr_ty(&body.value); | |
224 | if res_ty.is_unit() || res_ty.is_never() { | |
f20569fa XL |
225 | return; |
226 | } | |
17df50a5 XL |
227 | |
228 | let expr = if is_async_fn(kind) { | |
229 | match get_async_fn_body(cx.tcx, body) { | |
230 | Some(e) => e, | |
231 | None => return, | |
232 | } | |
233 | } else { | |
234 | &body.value | |
235 | }; | |
236 | lint_implicit_returns(cx, expr, expr.span.ctxt(), None); | |
f20569fa XL |
237 | } |
238 | } |