]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/or_fun_call.rs
move or_fun_call to its own module
[rust.git] / clippy_lints / src / methods / or_fun_call.rs
1 use crate::utils::eager_or_lazy::is_lazyness_candidate;
2 use crate::utils::{
3     contains_return, get_trait_def_id, implements_trait, is_type_diagnostic_item, last_path_segment, match_type, paths,
4     snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint_and_sugg,
5 };
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir as hir;
9 use rustc_lint::LateContext;
10 use rustc_middle::ty;
11 use rustc_span::source_map::Span;
12 use rustc_span::symbol::sym;
13 use std::borrow::Cow;
14
15 use super::OR_FUN_CALL;
16
17 /// Checks for the `OR_FUN_CALL` lint.
18 #[allow(clippy::too_many_lines)]
19 pub(super) fn check<'tcx>(
20     cx: &LateContext<'tcx>,
21     expr: &hir::Expr<'_>,
22     method_span: Span,
23     name: &str,
24     args: &'tcx [hir::Expr<'_>],
25 ) {
26     /// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`.
27     fn check_unwrap_or_default(
28         cx: &LateContext<'_>,
29         name: &str,
30         fun: &hir::Expr<'_>,
31         self_expr: &hir::Expr<'_>,
32         arg: &hir::Expr<'_>,
33         or_has_args: bool,
34         span: Span,
35     ) -> bool {
36         if_chain! {
37             if !or_has_args;
38             if name == "unwrap_or";
39             if let hir::ExprKind::Path(ref qpath) = fun.kind;
40             let path = &*last_path_segment(qpath).ident.as_str();
41             if ["default", "new"].contains(&path);
42             let arg_ty = cx.typeck_results().expr_ty(arg);
43             if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT);
44             if implements_trait(cx, arg_ty, default_trait_id, &[]);
45
46             then {
47                 let mut applicability = Applicability::MachineApplicable;
48                 span_lint_and_sugg(
49                     cx,
50                     OR_FUN_CALL,
51                     span,
52                     &format!("use of `{}` followed by a call to `{}`", name, path),
53                     "try this",
54                     format!(
55                         "{}.unwrap_or_default()",
56                         snippet_with_applicability(cx, self_expr.span, "..", &mut applicability)
57                     ),
58                     applicability,
59                 );
60
61                 true
62             } else {
63                 false
64             }
65         }
66     }
67
68     /// Checks for `*or(foo())`.
69     #[allow(clippy::too_many_arguments)]
70     fn check_general_case<'tcx>(
71         cx: &LateContext<'tcx>,
72         name: &str,
73         method_span: Span,
74         self_expr: &hir::Expr<'_>,
75         arg: &'tcx hir::Expr<'_>,
76         span: Span,
77         // None if lambda is required
78         fun_span: Option<Span>,
79     ) {
80         // (path, fn_has_argument, methods, suffix)
81         static KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [
82             (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"),
83             (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"),
84             (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
85             (&paths::RESULT, true, &["or", "unwrap_or"], "else"),
86         ];
87
88         if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = &arg.kind {
89             if path.ident.as_str() == "len" {
90                 let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs();
91
92                 match ty.kind() {
93                     ty::Slice(_) | ty::Array(_, _) => return,
94                     _ => (),
95                 }
96
97                 if is_type_diagnostic_item(cx, ty, sym::vec_type) {
98                     return;
99                 }
100             }
101         }
102
103         if_chain! {
104             if KNOW_TYPES.iter().any(|k| k.2.contains(&name));
105
106             if is_lazyness_candidate(cx, arg);
107             if !contains_return(&arg);
108
109             let self_ty = cx.typeck_results().expr_ty(self_expr);
110
111             if let Some(&(_, fn_has_arguments, poss, suffix)) =
112                 KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0));
113
114             if poss.contains(&name);
115
116             then {
117                 let macro_expanded_snipped;
118                 let sugg: Cow<'_, str> = {
119                     let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
120                         (false, Some(fun_span)) => (fun_span, false),
121                         _ => (arg.span, true),
122                     };
123                     let snippet = {
124                         let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, "..");
125                         if not_macro_argument_snippet == "vec![]" {
126                             macro_expanded_snipped = snippet(cx, snippet_span, "..");
127                             match macro_expanded_snipped.strip_prefix("$crate::vec::") {
128                                 Some(stripped) => Cow::from(stripped),
129                                 None => macro_expanded_snipped
130                             }
131                         }
132                         else {
133                             not_macro_argument_snippet
134                         }
135                     };
136
137                     if use_lambda {
138                         let l_arg = if fn_has_arguments { "_" } else { "" };
139                         format!("|{}| {}", l_arg, snippet).into()
140                     } else {
141                         snippet
142                     }
143                 };
144                 let span_replace_word = method_span.with_hi(span.hi());
145                 span_lint_and_sugg(
146                     cx,
147                     OR_FUN_CALL,
148                     span_replace_word,
149                     &format!("use of `{}` followed by a function call", name),
150                     "try this",
151                     format!("{}_{}({})", name, suffix, sugg),
152                     Applicability::HasPlaceholders,
153                 );
154             }
155         }
156     }
157
158     if args.len() == 2 {
159         match args[1].kind {
160             hir::ExprKind::Call(ref fun, ref or_args) => {
161                 let or_has_args = !or_args.is_empty();
162                 if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) {
163                     let fun_span = if or_has_args { None } else { Some(fun.span) };
164                     check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, fun_span);
165                 }
166             },
167             hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
168                 check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None);
169             },
170             _ => {},
171         }
172     }
173 }