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