]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
New upstream version 1.65.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / methods / or_fun_call.rs
CommitLineData
cdc7bbd5 1use clippy_utils::diagnostics::span_lint_and_sugg;
a2a8927a 2use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
064997fb 3use clippy_utils::source::{snippet, snippet_with_macro_callsite};
a2a8927a
XL
4use clippy_utils::ty::{implements_trait, match_type};
5use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
f20569fa
XL
6use if_chain::if_chain;
7use rustc_errors::Applicability;
8use rustc_hir as hir;
9use rustc_lint::LateContext;
f20569fa 10use rustc_span::source_map::Span;
cdc7bbd5 11use rustc_span::symbol::{kw, sym};
f20569fa
XL
12use std::borrow::Cow;
13
14use super::OR_FUN_CALL;
15
16/// Checks for the `OR_FUN_CALL` lint.
17#[allow(clippy::too_many_lines)]
18pub(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}