diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs new file mode 100644 index 00000000000..6866e9c652a --- /dev/null +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -0,0 +1,199 @@ +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 { + 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, + ); +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 857c1bf0ec0..180620e860a 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2,6 +2,7 @@ mod bind_instead_of_map; mod bytes_nth; mod clone_on_copy; mod clone_on_ref_ptr; +mod expect_fun_call; mod expect_used; mod filetype_is_file; mod filter_flat_map; @@ -50,8 +51,6 @@ mod useless_asref; mod wrong_self_convention; mod zst_offset; -use std::borrow::Cow; - use bind_instead_of_map::BindInsteadOfMap; use if_chain::if_chain; use rustc_ast::ast; @@ -68,10 +67,9 @@ use rustc_span::symbol::{sym, Symbol, SymbolStr}; use rustc_typeck::hir_ty_to_ty; use crate::utils::{ - contains_return, contains_ty, get_trait_def_id, implements_trait, in_macro, is_copy, is_expn_of, - is_type_diagnostic_item, iter_input_pats, match_def_path, match_qpath, method_calls, method_chain_args, paths, - return_ty, single_segment_path, snippet, snippet_with_applicability, span_lint, span_lint_and_help, - span_lint_and_sugg, SpanlessEq, + contains_return, contains_ty, get_trait_def_id, implements_trait, in_macro, is_copy, is_type_diagnostic_item, + iter_input_pats, match_def_path, match_qpath, method_calls, method_chain_args, paths, return_ty, + single_segment_path, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq, }; declare_clippy_lint! { @@ -1779,7 +1777,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { }, hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args, _) => { or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args); - lint_expect_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args); + expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args); let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0]); if args.len() == 1 && method_call.ident.name == sym::clone { @@ -1973,200 +1971,6 @@ impl<'tcx> LateLintPass<'tcx> for Methods { extract_msrv_attr!(LateContext); } -/// Checks for the `EXPECT_FUN_CALL` lint. -#[allow(clippy::too_many_lines)] -fn lint_expect_fun_call( - 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 { - 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, - ); -} - fn derefs_to_slice<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>,