]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::utils::eager_or_lazy::is_lazyness_candidate; |
2 | use crate::utils::{ | |
3 | contains_return, get_trait_def_id, implements_trait, is_type_diagnostic_item, last_path_segment, match_type, paths, | |
4 | snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint_and_sugg, | |
5 | }; | |
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_middle::ty; | |
11 | use rustc_span::source_map::Span; | |
12 | use rustc_span::symbol::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 | fn check_unwrap_or_default( | |
28 | cx: &LateContext<'_>, | |
29 | name: &str, | |
30 | fun: &hir::Expr<'_>, | |
31 | self_expr: &hir::Expr<'_>, | |
32 | arg: &hir::Expr<'_>, | |
33 | or_has_args: bool, | |
34 | span: Span, | |
35 | ) -> bool { | |
36 | if_chain! { | |
37 | if !or_has_args; | |
38 | if name == "unwrap_or"; | |
39 | if let hir::ExprKind::Path(ref qpath) = fun.kind; | |
40 | let path = &*last_path_segment(qpath).ident.as_str(); | |
41 | if ["default", "new"].contains(&path); | |
42 | let arg_ty = cx.typeck_results().expr_ty(arg); | |
43 | if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); | |
44 | if implements_trait(cx, arg_ty, default_trait_id, &[]); | |
45 | ||
46 | then { | |
47 | let mut applicability = Applicability::MachineApplicable; | |
48 | span_lint_and_sugg( | |
49 | cx, | |
50 | OR_FUN_CALL, | |
51 | span, | |
52 | &format!("use of `{}` followed by a call to `{}`", name, path), | |
53 | "try this", | |
54 | format!( | |
55 | "{}.unwrap_or_default()", | |
56 | snippet_with_applicability(cx, self_expr.span, "..", &mut applicability) | |
57 | ), | |
58 | applicability, | |
59 | ); | |
60 | ||
61 | true | |
62 | } else { | |
63 | false | |
64 | } | |
65 | } | |
66 | } | |
67 | ||
68 | /// Checks for `*or(foo())`. | |
69 | #[allow(clippy::too_many_arguments)] | |
70 | fn check_general_case<'tcx>( | |
71 | cx: &LateContext<'tcx>, | |
72 | name: &str, | |
73 | method_span: Span, | |
74 | self_expr: &hir::Expr<'_>, | |
75 | arg: &'tcx hir::Expr<'_>, | |
76 | span: Span, | |
77 | // None if lambda is required | |
78 | fun_span: Option<Span>, | |
79 | ) { | |
80 | // (path, fn_has_argument, methods, suffix) | |
81 | static KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [ | |
82 | (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"), | |
83 | (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"), | |
84 | (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"), | |
85 | (&paths::RESULT, true, &["or", "unwrap_or"], "else"), | |
86 | ]; | |
87 | ||
88 | if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = &arg.kind { | |
89 | if path.ident.as_str() == "len" { | |
90 | let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); | |
91 | ||
92 | match ty.kind() { | |
93 | ty::Slice(_) | ty::Array(_, _) => return, | |
94 | _ => (), | |
95 | } | |
96 | ||
97 | if is_type_diagnostic_item(cx, ty, sym::vec_type) { | |
98 | return; | |
99 | } | |
100 | } | |
101 | } | |
102 | ||
103 | if_chain! { | |
104 | if KNOW_TYPES.iter().any(|k| k.2.contains(&name)); | |
105 | ||
106 | if is_lazyness_candidate(cx, arg); | |
107 | if !contains_return(&arg); | |
108 | ||
109 | let self_ty = cx.typeck_results().expr_ty(self_expr); | |
110 | ||
111 | if let Some(&(_, fn_has_arguments, poss, suffix)) = | |
112 | KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0)); | |
113 | ||
114 | if poss.contains(&name); | |
115 | ||
116 | then { | |
117 | let macro_expanded_snipped; | |
118 | let sugg: Cow<'_, str> = { | |
119 | let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { | |
120 | (false, Some(fun_span)) => (fun_span, false), | |
121 | _ => (arg.span, true), | |
122 | }; | |
123 | let snippet = { | |
124 | let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, ".."); | |
125 | if not_macro_argument_snippet == "vec![]" { | |
126 | macro_expanded_snipped = snippet(cx, snippet_span, ".."); | |
127 | match macro_expanded_snipped.strip_prefix("$crate::vec::") { | |
128 | Some(stripped) => Cow::from(stripped), | |
129 | None => macro_expanded_snipped | |
130 | } | |
131 | } | |
132 | else { | |
133 | not_macro_argument_snippet | |
134 | } | |
135 | }; | |
136 | ||
137 | if use_lambda { | |
138 | let l_arg = if fn_has_arguments { "_" } else { "" }; | |
139 | format!("|{}| {}", l_arg, snippet).into() | |
140 | } else { | |
141 | snippet | |
142 | } | |
143 | }; | |
144 | let span_replace_word = method_span.with_hi(span.hi()); | |
145 | span_lint_and_sugg( | |
146 | cx, | |
147 | OR_FUN_CALL, | |
148 | span_replace_word, | |
149 | &format!("use of `{}` followed by a function call", name), | |
150 | "try this", | |
151 | format!("{}_{}({})", name, suffix, sugg), | |
152 | Applicability::HasPlaceholders, | |
153 | ); | |
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 | _ => {}, | |
171 | } | |
172 | } | |
173 | } |