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