]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/or_fun_call.rs
Remove a span from hir::ExprKind::MethodCall
[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::switch_to_lazy_eval;
3 use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_macro_callsite};
4 use clippy_utils::ty::{implements_trait, match_type};
5 use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
6 use if_chain::if_chain;
7 use rustc_errors::emitter::MAX_SUGGESTION_HIGHLIGHT_LINES;
8 use rustc_errors::Applicability;
9 use rustc_hir as hir;
10 use rustc_lint::LateContext;
11 use rustc_span::source_map::Span;
12 use rustc_span::symbol::{kw, 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     #[allow(clippy::too_many_arguments)]
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         method_span: Span,
37     ) -> bool {
38         let is_default_default = || is_trait_item(cx, fun, sym::Default);
39
40         let implements_default = |arg, default_trait_id| {
41             let arg_ty = cx.typeck_results().expr_ty(arg);
42             implements_trait(cx, arg_ty, default_trait_id, &[])
43         };
44
45         if_chain! {
46             if !or_has_args;
47             if name == "unwrap_or";
48             if let hir::ExprKind::Path(ref qpath) = fun.kind;
49             if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
50             let path = last_path_segment(qpath).ident.name;
51             // needs to target Default::default in particular or be *::new and have a Default impl
52             // available
53             if (matches!(path, kw::Default) && is_default_default())
54                 || (matches!(path, sym::new) && implements_default(arg, default_trait_id));
55
56             then {
57                 let mut applicability = Applicability::MachineApplicable;
58                 let hint = "unwrap_or_default()";
59                 let mut sugg_span = span;
60
61                 let mut sugg: String = format!(
62                     "{}.{}",
63                     snippet_with_applicability(cx, self_expr.span, "..", &mut applicability),
64                     hint
65                 );
66
67                 if sugg.lines().count() > MAX_SUGGESTION_HIGHLIGHT_LINES {
68                     sugg_span = method_span.with_hi(span.hi());
69                     sugg = hint.to_string();
70                 }
71
72                 span_lint_and_sugg(
73                     cx,
74                     OR_FUN_CALL,
75                     sugg_span,
76                     &format!("use of `{}` followed by a call to `{}`", name, path),
77                     "try this",
78                     sugg,
79                     applicability,
80                 );
81
82                 true
83             } else {
84                 false
85             }
86         }
87     }
88
89     /// Checks for `*or(foo())`.
90     #[allow(clippy::too_many_arguments)]
91     fn check_general_case<'tcx>(
92         cx: &LateContext<'tcx>,
93         name: &str,
94         method_span: Span,
95         self_expr: &hir::Expr<'_>,
96         arg: &'tcx hir::Expr<'_>,
97         span: Span,
98         // None if lambda is required
99         fun_span: Option<Span>,
100     ) {
101         // (path, fn_has_argument, methods, suffix)
102         static KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [
103             (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"),
104             (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"),
105             (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
106             (&paths::RESULT, true, &["or", "unwrap_or"], "else"),
107         ];
108
109         if_chain! {
110             if KNOW_TYPES.iter().any(|k| k.2.contains(&name));
111
112             if switch_to_lazy_eval(cx, arg);
113             if !contains_return(arg);
114
115             let self_ty = cx.typeck_results().expr_ty(self_expr);
116
117             if let Some(&(_, fn_has_arguments, poss, suffix)) =
118                 KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0));
119
120             if poss.contains(&name);
121
122             then {
123                 let macro_expanded_snipped;
124                 let sugg: Cow<'_, str> = {
125                     let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
126                         (false, Some(fun_span)) => (fun_span, false),
127                         _ => (arg.span, true),
128                     };
129                     let snippet = {
130                         let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, "..");
131                         if not_macro_argument_snippet == "vec![]" {
132                             macro_expanded_snipped = snippet(cx, snippet_span, "..");
133                             match macro_expanded_snipped.strip_prefix("$crate::vec::") {
134                                 Some(stripped) => Cow::from(stripped),
135                                 None => macro_expanded_snipped
136                             }
137                         }
138                         else {
139                             not_macro_argument_snippet
140                         }
141                     };
142
143                     if use_lambda {
144                         let l_arg = if fn_has_arguments { "_" } else { "" };
145                         format!("|{}| {}", l_arg, snippet).into()
146                     } else {
147                         snippet
148                     }
149                 };
150                 let span_replace_word = method_span.with_hi(span.hi());
151                 span_lint_and_sugg(
152                     cx,
153                     OR_FUN_CALL,
154                     span_replace_word,
155                     &format!("use of `{}` followed by a function call", name),
156                     "try this",
157                     format!("{}_{}({})", name, suffix, sugg),
158                     Applicability::HasPlaceholders,
159                 );
160             }
161         }
162     }
163
164     if let [self_arg, arg] = args {
165         let inner_arg = if let hir::ExprKind::Block(
166             hir::Block {
167                 stmts: [],
168                 expr: Some(expr),
169                 ..
170             },
171             _,
172         ) = arg.kind
173         {
174             expr
175         } else {
176             arg
177         };
178         match inner_arg.kind {
179             hir::ExprKind::Call(fun, or_args) => {
180                 let or_has_args = !or_args.is_empty();
181                 if !check_unwrap_or_default(cx, name, fun, self_arg, arg, or_has_args, expr.span, method_span) {
182                     let fun_span = if or_has_args { None } else { Some(fun.span) };
183                     check_general_case(cx, name, method_span, self_arg, arg, expr.span, fun_span);
184                 }
185             },
186             hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
187                 check_general_case(cx, name, method_span, self_arg, arg, expr.span, None);
188             },
189             _ => (),
190         }
191     }
192 }