]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
a2a8927a | 2 | use clippy_utils::eager_or_lazy::switch_to_lazy_eval; |
064997fb | 3 | use clippy_utils::source::{snippet, snippet_with_macro_callsite}; |
a2a8927a XL |
4 | use clippy_utils::ty::{implements_trait, match_type}; |
5 | use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths}; | |
f20569fa XL |
6 | use if_chain::if_chain; |
7 | use rustc_errors::Applicability; | |
8 | use rustc_hir as hir; | |
9 | use rustc_lint::LateContext; | |
f20569fa | 10 | use rustc_span::source_map::Span; |
cdc7bbd5 | 11 | use rustc_span::symbol::{kw, sym}; |
f20569fa XL |
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, | |
f2b60f7d | 23 | receiver: &'tcx hir::Expr<'_>, |
f20569fa XL |
24 | args: &'tcx [hir::Expr<'_>], |
25 | ) { | |
f2b60f7d FG |
26 | /// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`, |
27 | /// `or_insert(T::new())` or `or_insert(T::default())`. | |
5099ac24 | 28 | #[allow(clippy::too_many_arguments)] |
f20569fa XL |
29 | fn check_unwrap_or_default( |
30 | cx: &LateContext<'_>, | |
31 | name: &str, | |
32 | fun: &hir::Expr<'_>, | |
f20569fa XL |
33 | arg: &hir::Expr<'_>, |
34 | or_has_args: bool, | |
35 | span: Span, | |
5099ac24 | 36 | method_span: Span, |
f20569fa | 37 | ) -> bool { |
94222f64 XL |
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 | ||
f20569fa XL |
45 | if_chain! { |
46 | if !or_has_args; | |
f2b60f7d FG |
47 | if let Some(sugg) = match name { |
48 | "unwrap_or" => Some("unwrap_or_default"), | |
49 | "or_insert" => Some("or_default"), | |
50 | _ => None, | |
51 | }; | |
f20569fa | 52 | if let hir::ExprKind::Path(ref qpath) = fun.kind; |
94222f64 | 53 | if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default); |
cdc7bbd5 | 54 | let path = last_path_segment(qpath).ident.name; |
94222f64 XL |
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)); | |
f20569fa XL |
59 | |
60 | then { | |
f20569fa XL |
61 | span_lint_and_sugg( |
62 | cx, | |
63 | OR_FUN_CALL, | |
064997fb | 64 | method_span.with_hi(span.hi()), |
f20569fa XL |
65 | &format!("use of `{}` followed by a call to `{}`", name, path), |
66 | "try this", | |
f2b60f7d | 67 | format!("{}()", sugg), |
064997fb | 68 | Applicability::MachineApplicable, |
f20569fa XL |
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 | span: Span, | |
87 | // None if lambda is required | |
88 | fun_span: Option<Span>, | |
89 | ) { | |
90 | // (path, fn_has_argument, methods, suffix) | |
f2b60f7d | 91 | const KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [ |
f20569fa XL |
92 | (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"), |
93 | (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"), | |
94 | (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"), | |
95 | (&paths::RESULT, true, &["or", "unwrap_or"], "else"), | |
96 | ]; | |
97 | ||
f20569fa XL |
98 | if_chain! { |
99 | if KNOW_TYPES.iter().any(|k| k.2.contains(&name)); | |
100 | ||
a2a8927a | 101 | if switch_to_lazy_eval(cx, arg); |
cdc7bbd5 | 102 | if !contains_return(arg); |
f20569fa XL |
103 | |
104 | let self_ty = cx.typeck_results().expr_ty(self_expr); | |
105 | ||
106 | if let Some(&(_, fn_has_arguments, poss, suffix)) = | |
107 | KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0)); | |
108 | ||
109 | if poss.contains(&name); | |
110 | ||
111 | then { | |
112 | let macro_expanded_snipped; | |
113 | let sugg: Cow<'_, str> = { | |
114 | let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { | |
115 | (false, Some(fun_span)) => (fun_span, false), | |
116 | _ => (arg.span, true), | |
117 | }; | |
118 | let snippet = { | |
119 | let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, ".."); | |
120 | if not_macro_argument_snippet == "vec![]" { | |
121 | macro_expanded_snipped = snippet(cx, snippet_span, ".."); | |
122 | match macro_expanded_snipped.strip_prefix("$crate::vec::") { | |
123 | Some(stripped) => Cow::from(stripped), | |
124 | None => macro_expanded_snipped | |
125 | } | |
126 | } | |
127 | else { | |
128 | not_macro_argument_snippet | |
129 | } | |
130 | }; | |
131 | ||
132 | if use_lambda { | |
133 | let l_arg = if fn_has_arguments { "_" } else { "" }; | |
134 | format!("|{}| {}", l_arg, snippet).into() | |
135 | } else { | |
136 | snippet | |
137 | } | |
138 | }; | |
139 | let span_replace_word = method_span.with_hi(span.hi()); | |
140 | span_lint_and_sugg( | |
141 | cx, | |
142 | OR_FUN_CALL, | |
143 | span_replace_word, | |
144 | &format!("use of `{}` followed by a function call", name), | |
145 | "try this", | |
146 | format!("{}_{}({})", name, suffix, sugg), | |
147 | Applicability::HasPlaceholders, | |
148 | ); | |
149 | } | |
150 | } | |
151 | } | |
152 | ||
f2b60f7d | 153 | if let [arg] = args { |
a2a8927a XL |
154 | let inner_arg = if let hir::ExprKind::Block( |
155 | hir::Block { | |
156 | stmts: [], | |
157 | expr: Some(expr), | |
158 | .. | |
159 | }, | |
160 | _, | |
161 | ) = arg.kind | |
162 | { | |
163 | expr | |
164 | } else { | |
165 | arg | |
166 | }; | |
167 | match inner_arg.kind { | |
cdc7bbd5 | 168 | hir::ExprKind::Call(fun, or_args) => { |
f20569fa | 169 | let or_has_args = !or_args.is_empty(); |
064997fb | 170 | if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) { |
f20569fa | 171 | let fun_span = if or_has_args { None } else { Some(fun.span) }; |
f2b60f7d | 172 | check_general_case(cx, name, method_span, receiver, arg, expr.span, fun_span); |
f20569fa XL |
173 | } |
174 | }, | |
175 | hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { | |
f2b60f7d | 176 | check_general_case(cx, name, method_span, receiver, arg, expr.span, None); |
cdc7bbd5 XL |
177 | }, |
178 | _ => (), | |
f20569fa XL |
179 | } |
180 | } | |
181 | } |