-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Implicit hasher lint #2120
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
Implicit hasher lint #2120
Conversation
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 ❤️ this lint a lot! I'd have no qualms merging as is, so if you think any changes I suggested are too involved, feel free to leave them to be fixed in the future.
clippy_lints/src/types.rs
Outdated
/// used with them. | ||
/// | ||
/// **Known problems:** Suggestions for replacing constructors are not always | ||
/// accurate. |
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.
In what way are they inaccurate? Something a human can trivially fix? Or might there be cases that can't be resolved at all due to constraints by external crates?
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.
My thought was that there can be a HashMap
which has the same types as Self
but not exposed to outside.
impl Foo for HashMap<K, V> {
fn method(&self) {
let used_internally = HashMap::<K, V>::new(); // suggestion for this can be unwanted
}
}
But yes constraints by external crates are more problematic.
clippy_lints/src/types.rs
Outdated
let generics_span = generics.span.substitute_dummy({ | ||
let pos = snippet_opt(cx, item.span.until(target.span())) | ||
.and_then(|snip| { | ||
Some(item.span.lo() + ::syntax_pos::BytePos(snip.find("impl")? as u32 + 4)) |
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 scares me a little, can you add some tests where the entire thing is produced by macros, just the type is and some cases in between?
clippy_lints/src/types.rs
Outdated
|
||
for target in vis.found { | ||
let generics_snip = snippet(cx, generics.span, ""); | ||
let generics_snip_trimmed = if generics_snip.len() == 0 { |
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.
you can skip this check if you make the last argument of snippet
be "<>"
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.
snippet(cx, generics.span, "<>")
seems to return an empty string when there is no generics.
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... right... it's not when the span is missing, it's when the generics are empty. Don't mind me then.
clippy_lints/src/types.rs
Outdated
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitHasher { | ||
#[allow(cast_possible_truncation)] | ||
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) { | ||
if let ItemImpl(_, _, _, ref generics, _, ref ty, ref items) = item.node { |
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 make this a match on item.node
, so both the arms are in one match? It'll probably be easier if you move the arm bodies into their own methods, too.
clippy_lints/src/types.rs
Outdated
|
||
for target in vis.found { | ||
let generics_snip = snippet(cx, generics.span, ""); | ||
let generics_snip_trimmed = if generics_snip.len() == 0 { |
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.
lots of repetition between this arm and the other, can you split out the duplicate code into a method?
clippy_lints/src/types.rs
Outdated
/// Looks for default-hasher-dependent constructors like `HashMap::new`. | ||
struct ImplicitHasherConstructorVisitor<'a, 'tcx: 'a> { | ||
cx: &'a LateContext<'a, 'tcx>, | ||
body: Option<BodyId>, |
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.
You could store the body_tables here instead of the optional body. That way you get rid of the option and save yourself a lot of lookups.
clippy_lints/src/types.rs
Outdated
fn new(cx: &'a LateContext<'a, 'tcx>, target: ImplicitHasherType<'tcx>) -> Self { | ||
Self { | ||
cx, | ||
body: None, |
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.
you'd just use cx.tables
here in case you make the body_tables change.
tests/ui/implicit_hasher.stderr
Outdated
| ^^^^^^^^^^^^^^^^^^ | ||
help: ...and use generic constructor here | ||
| | ||
17 | (HashMap::new(), HashMap::with_capacity_and_hasher(10, Default::default())) |
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.
You can use https://github.com/rust-lang-nursery/rust-clippy/blob/73a1dd8e7f10f1c6cba618b60e3fed690a72a8d7/clippy_lints/src/utils/mod.rs#L667 to merge all suggestions into one. That should merge this one and the above one into a single one.
78f6690
to
5a61d88
Compare
clippy_lints/src/types.rs
Outdated
let params = &path.segments.last().as_ref()?.parameters.as_ref()?.types; | ||
let params_len = params.len(); | ||
|
||
let ty = cx.tcx.type_of(opt_def_id(path.def)?); |
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 returns the type of definition (like HashMap<K, V, S>
), not what is used in the code.
Can I get the ty::Ty
that corresponds to a hir::Ty
? Or is there any way to test if a ty::Ty
equals to a hir::Ty
?
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.
Have you tried cx.tables.node_id_to_ty(hir_ty.hir_id)
?
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.
No. I tried now but it panicked (node_id_to_type: no type for node
).
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.
rustc_typeck::hir_ty_to_ty
seems to work. However its doc page says "quasi-deprecated":(
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 it ever gets deprecated, we can move it to the utils
module:
// In case there are any projections etc, find the "environment"
// def-id that will be used to determine the traits/predicates in
// scope. This is derived from the enclosing item-like thing.
let env_node_id = tcx.hir.get_parent(hir_ty.id);
let env_def_id = tcx.hir.local_def_id(env_node_id);
let item_cx = self::collect::ItemCtxt::new(tcx, env_def_id);
item_cx.to_ty(hir_ty)
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.
Oops, I forgot to ping last time. Any suggestions? |
tests/ui/implicit_hasher.stderr
Outdated
| | ||
11 | impl<K: Hash + Eq, V, S: ::std::hash::BuildHasher + Default> Foo<i8> for HashMap<K, V> { | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
help: ...and change the type to |
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 is still made up of two separate suggestions. I think it'll be less confusing if it were also merged like you did with the constructors.
Just one more thing ;) |
Passed tests. |
Thanks for the high quality lint! |
Fixes #2101.
HashMap<K, V>
(without 3rd arg.) andHashSet<T>
(without 2nd arg.) inimpl Trait for <here>
and function arguments.BuildHasher
. PlusDefault
if non-generic constructor found.HashMap::new
orHashMap::with_capacity
, and suggests to replace them with generic alternatives.