--- /dev/null
+use crate::utils::{is_expn_of, is_type_diagnostic_item, snippet, snippet_with_applicability, span_lint_and_sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+use std::borrow::Cow;
+
+use super::EXPECT_FUN_CALL;
+
+/// Checks for the `EXPECT_FUN_CALL` lint.
+#[allow(clippy::too_many_lines)]
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span, name: &str, args: &[hir::Expr<'_>]) {
+ // 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 = arg;
+ loop {
+ arg_root = match &arg_root.kind {
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
+ hir::ExprKind::MethodCall(method_name, _, call_args, _) => {
+ if call_args.len() == 1
+ && (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref))
+ && {
+ let arg_type = cx.typeck_results().expr_ty(&call_args[0]);
+ let base_type = arg_type.peel_refs();
+ *base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::string_type)
+ }
+ {
+ &call_args[0]
+ } else {
+ break;
+ }
+ },
+ _ => break,
+ };
+ }
+ arg_root
+ }
+
+ // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
+ // converted to string.
+ fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+ let arg_ty = cx.typeck_results().expr_ty(arg);
+ if is_type_diagnostic_item(cx, arg_ty, sym::string_type) {
+ return false;
+ }
+ if let ty::Ref(_, ty, ..) = arg_ty.kind() {
+ if *ty.kind() == ty::Str && can_be_static_str(cx, arg) {
+ return false;
+ }
+ };
+ true
+ }
+
+ // Check if an expression could have type `&'static str`, knowing that it
+ // has type `&str` for some lifetime.
+ fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+ match arg.kind {
+ hir::ExprKind::Lit(_) => true,
+ hir::ExprKind::Call(fun, _) => {
+ if let hir::ExprKind::Path(ref p) = fun.kind {
+ match cx.qpath_res(p, fun.hir_id) {
+ hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
+ cx.tcx.fn_sig(def_id).output().skip_binder().kind(),
+ ty::Ref(ty::ReStatic, ..)
+ ),
+ _ => false,
+ }
+ } else {
+ false
+ }
+ },
+ hir::ExprKind::MethodCall(..) => {
+ cx.typeck_results()
+ .type_dependent_def_id(arg.hir_id)
+ .map_or(false, |method_id| {
+ matches!(
+ cx.tcx.fn_sig(method_id).output().skip_binder().kind(),
+ ty::Ref(ty::ReStatic, ..)
+ )
+ })
+ },
+ hir::ExprKind::Path(ref p) => matches!(
+ cx.qpath_res(p, arg.hir_id),
+ hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _)
+ ),
+ _ => false,
+ }
+ }
+
+ fn generate_format_arg_snippet(
+ cx: &LateContext<'_>,
+ a: &hir::Expr<'_>,
+ applicability: &mut Applicability,
+ ) -> Vec<String> {
+ if_chain! {
+ if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind;
+ if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind;
+ if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind;
+
+ then {
+ format_arg_expr_tup
+ .iter()
+ .map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned())
+ .collect()
+ } else {
+ unreachable!()
+ }
+ }
+ }
+
+ fn is_call(node: &hir::ExprKind<'_>) -> bool {
+ match node {
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
+ is_call(&expr.kind)
+ },
+ hir::ExprKind::Call(..)
+ | hir::ExprKind::MethodCall(..)
+ // These variants are debatable or require further examination
+ | hir::ExprKind::If(..)
+ | hir::ExprKind::Match(..)
+ | hir::ExprKind::Block{ .. } => true,
+ _ => false,
+ }
+ }
+
+ if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) {
+ return;
+ }
+
+ let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]);
+ let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::option_type) {
+ "||"
+ } else if is_type_diagnostic_item(cx, receiver_type, sym::result_type) {
+ "|_|"
+ } else {
+ return;
+ };
+
+ let arg_root = get_arg_root(cx, &args[1]);
+
+ let span_replace_word = method_span.with_hi(expr.span.hi());
+
+ let mut applicability = Applicability::MachineApplicable;
+
+ //Special handling for `format!` as arg_root
+ if_chain! {
+ if let hir::ExprKind::Block(block, None) = &arg_root.kind;
+ if block.stmts.len() == 1;
+ if let hir::StmtKind::Local(local) = &block.stmts[0].kind;
+ if let Some(arg_root) = &local.init;
+ if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind;
+ if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1;
+ if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind;
+ then {
+ let fmt_spec = &format_args[0];
+ let fmt_args = &format_args[1];
+
+ let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()];
+
+ args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability));
+
+ let sugg = args.join(", ");
+
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
+ applicability,
+ );
+
+ return;
+ }
+ }
+
+ let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
+ if requires_to_string(cx, arg_root) {
+ arg_root_snippet.to_mut().push_str(".to_string()");
+ }
+
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!(
+ "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})",
+ closure_args, arg_root_snippet
+ ),
+ applicability,
+ );
+}