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