blob: 288f966991acb0748d92aad87695d2f370e2f86f [file] [log] [blame] [edit]
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()
}