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