]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
New upstream version 1.66.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / methods / expect_fun_call.rs
CommitLineData
cdc7bbd5 1use clippy_utils::diagnostics::span_lint_and_sugg;
5099ac24 2use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
136023e0 3use clippy_utils::source::snippet_with_applicability;
cdc7bbd5 4use clippy_utils::ty::is_type_diagnostic_item;
f20569fa
XL
5use rustc_errors::Applicability;
6use rustc_hir as hir;
7use rustc_lint::LateContext;
8use rustc_middle::ty;
9use rustc_span::source_map::Span;
10use rustc_span::symbol::sym;
11use std::borrow::Cow;
12
13use super::EXPECT_FUN_CALL;
14
15/// Checks for the `EXPECT_FUN_CALL` lint.
16#[allow(clippy::too_many_lines)]
5099ac24
FG
17pub(super) fn check<'tcx>(
18 cx: &LateContext<'tcx>,
19 expr: &hir::Expr<'_>,
20 method_span: Span,
21 name: &str,
f2b60f7d 22 receiver: &'tcx hir::Expr<'tcx>,
5099ac24
FG
23 args: &'tcx [hir::Expr<'tcx>],
24) {
f20569fa
XL
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,
f2b60f7d
FG
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
f20569fa
XL
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);
c295e0f8 53 if is_type_diagnostic_item(cx, arg_ty, sym::String) {
f20569fa
XL
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(),
5099ac24 74 ty::Ref(re, ..) if re.is_static(),
f20569fa
XL
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(),
5099ac24 88 ty::Ref(re, ..) if re.is_static()
f20569fa
XL
89 )
90 })
91 },
92 hir::ExprKind::Path(ref p) => matches!(
93 cx.qpath_res(p, arg.hir_id),
5e7ed085 94 hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static(_), _)
f20569fa
XL
95 ),
96 _ => false,
97 }
98 }
99
f20569fa
XL
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
f2b60f7d 115 if args.len() != 1 || name != "expect" || !is_call(&args[0].kind) {
f20569fa
XL
116 return;
117 }
118
f2b60f7d 119 let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver);
c295e0f8 120 let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) {
f20569fa 121 "||"
c295e0f8 122 } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) {
f20569fa
XL
123 "|_|"
124 } else {
125 return;
126 };
127
f2b60f7d 128 let arg_root = get_arg_root(cx, &args[0]);
f20569fa
XL
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
5099ac24
FG
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();
136023e0
XL
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,
2b03887a 146 &format!("use of `{name}` followed by a function call"),
136023e0 147 "try this",
2b03887a 148 format!("unwrap_or_else({closure_args} panic!({sugg}))"),
136023e0
XL
149 applicability,
150 );
151 return;
f20569fa
XL
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,
2b03887a 163 &format!("use of `{name}` followed by a function call"),
f20569fa 164 "try this",
2b03887a 165 format!("unwrap_or_else({closure_args} {{ panic!(\"{{}}\", {arg_root_snippet}) }})"),
f20569fa
XL
166 applicability,
167 );
168}