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

new lint: unnecessary_indexing #12464

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Conversation

Jacherr
Copy link
Contributor

@Jacherr Jacherr commented Mar 11, 2024

Fixes #11858

Adds a new lint which checks for an if-conditional which checks if a sequence is not empty (!seq.is_empty()) and, in the body of the if statement, accesses the first element of said sequence.

Helps to reduce the complexity of the code and improve readability.

changelog: Add new lint unnecessary_indexing

@rustbot
Copy link
Collaborator

rustbot commented Mar 11, 2024

r? @Manishearth

rustbot has assigned @Manishearth.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Mar 11, 2024
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
@Jacherr
Copy link
Contributor Author

Jacherr commented Mar 13, 2024

I've given my best shot at making sure that the code is as clean as possible but I haven't done much with visitors etc. before, so my implementation is probably still a bit rough around the edges! It does, however, get the job done in all of the tests I put together (including mixes of locals and expressions, so on and so forth).

The code is made a bit more complicated because it needs to account for the presence of multiple locals, as well as no locals at all, referencing seq[0].

Edit: The new implementation with a visitor should appear here as soon as GitHub decides to work again 😅

@rustbot rustbot added has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Mar 13, 2024
@rustbot
Copy link
Collaborator

rustbot commented Mar 13, 2024

There are merge commits (commits with multiple parents) in your changes. We have a no merge policy so these commits will need to be removed for this pull request to be merged.

You can start a rebase with the following commands:

$ # rebase
$ git rebase -i master
$ # delete any merge commits in the editor that appears
$ git push --force-with-lease

The following commits are merge commits:

@Manishearth
Copy link
Member

r? clippy

still somewhat busy for a PR this involved

@rustbot rustbot assigned giraffate and unassigned Manishearth Mar 13, 2024
@rustbot rustbot removed has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Mar 13, 2024
@Jacherr
Copy link
Contributor Author

Jacherr commented Mar 14, 2024

Made a slight adjustment to not lint if a mutable reference to the sequence is taken, as this can affect the lint suggestions in a negative way, so this should hopefully eliminate some opportunities for false positives to appear.

@bors
Copy link
Contributor

bors commented Mar 17, 2024

