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