-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Useless vec false positive #11895
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
Useless vec false positive #11895
Conversation
r? @blyxyas (rustbot has picked a reviewer for you, use r? to override) |
clippy_lints/src/vec.rs
Outdated
@@ -20,6 +21,8 @@ use rustc_span::{sym, Span}; | |||
pub struct UselessVec { | |||
pub too_large_for_stack: u64, | |||
pub msrv: Msrv, | |||
pub span_to_lint_map: BTreeMap<Span, Option<(HirId, SuggestedType, String, Applicability)>>, | |||
pub spans_sure_to_lint: BTreeSet<Span>, |
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.
Is this extra set needed? It seems to me like the map on its own would be enough for determining uses
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 have removed this set, now there's only the span_to_lint_map.
clippy_lints/src/vec.rs
Outdated
SuggestedType::Array, | ||
); | ||
let span = expr.span.ctxt().outer_expn_data().call_site; | ||
self.spans_sure_to_lint.insert(span); |
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.
Is the logic here that if all uses of the variable let x = vec![];
adjust to a slice, then this is guaranteed to not be from a macro argument and we can always lint?
That would break down here I think:
macro_rules! m {
($x:expr) => {
fn f() { let _x = $x; $x.starts_with(&[]); }
fn f2() { let _x: Vec<i32> = $x; }
}
}
m!(vec![1]);
If I'm reading this correctly, it would lint here.
All uses of _x
in f
adjust to a slice, but it's still expanded twice.
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.
Oh, thanks for the catch. You are correct that the logic is broken here. My intention of adding the spans_sure_to_lint set was that when sometimes we might want to lint for _ in vec![...]
, but since there the vec![...]
does not adjust to a slice, subseqeunt check_expr
calls would insert None into the span_to_lint_map.
I think I will change the logic to store a set of HirId
s that are ok to lint, since expressions after macro expansion will have distinct HirIds.
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.
Meow :3
clippy_lints/src/vec.rs
Outdated
self.check_vec_macro(cx, &vec_args, span, arg.hir_id, SuggestedType::Array); | ||
} | ||
// search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]` | ||
else if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) { |
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 first check of this method is already searching for VecArgs::hir(cx, expr)
, is this check necessary again?
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 first check of this method was specifically looking for cases of let x = vec![...]
where every subsequent use of x
adjusts to a slice, whereas this check looks for cases where vec![]
is used in a context where the expression's type adjusts to a slice.
The original lint was also set up in this manner, but I readjusted the order of the different checks and also changed subsequent checks to use "else if" instead of "if".
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.
But I don't understand why you chose to create a new branch checking if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows())
, instead of adding this logic to the previous that checks the same thing.
|
||
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { | ||
for (span, lint_opt) in &self.span_to_lint_map { | ||
if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt { |
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 if
statement means that in cases like that self.span_to_lint_map.insert(span, None);
in line 129, it will not lint. In that case, why insert it in the first case?
If not linting is intended, both that line and the whole lintable_exprs
field can be removed.
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 reason to insert None
when we don't want to lint is to mark the specific span
as non-lintable. There are cases where the same span expands to multiple expressions (when a macro expands the vec!) and we want to lint only when all of the expanded expressions are ok for linting.
The reason for lintable_exprs
is because the first two checks of the lint will check cases where we do want to emit the lint even when the vec![...]
expression doesn't adjust to a slice. I chose to keep track of those expressions separately, although I could have also added a parameter to check_vec_macro
indicating whether we want to lint immediately, or wait until the check_crate_post
stage to emit lints.
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.
There's no need to gather all lintable expressions in a single collection and then do a crate pass, you can just lint in place.
The infrastructure needed to collect all lintable expressions costs performance and memory, some things that we're trying to improve right now. If you're trying to improve code cleanliness, you can just refactor the span_lint...
function into it's own function that takes less arguments. Like a report
function or emit
. It's done in other lints to avoid typing the same description / notes multiple times.
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.
Thanks for the suggestion. I'll look into refactoring this weekend, since I am busy with school this week.
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.
Welp 。^・ェ・^。
I pulled this PR on my personal computer to check if I could remove that new field, to no avail. I think that the current state of the PR is good to be merged, although I'd love to not have to add a new field / have to mark some spans as unlintable.
@y21 Do you find a way to remove that new field, or do you approve?
I'll approve from my part, let's see if someone comes before this Wednesday that has some ideas.
Eric, it seems like it will continue as-is, this is just the best solution we could find. Could you squash these commits into a single one? |
this fixes issue rust-lang#11861 by adding an extra map to keep track of which spans are ok to lint
1f7a351
to
884bec3
Compare
@bors r+ |
☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test |
changelog: [
useless_vec
]: fix false positive in macros.fixes #11861
We delay the emission of
useless_vec
lints to the check_crate_post stage, which allows us to effectively undo lints if we find that avec![]
expression is being used multiple times after macro expansion.