-
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 [legacy_numeric_constants
]
#10997
Conversation
r? @xFrednet (rustbot has picked a reviewer for you, use r? to override) |
43d84ed
to
da08b7f
Compare
What does this new lint do for this snippet? use std::u64;
fn main() {
let _ = u64::MAX;
} It should suggest simply removing the |
I think it should just not lint. Ideally it would remove the |
da08b7f
to
18ded67
Compare
I've altered its suggestion on such a case; it now suggests |
18ded67
to
30d5586
Compare
It's kinda weird to call this |
Not really a huge fan of this, since |
Thought of another corner case that might require handling separately: // replacing this with just `use u64::MAX` results in an error which ironically suggests `std::u64::MAX`
use std::u64::MAX;
fn main() {
let _ = MAX;
} |
As said on zulip, this is tricky. Likely not very plausible. It's either |
Ah right. If getting the span for |
Actually, is it possible to make the lint trigger on the actual imports like Might want to exclude floats from machine applicable in case someone does something like
|
What about |
Maybe this solution is worse, but you could implement a But that's just a thought. |
That's what I was thinking of, but I was worried of cases like use std::f64 as _;
// or
pub mod a {
pub use std::f64;
}
fn a() {
self::a::f64::MAX;
use std::f64;
f64::MAX; // will it always lint the above `use` instead of the `as _` one?
// etc...
// as alexendoo also noted, it'll likely be terribly difficult to create an accurate suggestion, so
// it might also not be worth the effort at all, like how would:
use std::{f64, f32::{MAX, self}, borrow::Cow};
// work?
} There are many edgecases to be worried of here, and to ensure that the removed |
The |
You can use the current logic where it checks if the first segment is e.g.
The part that makes it difficult is that all the required information to create a good suggestion isn't available in the HIR, it lowers nested |
Just to be clear, my thought was to keep linting expressions like you already are and only skip ones which look like associated constant usage Again, if providing a machine applicable suggestion is very difficult even doing it this way, I'm okay with just a
Thanks for working on this! I really appreciate it. |
Yeah that sounds fine by me |
I've made it have a note on every applicable suggestion that some |
} | ||
} | ||
}, | ||
UseKind::Glob => glob_use_stmts.push(item.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.
Maybe we can use names_imported_by_glob_use
to filter some of these out? Might not be worth the effort, though
legacy_integral_constants
]legacy_numeric_constants
]
I'm happy with this now. The only thing left is that FIXME. Not sure what the issue is there. |
7170eb4
to
d94f41a
Compare
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 like the lint idea, but I'm not too sure about the way it's implemented. I've left some notes. (I've read the discussion a few days ago, so sorry if some of these have already been discussed 😅 )
if !msrv.meets(msrvs::STD_INTEGRAL_CONSTANTS) || in_external_macro(cx.sess(), expr.span) { | ||
return; | ||
} |
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.
Can you add the lint to the msrv
config list and run metadata-collection
?
glob_use_stmts, | ||
} = self; | ||
|
||
if !item.span.is_dummy() && let ItemKind::Use(path, kind) = item.kind { |
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 think this should also check for item.span.from_expansion()
or in_external_macro
respectively
"usage of a legacy numeric constant" | ||
}; | ||
|
||
span_lint_and_then(cx, LEGACY_NUMERIC_CONSTANTS, span, msg, |diag| { |
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.
Clippy's span_lint_*
functions emit the lint, at the node, that the cx
has last passed to the LateLintPass
. The emission location is important for lint level tracking and lint expectations. If you emit a lint for a different node, please use span_lint_hir
or span_lint_hir_and_then
respectively. They allow you to specify what node should be used for lint level evaluation. Could you also add a test, that #[allow]
works?
FYI @blyxyas
tests/ui/legacy_numeric_constants.rs
Outdated
fn msrv_too_low() { | ||
// FIXME: Why does this lint?? | ||
std::u32::MAX; | ||
} |
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 properly also has a similar reason like span_lint_hir
. The msrv is tracked by extract_msrv_attr!(LateContext);
which does it for every node, but you lint once at the end. Either, you have to do the tracking in the visitor or change the logic to lint in the lint pass
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.
Yeah it should probably do it like that instead (the latter)
struct V<'a, 'tcx> { | ||
cx: &'a LateContext<'tcx>, | ||
msrv: &'a Msrv, | ||
use_stmts: &'a FxHashMap<Symbol, Vec<Span>>, | ||
glob_use_stmts: &'a Vec<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.
I'm not sure, if visiting everything again with a Visitor is the best thing to do. I'm guessing, you're doing it this way, to catch cases where the use std::primitive::u32
is after an item/body?
I can't remember if this has been discussed as part of the previous conversion. But I would guess that just linting on use std::primitive::u32
should be fine. There might be some uncool cases like use std::primitive::u32::MAX
but even then, I'd say it's enough to lint that one and state that all uses of MAX
have to be replaced with u32::MAX
. Walking all items again will probably have a performance impact and is weird when it comes to msrv tracking and lint emission.
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.
Yeah we need to do 2 passes to have that work unfortunately, we could lint the use
specifically if it exists but that might be a bit uglier than just linting the usage
I believe V
is used in check_crate_post
as I was a bit worried it wouldn't have the list of every Use
by the time it finds an applicable Expr
, but maybe that's not the case
We could perhaps do a visit in check_crate
like in check_crate_post
then use check_expr
like normal, now that we'd have the list of every Use
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.
Linting the use
seems fine to me since that's the part we want them to remove
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 we can't really know whether they're using it for std::f32::MAX
or std::f32::consts
. Something like
use std::f32;
use f32::consts;
Would be a FP (but not something people use regardless)
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 think this would be an acceptable FP, I can't imagine people really using this. :)
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 think this is a good idea and should also simplify the implementation :)
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 don't particularly agree, it's inconsistent with the behavior of deprecated
which lints on the usage itself which is generally a lot clearer. In the case of std::<int>
though we should also lint the use
itself as that's also quasi-deprecated but regardless the usage itself should be linted
Seeing usage of legacy numeric constants
on use std::u32::MAX
will likely confuse the programmer if removing that suddenly causes compilation to fail, and they may just silence the lint as a result (adding a single allow
is easier than manually changing every MAX
to u32::MAX
)
This would also make it non-MachineApplicable
in the case of std::f32::MAX
and friends, which I don't think is worth the increased simplicity...
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 think you may have misunderstood the behavior I proposed. I've edited it to hopefully clear things up.
I don't particularly agree, it's inconsistent with the behavior of
deprecated
which lints on the usage itself which is generally a lot clearer.
But the pattern i32::MAX
is not the problem, it's actually the prior use std::i32;
that's the problem because it was made redundant.
Seeing
usage of legacy numeric constants
onuse std::u32::MAX
will likely confuse the programmer if removing that suddenly causes compilation to fail, and they may just silence the lint as a result (adding a singleallow
is easier than manually changing everyMAX
tou32::MAX
)
Under the behavior I suggested, use std::u32::MAX;
would NOT trigger the lint. use std::u32;
would.
This would also make it non-
MachineApplicable
in the case ofstd::f32::MAX
and friends, which I don't think is worth the increased simplicity...
The behavior for std::f32::MAX
would not change at all under my proposal.
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 see.
But I still don't think we should go with this. In the case of f32::MAX
or f64::MAX
it'll result in a much less helpful message, just "remove the import" which is ambiguous; which import? It looks identical to f32::MAX
, just saying there's some import for std::f32
somewhere isn't all too helpful.
What this simplifies is removing the use
collection; however, the rest of it is only made more complicated. If the "remove the import" portion is improved, nothing has been simplified.
But the pattern
i32::MAX
is not the problem, it's actually the prioruse std::i32;
that's the problem because it was made redundant.
And in the cases where they're identical, It should still lint the usage but not give a suggestion, only listing the potentially bad imports. Then, since std::i32
is quasi-deprecated, that can be linted as well (we could also omit the bad imports in this case, since it'll be linted). In the case of f32::MAX
it'd always show the bad imports.
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 still don't think we should go with this. In the case of
f32::MAX
orf64::MAX
it'll result in a much less helpful message, just "remove the import" which is ambiguous; which import? It looks identical tof32::MAX
, just saying there's some import forstd::f32
somewhere isn't all too helpful.
I think you're overstating how hard it is to find and remove an import. And in the case of floats, it will commonly require more work anyways to resolve the ::consts
issue. Maybe we just shouldn't ever lint f32::MAX
.
What this simplifies is removing the
use
collection; however, the rest of it is only made more complicated. If the "remove the import" portion is improved, nothing has been simplified.And in the cases where they're identical, It should still lint the usage but not give a suggestion, only listing the potentially bad imports. Then, since
std::i32
is quasi-deprecated, that can be linted as well (we could also omit the bad imports in this case, since it'll be linted). In the case off32::MAX
it'd always show the bad imports.
In my opinion, linting i32::MAX
is purely noise. There's nothing to change with that code. All that needs to be done is remove the redundant import. With current behavior, stderr is longer and more repetitive.
It feels like throwing a warning when someone uses .try_into()
because they don't need to explicitly import the TryInto
trait, since it's in the prelude.
Here's how I see it. The current behavior is slower, more complicated, and more repetitive for a slightly more useful warning message. I don't think the tradeoffs are worth it.
That said, I'll be happy with either, and I appreciate your work on this.
Have you considered simply using diagnostics items instead of manually trying to match them ? |
They aren't diagnostic items, so we can't unfortunately. |
They could be added. I'm sure rust-lang/rust wouldn't mind them, in particular for a lint. |
That would require waiting for the next sync in 2 weeks. Blocking this for 2 weeks due to a minor nit isn't really the best imo. It's possible, but it would have to be done this way for a while. There's also >30 of them, adding that many pre-interned symbols for a single lint to use might not be desirable for the compiler to do tbh. Maybe if other lints use them, but not just one that's intending to port people away (and will likely be deprecated if they're ever deprecated themselves) |
They wouldn't mind at all. The diagnostic items are exactly intended for this kind of work. Just the sync wait time can be annoying from the Clippy side. I don't think it's a necessity, but definitely a nice idea :) |
Well, I completely forgot about this PR 😅 I should have this back going again within a few days |
d94f41a
to
1ad0d75
Compare
☔ The latest upstream changes (presumably #11239) made this pull request unmergeable. Please resolve the merge conflicts. |
LL | use std::u32; | ||
| ^^^^^^^^ | ||
| | ||
= help: use the associated constants on `u32` instead at their usage |
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.
Can we just recommend removing the import altogether? Maybe something like
help: remove this import
note: then `u32::<CONST>` will resolve to the respective associated constant
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 can't because somebody could do something stupid like use crate::u32::MAX
after it, which would be incorrect. It would be nice if we could detect that though
It's definitely pedantic, but I think clippy should only suggest that if it'll actually be correct (at least for the note; the help seems ok)
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.
Also, we should only show the note if it isn't aliased, like use std::u32 as u32_consts
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.
What about this then?
help: remove this import and use the associated constants on `u32` instead
note: then `u32::<CONST>` will resolve to the respective associated constant
Hey @Centri3 any updates on this? If you're busy I may be able to finish it off. |
|
I'm closing this PR due to inactivity. The implementation is an excellent start, if anyone is interested in picking this PR up, go ahead. If you have questions, you're welcome to ask them here as well. @Centri3 Thank you for working on this. If you want to continue your work, you can reopen this PR or open a new one :D |
new lint `legacy_numeric_constants` Rework of #10997 - uses diagnostic items - does not lint imports of the float modules (`use std::f32`) - does not lint usage of float constants that look like `f32::MIN` I chose to make the float changes because the following pattern is actually pretty useful ```rust use std::f32; let omega = freq * 2 * f32::consts::PI; ``` and the float modules are not TBD-deprecated like the integer modules. Closes #10995 --- changelog: New lint [`legacy_numeric_constants`] [#12312](#12312)
I don't like one specific part of its implementation, pretty obvious what it is :) If somebody has a better way to do it, please tell me!Thanks Alex :)(Still had fun on this though :D)
Closes #10995
changelog: New lint [
legacy_numeric_constants
]