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