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