Skip to content
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

Merged
merged 15 commits into from
Jan 19, 2018

Conversation

theotherphil
Copy link
Contributor

@theotherphil theotherphil commented Jan 14, 2018

#2237

Some things that need to be addressed before this can be merged. (@Manishearth @oli-obk)

  • Do I need to make any changes to the lint name, documentation, description or suggestions?
  • See DONOTMERGE comment at the start of lint_fold_any - how can I check that the fold being called is actually the trait method on Iterator?
  • Decide whether I need to handle my TODO: don't suggest .any(|x| f(x)) if we can suggest .any(f) (and if so, work out how to do this) (Resolution: won't bother)
  • Reduce the span of the error to only include .fold(...) instead the caller as well (see comments from @killercup about enabling rustfix support)

I'll create a new issue for the sum and product cases and handle them in another PR.

@theotherphil theotherphil changed the title WIP issue 2237 WIP https://github.com/rust-lang-nursery/rust-clippy/issues/2237 Jan 14, 2018
@theotherphil theotherphil changed the title WIP https://github.com/rust-lang-nursery/rust-clippy/issues/2237 WIP issue 2237 Jan 14, 2018
@theotherphil theotherphil changed the title WIP issue 2237 Lint for using hand-writing a fold with the same behaviour as any Jan 14, 2018
@@ -653,7 +670,8 @@ impl LintPass for Pass {
GET_UNWRAP,
STRING_EXTEND_CHARS,
ITER_CLONED_COLLECT,
USELESS_ASREF
USELESS_ASREF,
FOLD_ANY
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Does this macro support trailing commas? If so, please add one and the next diff is only one line :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears not:

error: unexpected end of macro invocation
   --> clippy_lints/src/methods.rs:674:21
    |
674 |             FOLD_ANY,
    |                     ^

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, that's too bad. Someone should make a pull request to change this :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created an issue #2352. I'll do this next weekend if it's still open.

Copy link
Member

@killercup killercup Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is a macro defined in the compiler, so that issue is probably more useful over at https://github.com/rust-lang/rust :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -1105,6 +1125,50 @@ fn lint_iter_cloned_collect(cx: &LateContext, expr: &hir::Expr, iter_args: &[hir
}
}

