]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/unnecessary_wraps.rs
Auto merge of #81135 - jyn514:no-backticks, r=flip1995
[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, Impl, 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::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:** Since this lint changes function type signature, you may need to
21     /// adjust some code at callee side.
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     complexity,
52     "functions that only return `Ok` or `Some`"
53 }
54
55 declare_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]);
56
57 impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
58     fn check_fn(
59         &mut self,
60         cx: &LateContext<'tcx>,
61         fn_kind: FnKind<'tcx>,
62         fn_decl: &FnDecl<'tcx>,
63         body: &Body<'tcx>,
64         span: Span,
65         hir_id: HirId,
66     ) {
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             _ => (),
75         }
76
77         if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
78             if matches!(
79                 item.kind,
80                 ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
81             ) {
82                 return;
83             }
84         }
85
86         let (return_type, path) = if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::option_type) {
87             ("Option", &paths::OPTION_SOME)
88         } else if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::result_type) {
89             ("Result", &paths::RESULT_OK)
90         } else {
91             return;
92         };
93
94         let mut suggs = Vec::new();
95         let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| {
96             if_chain! {
97                 if !in_macro(ret_expr.span);
98                 if let ExprKind::Call(ref func, ref args) = ret_expr.kind;
99                 if let ExprKind::Path(ref qpath) = func.kind;
100                 if match_qpath(qpath, path);
101                 if args.len() == 1;
102                 if !contains_return(&args[0]);
103                 then {
104                     suggs.push((ret_expr.span, snippet(cx, args[0].span.source_callsite(), "..").to_string()));
105                     true
106                 } else {
107                     false
108                 }
109             }
110         });
111
112         if can_sugg && !suggs.is_empty() {
113             span_lint_and_then(
114                 cx,
115                 UNNECESSARY_WRAPS,
116                 span,
117                 format!(
118                     "this function's return value is unnecessarily wrapped by `{}`",
119                     return_type
120                 )
121                 .as_str(),
122                 |diag| {
123                     let inner_ty = return_ty(cx, hir_id)
124                         .walk()
125                         .skip(1) // skip `std::option::Option` or `std::result::Result`
126                         .take(1) // take the first outermost inner type
127                         .filter_map(|inner| match inner.unpack() {
128                             GenericArgKind::Type(inner_ty) => Some(inner_ty.to_string()),
129                             _ => None,
130                         });
131                     inner_ty.for_each(|inner_ty| {
132                         diag.span_suggestion(
133                             fn_decl.output.span(),
134                             format!("remove `{}` from the return type...", return_type).as_str(),
135                             inner_ty,
136                             Applicability::MaybeIncorrect,
137                         );
138                     });
139                     diag.multipart_suggestion(
140                         "...and change the returning expressions",
141                         suggs,
142                         Applicability::MaybeIncorrect,
143                     );
144                 },
145             );
146         }
147     }
148 }