]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
5099ac24 | 2 | use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn}; |
136023e0 | 3 | use clippy_utils::source::snippet_with_applicability; |
cdc7bbd5 | 4 | use clippy_utils::ty::is_type_diagnostic_item; |
f20569fa XL |
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)] | |
5099ac24 FG |
17 | pub(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 | } |