fn lint_fold_any(cx: &LateContext, expr: &hir::Expr, fold_args: &[hir::Expr]) {
// DONOTMERGE: What if this is just some other method called fold?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if applicable in this context but other parts of clippy use match_trait_method(cx, expr, &paths::ITERATOR)

Copy link
Contributor Author

@theotherphil theotherphil Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I tried this (well, actually, I implemented the equivalent by hand as I missed this method), but this doesn't quite do what I want - it's not enough to know that the caller is an Iterator - this could be an inherent method rather than the trait method. Maybe this is rare/ill-advised enough that it doesn't matter?

Edit: actually, I think I misread and this does do what I want after all.

expr.span,
// TODO: don't suggest .any(|x| f(x)) if we can suggest .any(f)
&format!(
".fold(false, |{f}, {s}| {f} || {r})) is more succinctly expressed as .any(|{s}| {r})",
Copy link
Member

@killercup killercup Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use span_lint_and_sugg here to get output like this:

error: This `.fold` can be more succinctly expressed as `.any`
   --> $DIR/methods.rs:390:13
    |
390 |     let _ = (0..3).fold(false, |acc, x| acc || x > 2);
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
    |
    = note: `-D fold-any` implied by `-D warnings`

If you make sure the spans are right and exactly cover the .fold(…), this lint should work great with rustfix :)

Copy link
Contributor Author

@theotherphil theotherphil Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll just need to work out what it does first! (There's no doc comment at the moment)

Edit: no need to elaborate - I've got this working now

Copy link
Member

@killercup killercup Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, Rust's diagnostic messages (aka "fancy errors") allow you to add annotations to parts of the code. One special style of annotation is a "replacement suggestion". It looks just like the "help:" thing I posted above to a human, but rustfix can parse it to automatically fix your code.

The mystical span_lint_and_sugg is a shortcut to create a new lint message and at the same time also add a suggestion. It's basically the same as span_lint, but you pass two parameters, help: &str, which is the "try: " in the example above, and sugg: String, which is the suggested replacement (something like format!(".any(|{s}| {r})", …) in this case here).

Edit: too late :)

Edit 2: Feel free to add the above as doc comment ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks anyway. I'll add a doc comment.

I've actually not quite got this working, as I've got a span pointing at the whole expression, including the range. What's the best way of restricting this to the .fold(...) part?

/// Checks implementation of the `FOLD_ANY` lint
fn fold_any_ignore_initial_value_of_true() {
let _ = (0..3).fold(true, |acc, x| acc || x > 2);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also want to add another test that doesn't trigger the lint because it's not using a boolean, e.g. .fold(0, |acc, x| acc += if x > 2 { 1 } else { 0 })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

@theotherphil
Copy link
Contributor Author

@killercup I've addressed all of your comments so far, except for reducing the span to only include .fold(...). Although adding the doc comment created a new issue - now I get a wall of warnings about doc rendering differing between old and new renderers!

@killercup
Copy link
Member

reducing the span to only include .fold(...)

Not sure how to best do this, but I'm sure someone else can help us out :)

Although adding the doc comment created a new issue - now I get a wall of warnings about doc rendering differing between old and new renderers!

Yeah, rustdoc is switching to the CommonMark standard soon. What's the errors telling you? I assume something about the <pre>? If so, try putting the code block in triple-backticks or ident it by 4 spaces. (Just the usual Markdown ways to mark code blocks.)

@clarfonthey
Copy link
Contributor

I think that this should be extended to all of the similar methods on Iterator; see the original issue for some of my examples.

I don't mind just starting with any for now, but perhaps the lint should have a more generic name like unnecessary_fold.

@theotherphil
Copy link
Contributor Author

I agree that this should be extended to cover your other examples - I was planning on merging this case first and then adding the rest. I'm happy to change the name. Not sure if we want one lint covering all cases or one lint for each case.

@theotherphil
Copy link
Contributor Author

I've added tests locally to check that match_trait_methoddoes what I want, and discovered that this lint (and presumably the other methods lints) doesn't work if you call fold via Iterator::fold(...)

@clarfonthey
Copy link
Contributor

@oli-obk wouldn't this apply to the method-chain lints as well?

@oli-obk
Copy link
Contributor

oli-obk commented Jan 16, 2018

Jup, the method chain lints don't work on universal method calls, just on actual method calls with dot notation

cx,
FOLD_ANY,
expr.span,
// TODO: don't suggest .any(|x| f(x)) if we can suggest .any(f)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, we do have another lint for that ;)

If you really want to, you can extract the code from there into a separate function so you can call it from here and there and not duplicate any logic.

span_lint_and_sugg(
cx,
FOLD_ANY,
expr.span,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably use fold_args[0].span.next_point().with_hi(fold_args[1].span.hi()) to produce the span you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works (with small tweaks). Thanks!

@theotherphil
Copy link
Contributor Author

Thanks for the tips @oli-obk - I'll finish this PR off this weekend.

PatKind::Ref(ref subpat, _) => get_arg_name(subpat),
_ => None,
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing newline

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks

@theotherphil
Copy link
Contributor Author

Please don't merge just yet - I've decided to rename to unnecessary_fold and handle the other cases as part of this PR (I'm not going to worry about producing eta-reducible replacement suggestions for now).

@theotherphil
Copy link
Contributor Author

@oli-obk I think this is now in a mergeable state

@killercup
Copy link
Member

heads up: you'll most likely need to rebase on master to make this compile on the latest nightly

@theotherphil
Copy link
Contributor Author

theotherphil commented Jan 17, 2018

Do you know which of the AppVeyor jobs have any hope of succeeding? The first couple of failures appear to have nothing to do with my changes.

/// ```rust
/// let _ = (0..3).fold(false, |acc, x| acc || x > 2);
/// ```
/// This could be written as:
Copy link
Member

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.

Copy link
Contributor Author

@theotherphil theotherphil Jan 17, 2018

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

}

assert!(fold_args.len() == 3,
"Expected fold_args to have three entries - the receiver, the initial value and the closure");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this actually panic? I imagine a debug! and return; suffices

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

".{replacement}(|{s}| {r})",
replacement = replacement_method_name,
s = second_arg_ident,
r = snippet(cx, right_expr.span, "EXPR")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing commas are good

here, and everywhere below (and above)

/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
/// |
/// = note: `-D fold-any` implied by `-D warnings`
/// </pre>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace <pre> with triple back ticks or 4-space indent

cx,
UNNECESSARY_FOLD,
fold_span,
// TODO: don't suggest e.g. .any(|x| f(x)) if we can suggest .any(f)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe open an issue for this and link it here?

Also, Clippy has a lint that will suggest using UFCS instead of closures, so a second run will likely trigger (and fix) this anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declare_lint! {
pub UNNECESSARY_FOLD,
Warn,
"using `fold` when a more succint alternative exists"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/succint/succinct

@@ -623,6 +623,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 succint alternative exists.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/succint/succinct

bors bot added a commit to rust-lang/rustfix that referenced this pull request Jan 18, 2018
51: Fix test edge cases r=oli-obk a=killercup

Noticed this while adding test case for ~~fun~~ rust-lang/rust-clippy#2350
bors bot added a commit to rust-lang/rustfix that referenced this pull request Jan 18, 2018
51: Fix test edge cases r=oli-obk a=killercup

Noticed this while adding test case for ~~fun~~ rust-lang/rust-clippy#2350
@oli-obk oli-obk merged commit e642887 into rust-lang:master Jan 19, 2018
@oli-obk
Copy link
Contributor

oli-obk commented Jan 19, 2018

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants