]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/unnecessary_wraps.rs
Auto merge of #6931 - Jarcho:needless_borrow, r=phansch,flip1995
[rust.git] / clippy_lints / src / unnecessary_wraps.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::source::snippet;
3 use clippy_utils::{contains_return, in_macro, match_qpath, paths, return_ty, visitors::find_all_ret_expressions};
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir::intravisit::FnKind;
7 use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_middle::ty;
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
11 use rustc_span::symbol::sym;
12 use rustc_span::Span;
13
14 declare_clippy_lint! {
15     /// **What it does:** Checks for private functions that only return `Ok` or `Some`.
16     ///
17     /// **Why is this bad?** It is not meaningful to wrap values when no `None` or `Err` is returned.
18     ///
19     /// **Known problems:** There can be false positives if the function signature is designed to
20     /// fit some external requirement.
21     ///
22     /// **Example:**
23     ///
24     /// ```rust
25     /// fn get_cool_number(a: bool, b: bool) -> Option<i32> {
26     ///     if a && b {
27     ///         return Some(50);
28     ///     }
29     ///     if a {
30     ///         Some(0)
31     ///     } else {
32     ///         Some(10)
33     ///     }
34     /// }
35     /// ```
36     /// Use instead:
37     /// ```rust
38     /// fn get_cool_number(a: bool, b: bool) -> i32 {
39     ///     if a && b {
40     ///         return 50;
41     ///     }
42     ///     if a {
43     ///         0
44     ///     } else {
45     ///         10
46     ///     }
47     /// }
48     /// ```
49     pub UNNECESSARY_WRAPS,
50     pedantic,
51     "functions that only return `Ok` or `Some`"
52 }
53
54 declare_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]);
55
56 impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
57     fn check_fn(
58         &mut self,
59         cx: &LateContext<'tcx>,
60         fn_kind: FnKind<'tcx>,
61         fn_decl: &FnDecl<'tcx>,
62         body: &Body<'tcx>,
63         span: Span,
64         hir_id: HirId,
65     ) {
66         // Abort if public function/method or closure.
67         match fn_kind {
68             FnKind::ItemFn(.., visibility) | FnKind::Method(.., Some(visibility)) => {
69                 if visibility.node.is_pub() {
70                     return;
71                 }
72             },
73             FnKind::Closure => return,
74             FnKind::Method(..) => (),
75         }
76
77         // Abort if the method is implementing a trait or of it a trait method.
78         if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
79             if matches!(
80                 item.kind,
81                 ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
82             ) {
83                 return;
84             }
85         }
86
87         // Get the wrapper and inner types, if can't, abort.
88         let (return_type_label, path, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
89             if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did) {
90                 ("Option", &paths::OPTION_SOME, subst.type_at(0))
91             } else if cx.tcx.is_diagnostic_item(sym::result_type, adt_def.did) {
92                 ("Result", &paths::RESULT_OK, subst.type_at(0))
93             } else {
94                 return;
95             }
96         } else {
97             return;
98         };
99
100         // Check if all return expression respect the following condition and collect them.
101         let mut suggs = Vec::new();
102         let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| {
103             if_chain! {
104                 if !in_macro(ret_expr.span);
105                 // Check if a function call.
106                 if let ExprKind::Call(func, args) = ret_expr.kind;
107                 // Get the Path of the function call.
108                 if let ExprKind::Path(ref qpath) = func.kind;
109                 // Check if OPTION_SOME or RESULT_OK, depending on return type.
110                 if match_qpath(qpath, path);
111                 if args.len() == 1;
112                 // Make sure the function argument does not contain a return expression.
113                 if !contains_return(&args[0]);
114                 then {
115                     suggs.push(
116                         (
117                             ret_expr.span,
118                             if inner_type.is_unit() {
119                                 "".to_string()
120                             } else {
121                                 snippet(cx, args[0].span.source_callsite(), "..").to_string()
122                             }
123                         )
124                     );
125                     true
126                 } else {
127                     false
128                 }
129             }
130         });
131
132         if can_sugg && !suggs.is_empty() {
133             let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() {
134                 (
135                     "this function's return value is unnecessary".to_string(),
136                     "remove the return type...".to_string(),
137                     snippet(cx, fn_decl.output.span(), "..").to_string(),
138                     "...and then remove returned values",
139                 )
140             } else {
141                 (
142                     format!(
143                         "this function's return value is unnecessarily wrapped by `{}`",
144                         return_type_label
145                     ),
146                     format!("remove `{}` from the return type...", return_type_label),
147                     inner_type.to_string(),
148                     "...and then change returning expressions",
149                 )
150             };
151
152             span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| {
153                 diag.span_suggestion(
154                     fn_decl.output.span(),
155                     return_type_sugg_msg.as_str(),
156                     return_type_sugg,
157                     Applicability::MaybeIncorrect,
158                 );
159                 diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect);
160             });
161         }
162     }
163 }