]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/unnecessary_wraps.rs
Rollup merge of #79051 - LeSeulArtichaut:if-let-guard, r=matthewjasper
[rust.git] / clippy_lints / src / unnecessary_wraps.rs
1 use crate::utils::{
2     contains_return, in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_then,
3     visitors::find_all_ret_expressions,
4 };
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir::intravisit::FnKind;
8 use rustc_hir::{Body, ExprKind, FnDecl, HirId, ItemKind, Node};
9 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_middle::ty::subst::GenericArgKind;
11 use rustc_session::{declare_lint_pass, declare_tool_lint};
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:** Since this lint changes function type signature, you may need to
20     /// adjust some code at callee side.
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     complexity,
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         match fn_kind {
67             FnKind::ItemFn(.., visibility, _) | FnKind::Method(.., Some(visibility), _) => {
68                 if visibility.node.is_pub() {
69                     return;
70                 }
71             },
72             FnKind::Closure(..) => return,
73             _ => (),
74         }
75
76         if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
77             if matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), ..} | ItemKind::Trait(..)) {
78                 return;
79             }
80         }
81
82         let (return_type, path) = if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym!(option_type)) {
83             ("Option", &paths::OPTION_SOME)
84         } else if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym!(result_type)) {
85             ("Result", &paths::RESULT_OK)
86         } else {
87             return;
88         };
89
90         let mut suggs = Vec::new();
91         let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| {
92             if_chain! {
93                 if !in_macro(ret_expr.span);
94                 if let ExprKind::Call(ref func, ref args) = ret_expr.kind;
95                 if let ExprKind::Path(ref qpath) = func.kind;
96                 if match_qpath(qpath, path);
97                 if args.len() == 1;
98                 if !contains_return(&args[0]);
99                 then {
100                     suggs.push((ret_expr.span, snippet(cx, args[0].span.source_callsite(), "..").to_string()));
101                     true
102                 } else {
103                     false
104                 }
105             }
106         });
107
108         if can_sugg && !suggs.is_empty() {
109             span_lint_and_then(
110                 cx,
111                 UNNECESSARY_WRAPS,
112                 span,
113                 format!(
114                     "this function's return value is unnecessarily wrapped by `{}`",
115                     return_type
116                 )
117                 .as_str(),
118                 |diag| {
119                     let inner_ty = return_ty(cx, hir_id)
120                         .walk()
121                         .skip(1) // skip `std::option::Option` or `std::result::Result`
122                         .take(1) // take the first outermost inner type
123                         .filter_map(|inner| match inner.unpack() {
124                             GenericArgKind::Type(inner_ty) => Some(inner_ty.to_string()),
125                             _ => None,
126                         });
127                     inner_ty.for_each(|inner_ty| {
128                         diag.span_suggestion(
129                             fn_decl.output.span(),
130                             format!("remove `{}` from the return type...", return_type).as_str(),
131                             inner_ty,
132                             Applicability::MaybeIncorrect,
133                         );
134                     });
135                     diag.multipart_suggestion(
136                         "...and change the returning expressions",
137                         suggs,
138                         Applicability::MaybeIncorrect,
139                     );
140                 },
141             );
142         }
143     }
144 }