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