☔ The latest upstream changes (presumably #12451) made this pull request unmergeable. Please resolve the merge conflicts.

// any other index expressions to replace with `pat` (or "element" if no local exists)
let mut extra_exprs: Vec<&Expr<'_>> = vec![];

for_each_expr(block.stmts, |x| {
Copy link
Member

Choose a reason for hiding this comment

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

👀 It appears that the blocks.expr is being ignored, thus the code below does not lint:

let first = if !a.is_empty() {
    a[0]
};

Could you add a test to confirm that?

Copy link
Member

@J-ZhengLi J-ZhengLi May 14, 2024

Choose a reason for hiding this comment

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

I just gave it a try, and this case indeed have FN, passing the whole block to the visitor call solves it, since the block.expr will then be visited, can you change it?

and, make sure to skip the lint if the is_empty call and the indexing are from some external macro, there's a rustc_middle::lint::in_external_macro function does the check. We don't want the lint to suggesting user to change something that they have no control of.
it's pretty good as a first implementation (or second)

Copy link
Member

Choose a reason for hiding this comment

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

A regular .from_expansion() would be fine, we don't really want to lint across local macros here either

@J-ZhengLi J-ZhengLi added the S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) label May 14, 2024
@J-ZhengLi
Copy link
Member

looks like @giraffate haven't been active for quite a while, so, it might make sense to re-assign someone else for now?

r? clippy

@rustbot rustbot assigned Alexendoo and unassigned giraffate May 14, 2024
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
@Jacherr
Copy link
Contributor Author

Jacherr commented May 29, 2024

Apologies, I accidentally managed to close this when attempting to fix the conflicts on this branch.

@Jacherr Jacherr reopened this May 29, 2024
@Jacherr
Copy link
Contributor Author

Jacherr commented May 30, 2024

@rustbot ready

@y21 y21 removed the S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) label May 30, 2024
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
clippy_lints/src/unnecessary_indexing.rs Show resolved Hide resolved
Comment on lines 142 to 143
// the receiver for the index operation
pub index_receiver: Option<&'a Expr<'a>>,
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to store index_receiver? It looks like using conditional_receiver above would suffice as they have the same source text

Copy link
Contributor Author

Choose a reason for hiding this comment

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

index_receiver has some additional bounds on it, for example it is only Some if the index is a primitive constant, and not some kind of dynamic value stored in a variable or constant somewhere else, so I think it can stay. I'll update the comment on the field in the struct to better explain this.

This comment was marked as outdated.

clippy_lints/src/unnecessary_indexing.rs Outdated Show resolved Hide resolved
@bors
Copy link
Contributor

bors commented Jun 11, 2024

☔ The latest upstream changes (presumably #12849) made this pull request unmergeable. Please resolve the merge conflicts.

@Alexendoo Alexendoo added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jun 20, 2024
@Jacherr
Copy link
Contributor Author

Jacherr commented Jun 27, 2024

@rustbot ready

Once everything's good to go I can squash everything. The commit history is getting quite messy :p

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Jun 27, 2024
@bors
Copy link
Contributor

bors commented Jul 4, 2024

☔ The latest upstream changes (presumably #12873) made this pull request unmergeable. Please resolve the merge conflicts.

"condition can be simplified with if..let syntax",
|diag| {
if let Some(first_local) = r.first_local
&& first_local.pat.simple_ident().is_some()
Copy link
Member

Choose a reason for hiding this comment

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

If we use if let Some(ident) = first_local.pat.simple_ident() we can use ident.name later instead of getting a snippet from the pat's span

if let Some(first_local) = r.first_local
&& first_local.pat.simple_ident().is_some()
{
diag.span_suggestion(
Copy link
Member

Choose a reason for hiding this comment

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

We should push all the suggestions into one vec and use multipart_suggestion at the end for all the changes, multiple span suggestions are presented as alternatives rather than something to combine in e.g. rust-analyzer

{
diag.span_suggestion(
if_expr.cond.span,
"consider using if..let syntax (variable may need to be dereferenced)",
Copy link
Member

Choose a reason for hiding this comment

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

Let's be confident that our suggestion won't require dereferencing

Suggested change
"consider using if..let syntax (variable may need to be dereferenced)",
"consider using `if let` instead of indexing",

Comment on lines +199 to +209
} else if adjustments.iter().any(|adjustment| {
matches!(
adjustment.kind,
Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Mut { .. }))
)
}) {
// do not lint on mutable auto borrows (https://github.com/rust-lang/rust-clippy/pull/12464#discussion_r1600352696)
return ControlFlow::Break(());
} else if let ExprKind::AddrOf(_, Mutability::Mut, _) = x.kind {
return ControlFlow::Break(());
};
Copy link
Member

Choose a reason for hiding this comment

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

This will false negative for any code that mutates unrelated things

if !x.is_empty() {
    unrelated.push(x[0]);
}

Something like

} else if path_to_local_id(x, conditional_receiver_hid) {
    match cx.typeck_results().expr_ty_adjusted(x).kind() {
        RawPtr(_, Mutability::Mut) | Ref(_, _, Mutability::Mut) => return ControlFlow::Break(());
        Adt(..) => {
            if /* parent expr is not & */ {
                return ControlFlow::Break(())
            }
        }
        _ => {}
    }
}

would narrow it down to only things that mutate our input, it should also let us remove this check and lint when the type is &mut [T]/&mut Vec<T> (assuming it works)

Copy link
Member

Choose a reason for hiding this comment

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

A step further would be to not break immediately but store a bool that we've seen a mutation and break in the if let ExprKind::Index(receiver, index, _) if it's true

This would be since it doesn't matter if the code is mutating the input as long as it comes after all the indexes we're modifying:

// ok
if !input.is_empty() {
    input[0];
    input.push(1);
}
// not ok
if !input.is_empty() {
    input.push(1);
    input[0];
}

Comment on lines +192 to +198
if let Node::Expr(x) = cx.tcx.parent_hir_node(x.hir_id)
&& let ExprKind::AddrOf(_, _, _) = x.kind
{
extra_exprs_borrow.push(x);
} else {
extra_exprs_copy.push(x);
};
Copy link
Member

Choose a reason for hiding this comment

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

It would also need a borrow when x is auto borrowed

fn f(x: &[String]) {
    if !x.is_empty() {
        let _ = x[0].len();
    }
}

&& path_to_local_id(receiver, conditional_receiver_hid)
&& val.0 == 0
{
index_receiver = Some(receiver);
Copy link
Member

Choose a reason for hiding this comment

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

As a callback to #12464 (comment) we could make this a bool and check it before returning to ditch the field

@Alexendoo
Copy link
Member

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jul 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

suggest using if let Some(x) = arr.first() over if !arr.is_empty() then arr[0]
8 participants