| use clippy_utils::diagnostics::span_lint_and_sugg; |
| use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span, root_macro_call_first_node}; |
| use clippy_utils::res::MaybeDef; |
| use clippy_utils::source::snippet_with_applicability; |
| use clippy_utils::visitors::for_each_expr; |
| use clippy_utils::{contains_return, is_inside_always_const_context, peel_blocks}; |
| use rustc_errors::Applicability; |
| use rustc_hir as hir; |
| use rustc_lint::LateContext; |
| use rustc_span::Span; |
| use rustc_span::symbol::sym; |
| use std::borrow::Cow; |
| use std::ops::ControlFlow; |
| |
| use super::EXPECT_FUN_CALL; |
| |
| /// Checks for the `EXPECT_FUN_CALL` lint. |
| pub(super) fn check<'tcx>( |
| cx: &LateContext<'tcx>, |
| format_args_storage: &FormatArgsStorage, |
| expr: &hir::Expr<'_>, |
| method_span: Span, |
| receiver: &'tcx hir::Expr<'tcx>, |
| arg: &'tcx hir::Expr<'tcx>, |
| ) { |
| let arg_root = get_arg_root(cx, arg); |
| if contains_call(cx, arg_root) && !contains_return(arg_root) { |
| let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver); |
| let closure_args = if receiver_type.is_diag_item(cx, sym::Option) { |
| "||" |
| } else if receiver_type.is_diag_item(cx, sym::Result) { |
| "|_|" |
| } else { |
| return; |
| }; |
| |
| let span_replace_word = method_span.with_hi(expr.span.hi()); |
| |
| let mut applicability = Applicability::MachineApplicable; |
| |
| // Special handling for `format!` as arg_root |
| if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) { |
| if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) |
| && let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn) |
| { |
| let span = format_args_inputs_span(format_args); |
| let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); |
| span_lint_and_sugg( |
| cx, |
| EXPECT_FUN_CALL, |
| span_replace_word, |
| "function call inside of `expect`", |
| "try", |
| format!("unwrap_or_else({closure_args} panic!({sugg}))"), |
| applicability, |
| ); |
| } |
| return; |
| } |
| |
| let arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); |
| |
| span_lint_and_sugg( |
| cx, |
| EXPECT_FUN_CALL, |
| span_replace_word, |
| "function call inside of `expect`", |
| "try", |
| format!("unwrap_or_else({closure_args} panic!(\"{{}}\", {arg_root_snippet}))"), |
| applicability, |
| ); |
| } |
| } |
| |
| /// Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or |
| /// `&str` |
| fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { |
| let mut arg_root = peel_blocks(arg); |
| loop { |
| arg_root = match &arg_root.kind { |
| hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, |
| hir::ExprKind::MethodCall(method_name, receiver, [], ..) => { |
| if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && { |
| let arg_type = cx.typeck_results().expr_ty(receiver); |
| let base_type = arg_type.peel_refs(); |
| base_type.is_str() || base_type.is_lang_item(cx, hir::LangItem::String) |
| } { |
| receiver |
| } else { |
| break; |
| } |
| }, |
| _ => break, |
| }; |
| } |
| arg_root |
| } |
| |
| fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool { |
| for_each_expr(cx, arg, |expr| { |
| if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. }) |
| && !is_inside_always_const_context(cx.tcx, expr.hir_id) |
| { |
| ControlFlow::Break(()) |
| } else { |
| ControlFlow::Continue(()) |
| } |
| }) |
| .is_some() |
| } |