]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
New upstream version 1.53.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / methods / expect_fun_call.rs
CommitLineData
cdc7bbd5
XL
1use clippy_utils::diagnostics::span_lint_and_sugg;
2use clippy_utils::is_expn_of;
3use clippy_utils::source::{snippet, snippet_with_applicability};
4use clippy_utils::ty::is_type_diagnostic_item;
f20569fa
XL
5use if_chain::if_chain;
6use rustc_errors::Applicability;
7use rustc_hir as hir;
8use rustc_lint::LateContext;
9use rustc_middle::ty;
10use rustc_span::source_map::Span;
11use rustc_span::symbol::sym;
12use std::borrow::Cow;
13
14use super::EXPECT_FUN_CALL;
15
16/// Checks for the `EXPECT_FUN_CALL` lint.
17#[allow(clippy::too_many_lines)]
18pub(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! {
cdc7bbd5
XL
103 if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, format_arg) = a.kind;
104 if let hir::ExprKind::Match(format_arg_expr, _, _) = format_arg.kind;
105 if let hir::ExprKind::Tup(format_arg_expr_tup) = format_arg_expr.kind;
f20569fa
XL
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;
cdc7bbd5 158 if let hir::ExprKind::Call(inner_fun, inner_args) = arg_root.kind;
f20569fa
XL
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}