]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/let_and_return.rs
Rollup merge of #73949 - wesleywiser:simplify_try_fixes, r=oli-obk
[rust.git] / src / tools / clippy / clippy_lints / src / let_and_return.rs
1 use if_chain::if_chain;
2 use rustc_errors::Applicability;
3 use rustc_hir::def_id::DefId;
4 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
5 use rustc_hir::{Block, Expr, ExprKind, PatKind, StmtKind};
6 use rustc_lint::{LateContext, LateLintPass, LintContext};
7 use rustc_middle::hir::map::Map;
8 use rustc_middle::lint::in_external_macro;
9 use rustc_middle::ty::subst::GenericArgKind;
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
11
12 use crate::utils::{in_macro, match_qpath, snippet_opt, span_lint_and_then};
13
14 declare_clippy_lint! {
15     /// **What it does:** Checks for `let`-bindings, which are subsequently
16     /// returned.
17     ///
18     /// **Why is this bad?** It is just extraneous code. Remove it to make your code
19     /// more rusty.
20     ///
21     /// **Known problems:** None.
22     ///
23     /// **Example:**
24     /// ```rust
25     /// fn foo() -> String {
26     ///     let x = String::new();
27     ///     x
28     /// }
29     /// ```
30     /// instead, use
31     /// ```
32     /// fn foo() -> String {
33     ///     String::new()
34     /// }
35     /// ```
36     pub LET_AND_RETURN,
37     style,
38     "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
39 }
40
41 declare_lint_pass!(LetReturn => [LET_AND_RETURN]);
42
43 impl<'tcx> LateLintPass<'tcx> for LetReturn {
44     fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
45         // we need both a let-binding stmt and an expr
46         if_chain! {
47             if let Some(retexpr) = block.expr;
48             if let Some(stmt) = block.stmts.iter().last();
49             if let StmtKind::Local(local) = &stmt.kind;
50             if local.ty.is_none();
51             if local.attrs.is_empty();
52             if let Some(initexpr) = &local.init;
53             if let PatKind::Binding(.., ident, _) = local.pat.kind;
54             if let ExprKind::Path(qpath) = &retexpr.kind;
55             if match_qpath(qpath, &[&*ident.name.as_str()]);
56             if !last_statement_borrows(cx, initexpr);
57             if !in_external_macro(cx.sess(), initexpr.span);
58             if !in_external_macro(cx.sess(), retexpr.span);
59             if !in_external_macro(cx.sess(), local.span);
60             if !in_macro(local.span);
61             then {
62                 span_lint_and_then(
63                     cx,
64                     LET_AND_RETURN,
65                     retexpr.span,
66                     "returning the result of a `let` binding from a block",
67                     |err| {
68                         err.span_label(local.span, "unnecessary `let` binding");
69
70                         if let Some(snippet) = snippet_opt(cx, initexpr.span) {
71                             err.multipart_suggestion(
72                                 "return the expression directly",
73                                 vec![
74                                     (local.span, String::new()),
75                                     (retexpr.span, snippet),
76                                 ],
77                                 Applicability::MachineApplicable,
78                             );
79                         } else {
80                             err.span_help(initexpr.span, "this expression can be directly returned");
81                         }
82                     },
83                 );
84             }
85         }
86     }
87 }
88
89 fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
90     let mut visitor = BorrowVisitor { cx, borrows: false };
91     walk_expr(&mut visitor, expr);
92     visitor.borrows
93 }
94
95 struct BorrowVisitor<'a, 'tcx> {
96     cx: &'a LateContext<'tcx>,
97     borrows: bool,
98 }
99
100 impl BorrowVisitor<'_, '_> {
101     fn fn_def_id(&self, expr: &Expr<'_>) -> Option<DefId> {
102         match &expr.kind {
103             ExprKind::MethodCall(..) => self.cx.tables().type_dependent_def_id(expr.hir_id),
104             ExprKind::Call(
105                 Expr {
106                     kind: ExprKind::Path(qpath),
107                     ..
108                 },
109                 ..,
110             ) => self.cx.qpath_res(qpath, expr.hir_id).opt_def_id(),
111             _ => None,
112         }
113     }
114 }
115
116 impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
117     type Map = Map<'tcx>;
118
119     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
120         if self.borrows {
121             return;
122         }
123
124         if let Some(def_id) = self.fn_def_id(expr) {
125             self.borrows = self
126                 .cx
127                 .tcx
128                 .fn_sig(def_id)
129                 .output()
130                 .skip_binder()
131                 .walk()
132                 .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
133         }
134
135         walk_expr(self, expr);
136     }
137
138     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
139         NestedVisitorMap::None
140     }
141 }