124 lines
4.8 KiB
Rust
124 lines
4.8 KiB
Rust
use rustc_errors::Applicability;
|
|
use rustc_hir::{Closure, Expr, ExprKind, HirId, StmtKind, UnOp};
|
|
use rustc_lint::LateContext;
|
|
use rustc_middle::ty;
|
|
use rustc_span::Span;
|
|
|
|
use super::NEEDLESS_CHARACTER_ITERATION;
|
|
use super::utils::get_last_chain_binding_hir_id;
|
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
|
use clippy_utils::source::SpanRangeExt;
|
|
use clippy_utils::{match_def_path, path_to_local_id, peel_blocks};
|
|
|
|
fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> {
|
|
while let ExprKind::AddrOf(_, _, e) = expr.kind {
|
|
expr = e;
|
|
}
|
|
expr
|
|
}
|
|
|
|
fn handle_expr(
|
|
cx: &LateContext<'_>,
|
|
expr: &Expr<'_>,
|
|
first_param: HirId,
|
|
span: Span,
|
|
before_chars: Span,
|
|
revert: bool,
|
|
is_all: bool,
|
|
) {
|
|
match expr.kind {
|
|
ExprKind::MethodCall(method, receiver, [], _) => {
|
|
// If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
|
|
// `is_ascii`, then only `.all()` should warn.
|
|
if revert != is_all
|
|
&& method.ident.name.as_str() == "is_ascii"
|
|
&& path_to_local_id(receiver, first_param)
|
|
&& let char_arg_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs()
|
|
&& *char_arg_ty.kind() == ty::Char
|
|
&& let Some(snippet) = before_chars.get_source_text(cx)
|
|
{
|
|
span_lint_and_sugg(
|
|
cx,
|
|
NEEDLESS_CHARACTER_ITERATION,
|
|
span,
|
|
"checking if a string is ascii using iterators",
|
|
"try",
|
|
format!("{}{snippet}.is_ascii()", if revert { "!" } else { "" }),
|
|
Applicability::MachineApplicable,
|
|
);
|
|
}
|
|
},
|
|
ExprKind::Block(block, _) => {
|
|
if block.stmts.iter().any(|stmt| !matches!(stmt.kind, StmtKind::Let(_))) {
|
|
// If there is something else than let bindings, then better not emit the lint.
|
|
return;
|
|
}
|
|
if let Some(block_expr) = block.expr
|
|
// First we ensure that this is a "binding chain" (each statement is a binding
|
|
// of the previous one) and that it is a binding of the closure argument.
|
|
&& let Some(last_chain_binding_id) =
|
|
get_last_chain_binding_hir_id(first_param, block.stmts)
|
|
{
|
|
handle_expr(
|
|
cx,
|
|
block_expr,
|
|
last_chain_binding_id,
|
|
span,
|
|
before_chars,
|
|
revert,
|
|
is_all,
|
|
);
|
|
}
|
|
},
|
|
ExprKind::Unary(UnOp::Not, expr) => handle_expr(cx, expr, first_param, span, before_chars, !revert, is_all),
|
|
ExprKind::Call(fn_path, [arg]) => {
|
|
// If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
|
|
// `is_ascii`, then only `.all()` should warn.
|
|
if revert != is_all
|
|
&& let ExprKind::Path(path) = fn_path.kind
|
|
&& let Some(fn_def_id) = cx.qpath_res(&path, fn_path.hir_id).opt_def_id()
|
|
&& match_def_path(cx, fn_def_id, &["core", "char", "methods", "<impl char>", "is_ascii"])
|
|
&& path_to_local_id(peels_expr_ref(arg), first_param)
|
|
&& let Some(snippet) = before_chars.get_source_text(cx)
|
|
{
|
|
span_lint_and_sugg(
|
|
cx,
|
|
NEEDLESS_CHARACTER_ITERATION,
|
|
span,
|
|
"checking if a string is ascii using iterators",
|
|
"try",
|
|
format!("{}{snippet}.is_ascii()", if revert { "!" } else { "" }),
|
|
Applicability::MachineApplicable,
|
|
);
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
}
|
|
|
|
pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>, is_all: bool) {
|
|
if let ExprKind::Closure(&Closure { body, .. }) = closure_arg.kind
|
|
&& let body = cx.tcx.hir().body(body)
|
|
&& let Some(first_param) = body.params.first()
|
|
&& let ExprKind::MethodCall(method, mut recv, [], _) = recv.kind
|
|
&& method.ident.name.as_str() == "chars"
|
|
&& let str_ty = cx.typeck_results().expr_ty_adjusted(recv).peel_refs()
|
|
&& *str_ty.kind() == ty::Str
|
|
{
|
|
let expr_start = recv.span;
|
|
while let ExprKind::MethodCall(_, new_recv, _, _) = recv.kind {
|
|
recv = new_recv;
|
|
}
|
|
let body_expr = peel_blocks(body.value);
|
|
|
|
handle_expr(
|
|
cx,
|
|
body_expr,
|
|
first_param.pat.hir_id,
|
|
recv.span.with_hi(call_expr.span.hi()),
|
|
recv.span.with_hi(expr_start.hi()),
|
|
false,
|
|
is_all,
|
|
);
|
|
}
|
|
}
|