]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
Rollup merge of #105523 - estebank:suggest-collect-vec, r=compiler-errors
[rust.git] / src / tools / clippy / 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_macro_callsite};
4 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
5 use clippy_utils::{contains_return, is_trait_item, last_path_segment};
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_span::source_map::Span;
11 use rustc_span::symbol::{kw, sym, Symbol};
12 use std::borrow::Cow;
13
14 use super::OR_FUN_CALL;
15
16 /// Checks for the `OR_FUN_CALL` lint.
17 #[allow(clippy::too_many_lines)]
18 pub(super) fn check<'tcx>(
19     cx: &LateContext<'tcx>,
20     expr: &hir::Expr<'_>,
21     method_span: Span,
22     name: &str,
23     receiver: &'tcx hir::Expr<'_>,
24     args: &'tcx [hir::Expr<'_>],
25 ) {
26     /// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
27     /// `or_insert(T::new())` or `or_insert(T::default())`.
28     #[allow(clippy::too_many_arguments)]
29     fn check_unwrap_or_default(
30         cx: &LateContext<'_>,
31         name: &str,
32         fun: &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 let Some(sugg) = match name {
48                 "unwrap_or" => Some("unwrap_or_default"),
49                 "or_insert" => Some("or_default"),
50                 _ => None,
51             };
52             if let hir::ExprKind::Path(ref qpath) = fun.kind;
53             if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
54             let path = last_path_segment(qpath).ident.name;
55             // needs to target Default::default in particular or be *::new and have a Default impl
56             // available
57             if (matches!(path, kw::Default) && is_default_default())
58                 || (matches!(path, sym::new) && implements_default(arg, default_trait_id));
59
60             then {
61                 span_lint_and_sugg(
62                     cx,
63                     OR_FUN_CALL,
64                     method_span.with_hi(span.hi()),
65                     &format!("use of `{name}` followed by a call to `{path}`"),
66                     "try this",
67                     format!("{sugg}()"),
68                     Applicability::MachineApplicable,
69                 );
70
71                 true
72             } else {
73                 false
74             }
75         }
76     }
77
78     /// Checks for `*or(foo())`.
79     #[allow(clippy::too_many_arguments)]
80     fn check_general_case<'tcx>(
81         cx: &LateContext<'tcx>,
82         name: &str,
83         method_span: Span,
84         self_expr: &hir::Expr<'_>,
85         arg: &'tcx hir::Expr<'_>,
86         // `Some` if fn has second argument
87         second_arg: Option<&hir::Expr<'_>>,
88         span: Span,
89         // None if lambda is required
90         fun_span: Option<Span>,
91     ) {
92         // (path, fn_has_argument, methods, suffix)
93         const KNOW_TYPES: [(Symbol, bool, &[&str], &str); 4] = [
94             (sym::BTreeEntry, false, &["or_insert"], "with"),
95             (sym::HashMapEntry, false, &["or_insert"], "with"),
96             (sym::Option, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
97             (sym::Result, true, &["or", "unwrap_or"], "else"),
98         ];
99
100         if_chain! {
101             if KNOW_TYPES.iter().any(|k| k.2.contains(&name));
102
103             if switch_to_lazy_eval(cx, arg);
104             if !contains_return(arg);
105
106             let self_ty = cx.typeck_results().expr_ty(self_expr);
107
108             if let Some(&(_, fn_has_arguments, poss, suffix)) =
109                 KNOW_TYPES.iter().find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0));
110
111             if poss.contains(&name);
112
113             then {
114                 let sugg = {
115                     let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
116                         (false, Some(fun_span)) => (fun_span, false),
117                         _ => (arg.span, true),
118                     };
119
120                     let format_span = |span: Span| {
121                         let not_macro_argument_snippet = snippet_with_macro_callsite(cx, span, "..");
122                         let snip = if not_macro_argument_snippet == "vec![]" {
123                             let macro_expanded_snipped = snippet(cx, snippet_span, "..");
124                             match macro_expanded_snipped.strip_prefix("$crate::vec::") {
125                                 Some(stripped) => Cow::Owned(stripped.to_owned()),
126                                 None => macro_expanded_snipped,
127                             }
128                         } else {
129                             not_macro_argument_snippet
130                         };
131
132                         snip.to_string()
133                     };
134
135                     let snip = format_span(snippet_span);
136                     let snip = if use_lambda {
137                         let l_arg = if fn_has_arguments { "_" } else { "" };
138                         format!("|{l_arg}| {snip}")
139                     } else {
140                         snip
141                     };
142
143                     if let Some(f) = second_arg {
144                         let f = format_span(f.span);
145                         format!("{snip}, {f}")
146                     } else {
147                         snip
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 `{name}` followed by a function call"),
156                     "try this",
157                     format!("{name}_{suffix}({sugg})"),
158                     Applicability::HasPlaceholders,
159                 );
160             }
161         }
162     }
163
164     let extract_inner_arg = |arg: &'tcx hir::Expr<'_>| {
165         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     };
179
180     if let [arg] = args {
181         let inner_arg = extract_inner_arg(arg);
182         match inner_arg.kind {
183             hir::ExprKind::Call(fun, or_args) => {
184                 let or_has_args = !or_args.is_empty();
185                 if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
186                     let fun_span = if or_has_args { None } else { Some(fun.span) };
187                     check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span);
188                 }
189             },
190             hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
191                 check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
192             },
193             _ => (),
194         }
195     }
196
197     // `map_or` takes two arguments
198     if let [arg, lambda] = args {
199         let inner_arg = extract_inner_arg(arg);
200         if let hir::ExprKind::Call(fun, or_args) = inner_arg.kind {
201             let fun_span = if or_args.is_empty() { Some(fun.span) } else { None };
202             check_general_case(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span);
203         }
204     }
205 }