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

Add needless_character_iteration lint #12815

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

GuillaumeGomez
Copy link
Member

Fixes #4817.

r? @xFrednet

changelog: Add needless_character_iteration lint

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label May 17, 2024
@GuillaumeGomez GuillaumeGomez force-pushed the add-needless_character_iteration branch 2 times, most recently from 1fa6ea2 to 076e3bc Compare May 18, 2024 13:25
@GuillaumeGomez
Copy link
Member Author

Fixed dogfood errors.

#![allow(clippy::unnecessary_operation)]

fn main() {
"foo".chars().all(|c| c.is_ascii());
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a test where there is a different method between chars() and all()? Also some test where the all call tests something else as well, like .all(|c| c.is_ascii() && c.is_alphabetic())

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

Copy link
Member

@xFrednet xFrednet left a comment

Choose a reason for hiding this comment

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

Solid start, I'd like to see a few more tests and have a few questions, then we can merge this :D

Comment on lines +16 to +37
"foo".chars().all(|c| {
//~^ ERROR: checking if a string is ascii using iterators
let x = c;
x.is_ascii()
});
Copy link
Member

Choose a reason for hiding this comment

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

Could you also add a test where the closure contains some additional statements, like this:

    "foo".chars().all(|c| {
        let x = c;
        magic(x);
        x.is_ascii()
    });

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure!

Copy link
Member Author

Choose a reason for hiding this comment

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

That was a very good call, the lint was emitted in this case but shouldn't have! Fixed it. :)

Comment on lines +65 to +96
while let ExprKind::MethodCall(_, new_recv, _, _) = recv.kind {
recv = new_recv;
}
Copy link
Member

Choose a reason for hiding this comment

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

When is this loop actually used? It feels like this could iterate through more methods calls, then the ones that are being linted 🤔

An example I considered was

#[derive(Default)]
struct S {
    field: &'static str,
}

impl S {
    fn field(&self) -> &str {
        &self.field
    }
}

S::default().field().chars().all(|x| x.is_ascii());

Copy link
Member Author

Choose a reason for hiding this comment

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

It's used to the get the whole chain of calls to suggest replacement for everything.

Copy link
Member

@xFrednet xFrednet May 27, 2024

Choose a reason for hiding this comment

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

Can you give an example for when this? I would thing that just taking recv.span() would already cover the whole thing? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

In the code example you gave, without this the span would cover chars().all(|x| x.is_ascii()). With this, it covers the entire expression: S::default().field().chars().all(|x| x.is_ascii()).

This is needed in case we need to have !.is_ascii(). If we don't have the full expression, then the suggestion will go wrong.

Comment on lines 40 to 60
ExprKind::Block(block, _) => {
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);
}
},
Copy link
Member

Choose a reason for hiding this comment

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

I feel like this part complicates the lint unnecessary. If people write weird code where they chain these things, I'd say it's okay to not lint it, but we can keep it if you want.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's convenient and easy enough to check so if you don't mind, I'd prefer to keep it. ;)

@GuillaumeGomez GuillaumeGomez force-pushed the add-needless_character_iteration branch from 076e3bc to 566dfd9 Compare May 27, 2024 12:16
@GuillaumeGomez
Copy link
Member Author

Fixed the corner cases and added more tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New lint: detect needless character iteration
3 participants