]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/expect_fun_call.rs
Destructure args in methods module
[rust.git] / clippy_lints / src / methods / expect_fun_call.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::is_expn_of;
3 use clippy_utils::source::{snippet, snippet_with_applicability};
4 use clippy_utils::ty::is_type_diagnostic_item;
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir as hir;
8 use rustc_lint::LateContext;
9 use rustc_middle::ty;
10 use rustc_span::source_map::Span;
11 use rustc_span::symbol::sym;
12 use std::borrow::Cow;
13
14 use super::EXPECT_FUN_CALL;
15
16 /// Checks for the `EXPECT_FUN_CALL` lint.
17 #[allow(clippy::too_many_lines)]
18 pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span, name: &str, args: &[hir::Expr<'_>]) {
19     // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
20     // `&str`
21     fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
22         let mut arg_root = arg;
23         loop {
24             arg_root = match &arg_root.kind {
25                 hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
26                 hir::ExprKind::MethodCall(method_name, _, call_args, _) => {
27                     if call_args.len() == 1
28                         && (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref))
29                         && {
30                             let arg_type = cx.typeck_results().expr_ty(&call_args[0]);
31                             let base_type = arg_type.peel_refs();
32                             *base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::string_type)
33                         }
34                     {
35                         &call_args[0]
36                     } else {
37                         break;
38                     }
39                 },
40                 _ => break,
41             };
42         }
43         arg_root
44     }
45
46     // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
47     // converted to string.
48     fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
49         let arg_ty = cx.typeck_results().expr_ty(arg);
50         if is_type_diagnostic_item(cx, arg_ty, sym::string_type) {
51             return false;
52         }
53         if let ty::Ref(_, ty, ..) = arg_ty.kind() {
54             if *ty.kind() == ty::Str && can_be_static_str(cx, arg) {
55                 return false;
56             }
57         };
58         true
59     }
60
61     // Check if an expression could have type `&'static str`, knowing that it
62     // has type `&str` for some lifetime.
63     fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
64         match arg.kind {
65             hir::ExprKind::Lit(_) => true,
66             hir::ExprKind::Call(fun, _) => {
67                 if let hir::ExprKind::Path(ref p) = fun.kind {
68                     match cx.qpath_res(p, fun.hir_id) {
69                         hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
70                             cx.tcx.fn_sig(def_id).output().skip_binder().kind(),
71                             ty::Ref(ty::ReStatic, ..)
72                         ),
73                         _ => false,
74                     }
75                 } else {
76                     false
77                 }
78             },
79             hir::ExprKind::MethodCall(..) => {
80                 cx.typeck_results()
81                     .type_dependent_def_id(arg.hir_id)
82                     .map_or(false, |method_id| {
83                         matches!(
84                             cx.tcx.fn_sig(method_id).output().skip_binder().kind(),
85                             ty::Ref(ty::ReStatic, ..)
86                         )
87                     })
88             },
89             hir::ExprKind::Path(ref p) => matches!(
90                 cx.qpath_res(p, arg.hir_id),
91                 hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _)
92             ),
93             _ => false,
94         }
95     }
96
97     fn generate_format_arg_snippet(
98         cx: &LateContext<'_>,
99         a: &hir::Expr<'_>,
100         applicability: &mut Applicability,
101     ) -> Vec<String> {
102         if_chain! {
103             if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind;
104             if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind;
105             if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind;
106
107             then {
108                 format_arg_expr_tup
109                     .iter()
110                     .map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned())
111                     .collect()
112             } else {
113                 unreachable!()
114             }
115         }
116     }
117
118     fn is_call(node: &hir::ExprKind<'_>) -> bool {
119         match node {
120             hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
121                 is_call(&expr.kind)
122             },
123             hir::ExprKind::Call(..)
124             | hir::ExprKind::MethodCall(..)
125             // These variants are debatable or require further examination
126             | hir::ExprKind::If(..)
127             | hir::ExprKind::Match(..)
128             | hir::ExprKind::Block{ .. } => true,
129             _ => false,
130         }
131     }
132
133     if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) {
134         return;
135     }
136
137     let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]);
138     let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::option_type) {
139         "||"
140     } else if is_type_diagnostic_item(cx, receiver_type, sym::result_type) {
141         "|_|"
142     } else {
143         return;
144     };
145
146     let arg_root = get_arg_root(cx, &args[1]);
147
148     let span_replace_word = method_span.with_hi(expr.span.hi());
149
150     let mut applicability = Applicability::MachineApplicable;
151
152     //Special handling for `format!` as arg_root
153     if_chain! {
154         if let hir::ExprKind::Block(block, None) = &arg_root.kind;
155         if block.stmts.len() == 1;
156         if let hir::StmtKind::Local(local) = &block.stmts[0].kind;
157         if let Some(arg_root) = &local.init;
158         if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind;
159         if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1;
160         if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind;
161         then {
162             let fmt_spec = &format_args[0];
163             let fmt_args = &format_args[1];
164
165             let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()];
166
167             args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability));
168
169             let sugg = args.join(", ");
170
171             span_lint_and_sugg(
172                 cx,
173                 EXPECT_FUN_CALL,
174                 span_replace_word,
175                 &format!("use of `{}` followed by a function call", name),
176                 "try this",
177                 format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
178                 applicability,
179             );
180
181             return;
182         }
183     }
184
185     let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
186     if requires_to_string(cx, arg_root) {
187         arg_root_snippet.to_mut().push_str(".to_string()");
188     }
189
190     span_lint_and_sugg(
191         cx,
192         EXPECT_FUN_CALL,
193         span_replace_word,
194         &format!("use of `{}` followed by a function call", name),
195         "try this",
196         format!(
197             "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})",
198             closure_args, arg_root_snippet
199         ),
200         applicability,
201     );
202 }