-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Lint for using hand-writing a fold with the same behaviour as any #2350
Changes from all commits
f6e56d2
1feb9fd
528be23
7e833ea
360f235
70a5535
ad16493
a64d19c
1cac693
29a2dd4
9806b31
b73efad
a324a2b
7030259
71abd81
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 |
---|---|---|
|
@@ -9,10 +9,10 @@ use std::borrow::Cow; | |
use std::fmt; | ||
use std::iter; | ||
use syntax::ast; | ||
use syntax::codemap::Span; | ||
use utils::{get_trait_def_id, implements_trait, in_external_macro, in_macro, is_copy, is_self, is_self_ty, | ||
use syntax::codemap::{Span, BytePos}; | ||
use utils::{get_arg_name, get_trait_def_id, implements_trait, in_external_macro, in_macro, is_copy, is_self, is_self_ty, | ||
iter_input_pats, last_path_segment, match_def_path, match_path, match_qpath, match_trait_method, | ||
match_type, method_chain_args, return_ty, same_tys, single_segment_path, snippet, span_lint, | ||
match_type, method_chain_args, return_ty, remove_blocks, same_tys, single_segment_path, snippet, span_lint, | ||
span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth}; | ||
use utils::paths; | ||
use utils::sugg; | ||
|
@@ -622,6 +622,29 @@ declare_lint! { | |
"using `as_ref` where the types before and after the call are the same" | ||
} | ||
|
||
|
||
/// **What it does:** Checks for using `fold` when a more succinct alternative exists. | ||
/// Specifically, this checks for `fold`s which could be replaced by `any`, `all`, | ||
/// `sum` or `product`. | ||
/// | ||
/// **Why is this bad?** Readability. | ||
/// | ||
/// **Known problems:** None. | ||
/// | ||
/// **Example:** | ||
/// ```rust | ||
/// let _ = (0..3).fold(false, |acc, x| acc || x > 2); | ||
/// ``` | ||
/// This could be written as: | ||
/// ```rust | ||
/// let _ = (0..3).any(|x| x > 2); | ||
/// ``` | ||
declare_lint! { | ||
pub UNNECESSARY_FOLD, | ||
Warn, | ||
"using `fold` when a more succinct alternative exists" | ||
} | ||
|
||
impl LintPass for Pass { | ||
fn get_lints(&self) -> LintArray { | ||
lint_array!( | ||
|
@@ -652,7 +675,8 @@ impl LintPass for Pass { | |
GET_UNWRAP, | ||
STRING_EXTEND_CHARS, | ||
ITER_CLONED_COLLECT, | ||
USELESS_ASREF | ||
USELESS_ASREF, | ||
UNNECESSARY_FOLD | ||
) | ||
} | ||
} | ||
|
@@ -716,6 +740,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass { | |
lint_asref(cx, expr, "as_ref", arglists[0]); | ||
} else if let Some(arglists) = method_chain_args(expr, &["as_mut"]) { | ||
lint_asref(cx, expr, "as_mut", arglists[0]); | ||
} else if let Some(arglists) = method_chain_args(expr, &["fold"]) { | ||
lint_unnecessary_fold(cx, expr, arglists[0]); | ||
} | ||
|
||
lint_or_fun_call(cx, expr, &method_call.name.as_str(), args); | ||
|
@@ -1106,6 +1132,93 @@ fn lint_iter_cloned_collect(cx: &LateContext, expr: &hir::Expr, iter_args: &[hir | |
} | ||
} | ||
|
||
fn lint_unnecessary_fold(cx: &LateContext, expr: &hir::Expr, fold_args: &[hir::Expr]) { | ||
// Check that this is a call to Iterator::fold rather than just some function called fold | ||
if !match_trait_method(cx, expr, &paths::ITERATOR) { | ||
return; | ||
} | ||
|
||
assert!(fold_args.len() == 3, | ||
"Expected fold_args to have three entries - the receiver, the initial value and the closure"); | ||
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. Should this actually panic? I imagine a 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. Well, it's a logic bug, so I would have thought it should panic. I don't feel strongly though - I can change if it you'd prefer. |
||
|
||
fn check_fold_with_op( | ||
cx: &LateContext, | ||
fold_args: &[hir::Expr], | ||
op: hir::BinOp_, | ||
replacement_method_name: &str, | ||
replacement_has_args: bool) { | ||
|
||
if_chain! { | ||
// Extract the body of the closure passed to fold | ||
if let hir::ExprClosure(_, _, body_id, _, _) = fold_args[2].node; | ||
let closure_body = cx.tcx.hir.body(body_id); | ||
let closure_expr = remove_blocks(&closure_body.value); | ||
|
||
// Check if the closure body is of the form `acc <op> some_expr(x)` | ||
if let hir::ExprBinary(ref bin_op, ref left_expr, ref right_expr) = closure_expr.node; | ||
if bin_op.node == op; | ||
|
||
// Extract the names of the two arguments to the closure | ||
if let Some(first_arg_ident) = get_arg_name(&closure_body.arguments[0].pat); | ||
if let Some(second_arg_ident) = get_arg_name(&closure_body.arguments[1].pat); | ||
|
||
if let hir::ExprPath(hir::QPath::Resolved(None, ref path)) = left_expr.node; | ||
if path.segments.len() == 1 && &path.segments[0].name == &first_arg_ident; | ||
|
||
then { | ||
// Span containing `.fold(...)` | ||
let fold_span = fold_args[0].span.next_point().with_hi(fold_args[2].span.hi() + BytePos(1)); | ||
|
||
let sugg = if replacement_has_args { | ||
format!( | ||
".{replacement}(|{s}| {r})", | ||
replacement = replacement_method_name, | ||
s = second_arg_ident, | ||
r = snippet(cx, right_expr.span, "EXPR"), | ||
) | ||
} else { | ||
format!( | ||
".{replacement}()", | ||
replacement = replacement_method_name, | ||
) | ||
}; | ||
|
||
span_lint_and_sugg( | ||
cx, | ||
UNNECESSARY_FOLD, | ||
fold_span, | ||
// TODO #2371 don't suggest e.g. .any(|x| f(x)) if we can suggest .any(f) | ||
"this `.fold` can be written more succinctly using another method", | ||
"try", | ||
sugg, | ||
); | ||
} | ||
} | ||
} | ||
|
||
// Check if the first argument to .fold is a suitable literal | ||
match fold_args[1].node { | ||
hir::ExprLit(ref lit) => { | ||
match lit.node { | ||
ast::LitKind::Bool(false) => check_fold_with_op( | ||
cx, fold_args, hir::BinOp_::BiOr, "any", true | ||
), | ||
ast::LitKind::Bool(true) => check_fold_with_op( | ||
cx, fold_args, hir::BinOp_::BiAnd, "all", true | ||
), | ||
ast::LitKind::Int(0, _) => check_fold_with_op( | ||
cx, fold_args, hir::BinOp_::BiAdd, "sum", false | ||
), | ||
ast::LitKind::Int(1, _) => check_fold_with_op( | ||
cx, fold_args, hir::BinOp_::BiMul, "product", false | ||
), | ||
_ => return | ||
} | ||
} | ||
_ => return | ||
}; | ||
} | ||
|
||
fn lint_iter_nth(cx: &LateContext, expr: &hir::Expr, iter_args: &[hir::Expr], is_mut: bool) { | ||
let mut_str = if is_mut { "_mut" } else { "" }; | ||
let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.tables.expr_ty(&iter_args[0])).is_some() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure it's good style to put an empty line above and below code blocks in markdown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existing lints in this file don't, for whatever that's worth