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