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