-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move expect_fun_call to its own module
- Loading branch information
Showing
2 changed files
with
204 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<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, | ||
); | ||
} |
Oops, something went wrong.