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;
7 use rustc_lint::LateContext;
9 use rustc_span::source_map::Span;
10 use rustc_span::symbol::sym;
13 use super::EXPECT_FUN_CALL;
15 /// Checks for the `EXPECT_FUN_CALL` lint.
16 #[allow(clippy::too_many_lines)]
17 pub(super) fn check<'tcx>(
18 cx: &LateContext<'tcx>,
22 args: &'tcx [hir::Expr<'tcx>],
24 // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
26 fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
27 let mut arg_root = arg;
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)
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)
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) {
58 if let ty::Ref(_, ty, ..) = arg_ty.kind() {
59 if *ty.kind() == ty::Str && can_be_static_str(cx, arg) {
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 {
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(),
84 hir::ExprKind::MethodCall(..) => {
86 .type_dependent_def_id(arg.hir_id)
87 .map_or(false, |method_id| {
89 cx.tcx.fn_sig(method_id).output().skip_binder().kind(),
90 ty::Ref(re, ..) if re.is_static()
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, _)
102 fn is_call(node: &hir::ExprKind<'_>) -> bool {
104 hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
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,
117 if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) {
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) {
124 } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) {
130 let arg_root = get_arg_root(cx, &args[1]);
132 let span_replace_word = method_span.with_hi(expr.span.hi());
134 let mut applicability = Applicability::MachineApplicable;
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) {
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);
148 &format!("use of `{}` followed by a function call", name),
150 format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
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()");
165 &format!("use of `{}` followed by a function call", name),
168 "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})",
169 closure_args, arg_root_snippet