-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add needless_character_iteration lint
#12815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| 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::utils::get_last_chain_binding_hir_id; | ||
| use super::NEEDLESS_CHARACTER_ITERATION; | ||
| use clippy_utils::diagnostics::span_lint_and_sugg; | ||
| use clippy_utils::source::snippet_opt; | ||
| 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, | ||
| ) { | ||
| match expr.kind { | ||
| ExprKind::MethodCall(method, receiver, [], _) => { | ||
| if 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) = snippet_opt(cx, before_chars) | ||
| { | ||
| 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); | ||
| } | ||
| }, | ||
| ExprKind::Unary(UnOp::Not, expr) => handle_expr(cx, expr, first_param, span, before_chars, !revert), | ||
| ExprKind::Call(fn_path, [arg]) => { | ||
| if 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) = snippet_opt(cx, before_chars) | ||
| { | ||
| 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<'_>) { | ||
| 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, | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| #![warn(clippy::needless_character_iteration)] | ||
| #![allow(clippy::map_identity, clippy::unnecessary_operation)] | ||
|
|
||
| #[derive(Default)] | ||
| struct S { | ||
| field: &'static str, | ||
| } | ||
|
|
||
| impl S { | ||
| fn field(&self) -> &str { | ||
| self.field | ||
| } | ||
| } | ||
|
|
||
| fn magic(_: char) {} | ||
|
|
||
| fn main() { | ||
| "foo".is_ascii(); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| !"foo".is_ascii(); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| "foo".is_ascii(); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| !"foo".is_ascii(); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
|
|
||
| let s = String::new(); | ||
| s.is_ascii(); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| !"foo".to_string().is_ascii(); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
|
|
||
| "foo".is_ascii(); | ||
| !"foo".is_ascii(); | ||
|
|
||
| S::default().field().is_ascii(); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
|
|
||
| // Should not lint! | ||
| "foo".chars().all(|c| { | ||
| let x = c; | ||
| magic(x); | ||
| x.is_ascii() | ||
| }); | ||
|
|
||
| // Should not lint! | ||
| "foo".chars().all(|c| c.is_ascii() && c.is_alphabetic()); | ||
|
|
||
| // Should not lint! | ||
| "foo".chars().map(|c| c).all(|c| !char::is_ascii(&c)); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| #![warn(clippy::needless_character_iteration)] | ||
| #![allow(clippy::map_identity, clippy::unnecessary_operation)] | ||
|
|
||
| #[derive(Default)] | ||
| struct S { | ||
| field: &'static str, | ||
| } | ||
|
|
||
| impl S { | ||
| fn field(&self) -> &str { | ||
| self.field | ||
| } | ||
| } | ||
|
|
||
| fn magic(_: char) {} | ||
|
|
||
| fn main() { | ||
| "foo".chars().all(|c| c.is_ascii()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a test where there is a different method between
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| "foo".chars().all(|c| !c.is_ascii()); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| "foo".chars().all(|c| char::is_ascii(&c)); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| "foo".chars().all(|c| !char::is_ascii(&c)); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
|
|
||
| let s = String::new(); | ||
| s.chars().all(|c| c.is_ascii()); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| "foo".to_string().chars().all(|c| !c.is_ascii()); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
|
|
||
| "foo".chars().all(|c| { | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| let x = c; | ||
| x.is_ascii() | ||
| }); | ||
xFrednet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "foo".chars().all(|c| { | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
| let x = c; | ||
| !x.is_ascii() | ||
| }); | ||
|
|
||
| S::default().field().chars().all(|x| x.is_ascii()); | ||
| //~^ ERROR: checking if a string is ascii using iterators | ||
|
|
||
| // Should not lint! | ||
| "foo".chars().all(|c| { | ||
| let x = c; | ||
| magic(x); | ||
| x.is_ascii() | ||
| }); | ||
|
|
||
| // Should not lint! | ||
| "foo".chars().all(|c| c.is_ascii() && c.is_alphabetic()); | ||
|
|
||
| // Should not lint! | ||
| "foo".chars().map(|c| c).all(|c| !char::is_ascii(&c)); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.