-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
base: master
Are you sure you want to change the base?
Conversation
r? @Manishearth rustbot has assigned @Manishearth. Use r? to explicitly pick a reviewer |
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 Edit: The new implementation with a visitor should appear here as soon as GitHub decides to work again 😅 |
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:
The following commits are merge commits: |
r? clippy still somewhat busy for a PR this involved |
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. |
☔ 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| { |
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.
👀 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?
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 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)
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.
A regular .from_expansion()
would be fine, we don't really want to lint across local macros here either
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 |
Apologies, I accidentally managed to close this when attempting to fix the conflicts on this branch. |
@rustbot ready |
// the receiver for the index operation | ||
pub index_receiver: Option<&'a Expr<'a>>, |
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.
Do we need to store index_receiver
? It looks like using conditional_receiver
above would suffice as they have the same source text
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.
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.
This comment was marked as outdated.
Sorry, something went wrong.
☔ The latest upstream changes (presumably #12849) made this pull request unmergeable. Please resolve the merge conflicts. |
@rustbot ready Once everything's good to go I can squash everything. The commit history is getting quite messy :p |
☔ 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() |
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.
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( |
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.
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)", |
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.
Let's be confident that our suggestion won't require dereferencing
"consider using if..let syntax (variable may need to be dereferenced)", | |
"consider using `if let` instead of indexing", |
} 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(()); | ||
}; |
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.
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)
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.
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];
}
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); | ||
}; |
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.
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); |
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.
As a callback to #12464 (comment) we could make this a bool and check it before returning to ditch the field
@rustbot author |
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