Skip to content

Commit 3422282

Browse files
committed
Auto merge of #11556 - Alexendoo:manual-hash-one, r=Manishearth
Add `manual_hash_one` lint Adds a lint to suggest using [`BuildHasher::hash_one`](https://doc.rust-lang.org/std/hash/trait.BuildHasher.html#method.hash_one) changelog: [`manual_hash_one`]: new lint
2 parents 6c48ef3 + 7802b08 commit 3422282

10 files changed

+417
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5072,6 +5072,7 @@ Released 2018-09-13
50725072
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
50735073
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
50745074
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
5075+
[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
50755076
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
50765077
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
50775078
[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
280280
crate::manual_clamp::MANUAL_CLAMP_INFO,
281281
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
282282
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
283+
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
283284
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
284285
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
285286
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ mod manual_async_fn;
190190
mod manual_bits;
191191
mod manual_clamp;
192192
mod manual_float_methods;
193+
mod manual_hash_one;
193194
mod manual_is_ascii_check;
194195
mod manual_let_else;
195196
mod manual_main_separator_str;
@@ -1119,6 +1120,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11191120
msrv(),
11201121
))
11211122
});
1123+
store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(msrv())));
11221124
// add lints here, do not remove this comment, it's used in `new_lint`
11231125
}
11241126

clippy_lints/src/manual_hash_one.rs

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use clippy_utils::diagnostics::span_lint_hir_and_then;
2+
use clippy_utils::msrvs::{self, Msrv};
3+
use clippy_utils::source::snippet_opt;
4+
use clippy_utils::visitors::{is_local_used, local_used_once};
5+
use clippy_utils::{is_trait_method, path_to_local_id};
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{BindingAnnotation, ExprKind, Local, Node, PatKind, StmtKind};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_session::{declare_tool_lint, impl_lint_pass};
10+
use rustc_span::sym;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks for cases where [`BuildHasher::hash_one`] can be used.
15+
///
16+
/// [`BuildHasher::hash_one`]: https://doc.rust-lang.org/std/hash/trait.BuildHasher.html#method.hash_one
17+
///
18+
/// ### Why is this bad?
19+
/// It is more concise to use the `hash_one` method.
20+
///
21+
/// ### Example
22+
/// ```rust
23+
/// use std::hash::{BuildHasher, Hash, Hasher};
24+
/// use std::collections::hash_map::RandomState;
25+
///
26+
/// let s = RandomState::new();
27+
/// let value = vec![1, 2, 3];
28+
///
29+
/// let mut hasher = s.build_hasher();
30+
/// value.hash(&mut hasher);
31+
/// let hash = hasher.finish();
32+
/// ```
33+
/// Use instead:
34+
/// ```rust
35+
/// use std::hash::BuildHasher;
36+
/// use std::collections::hash_map::RandomState;
37+
///
38+
/// let s = RandomState::new();
39+
/// let value = vec![1, 2, 3];
40+
///
41+
/// let hash = s.hash_one(&value);
42+
/// ```
43+
#[clippy::version = "1.74.0"]
44+
pub MANUAL_HASH_ONE,
45+
complexity,
46+
"manual implementations of `BuildHasher::hash_one`"
47+
}
48+
49+
pub struct ManualHashOne {
50+
msrv: Msrv,
51+
}
52+
53+
impl ManualHashOne {
54+
#[must_use]
55+
pub fn new(msrv: Msrv) -> Self {
56+
Self { msrv }
57+
}
58+
}
59+
60+
impl_lint_pass!(ManualHashOne => [MANUAL_HASH_ONE]);
61+
62+
impl LateLintPass<'_> for ManualHashOne {
63+
fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
64+
// `let mut hasher = seg.build_hasher();`
65+
if let PatKind::Binding(BindingAnnotation::MUT, hasher, _, None) = local.pat.kind
66+
&& let Some(init) = local.init
67+
&& !init.span.from_expansion()
68+
&& let ExprKind::MethodCall(seg, build_hasher, [], _) = init.kind
69+
&& seg.ident.name == sym!(build_hasher)
70+
71+
&& let Node::Stmt(local_stmt) = cx.tcx.hir().get_parent(local.hir_id)
72+
&& let Node::Block(block) = cx.tcx.hir().get_parent(local_stmt.hir_id)
73+
74+
&& let mut stmts = block.stmts.iter()
75+
.skip_while(|stmt| stmt.hir_id != local_stmt.hir_id)
76+
.skip(1)
77+
.filter(|&stmt| is_local_used(cx, stmt, hasher))
78+
79+
// `hashed_value.hash(&mut hasher);`
80+
&& let Some(hash_stmt) = stmts.next()
81+
&& let StmtKind::Semi(hash_expr) = hash_stmt.kind
82+
&& !hash_expr.span.from_expansion()
83+
&& let ExprKind::MethodCall(seg, hashed_value, [ref_to_hasher], _) = hash_expr.kind
84+
&& seg.ident.name == sym::hash
85+
&& is_trait_method(cx, hash_expr, sym::Hash)
86+
&& path_to_local_id(ref_to_hasher.peel_borrows(), hasher)
87+
88+
&& let maybe_finish_stmt = stmts.next()
89+
// There should be no more statements referencing `hasher`
90+
&& stmts.next().is_none()
91+
92+
// `hasher.finish()`, may be anywhere in a statement or the trailing expr of the block
93+
&& let Some(path_expr) = local_used_once(cx, (maybe_finish_stmt, block.expr), hasher)
94+
&& let Node::Expr(finish_expr) = cx.tcx.hir().get_parent(path_expr.hir_id)
95+
&& !finish_expr.span.from_expansion()
96+
&& let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind
97+
&& seg.ident.name == sym!(finish)
98+
99+
&& self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE)
100+
{
101+
span_lint_hir_and_then(
102+
cx,
103+
MANUAL_HASH_ONE,
104+
finish_expr.hir_id,
105+
finish_expr.span,
106+
"manual implementation of `BuildHasher::hash_one`",
107+
|diag| {
108+
if let Some(build_hasher) = snippet_opt(cx, build_hasher.span)
109+
&& let Some(hashed_value) = snippet_opt(cx, hashed_value.span)
110+
{
111+
diag.multipart_suggestion(
112+
"try",
113+
vec![
114+
(local_stmt.span, String::new()),
115+
(hash_stmt.span, String::new()),
116+
(
117+
finish_expr.span,
118+
// `needless_borrows_for_generic_args` will take care of
119+
// removing the `&` when it isn't needed
120+
format!("{build_hasher}.hash_one(&{hashed_value})")
121+
)
122+
],
123+
Applicability::MachineApplicable,
124+
);
125+
126+
}
127+
},
128+
);
129+
}
130+
}
131+
132+
extract_msrv_attr!(LateContext);
133+
}

clippy_lints/src/utils/conf.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ define_Conf! {
294294
///
295295
/// Suppress lints whenever the suggested change would cause breakage for other crates.
296296
(avoid_breaking_exported_api: bool = true),
297-
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD.
297+
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE.
298298
///
299299
/// The minimum rust version that the project supports
300300
(msrv: Option<String> = None),

clippy_utils/src/msrvs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ macro_rules! msrv_aliases {
1919

2020
// names may refer to stabilized feature flags or library items
2121
msrv_aliases! {
22-
1,71,0 { TUPLE_ARRAY_CONVERSIONS }
22+
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
2323
1,70,0 { OPTION_IS_SOME_AND, BINARY_HEAP_RETAIN }
2424
1,68,0 { PATH_MAIN_SEPARATOR_STR }
2525
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }

clippy_utils/src/visitors.rs

+44
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,27 @@ where
6262
}
6363
}
6464
}
65+
impl<'tcx, A, B> Visitable<'tcx> for (A, B)
66+
where
67+
A: Visitable<'tcx>,
68+
B: Visitable<'tcx>,
69+
{
70+
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
71+
let (a, b) = self;
72+
a.visit(visitor);
73+
b.visit(visitor);
74+
}
75+
}
76+
impl<'tcx, T> Visitable<'tcx> for Option<T>
77+
where
78+
T: Visitable<'tcx>,
79+
{
80+
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
81+
if let Some(x) = self {
82+
x.visit(visitor);
83+
}
84+
}
85+
}
6586
macro_rules! visitable_ref {
6687
($t:ident, $f:ident) => {
6788
impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
@@ -748,3 +769,26 @@ pub fn contains_break_or_continue(expr: &Expr<'_>) -> bool {
748769
})
749770
.is_some()
750771
}
772+
773+
/// If the local is only used once in `visitable` returns the path expression referencing the given
774+
/// local
775+
pub fn local_used_once<'tcx>(
776+
cx: &LateContext<'tcx>,
777+
visitable: impl Visitable<'tcx>,
778+
id: HirId,
779+
) -> Option<&'tcx Expr<'tcx>> {
780+
let mut expr = None;
781+
782+
let cf = for_each_expr_with_closures(cx, visitable, |e| {
783+
if path_to_local_id(e, id) && expr.replace(e).is_some() {
784+
ControlFlow::Break(())
785+
} else {
786+
ControlFlow::Continue(())
787+
}
788+
});
789+
if cf.is_some() {
790+
return None;
791+
}
792+
793+
expr
794+
}

tests/ui/manual_hash_one.fixed

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#![warn(clippy::manual_hash_one)]
2+
#![allow(clippy::needless_borrows_for_generic_args)]
3+
4+
use std::hash::{BuildHasher, Hash, Hasher};
5+
6+
fn returned(b: impl BuildHasher) -> u64 {
7+
8+
9+
b.hash_one(&true)
10+
}
11+
12+
fn unsized_receiver(b: impl BuildHasher, s: &str) {
13+
14+
15+
let _ = b.hash_one(&s[4..10]);
16+
}
17+
18+
fn owned_value(b: impl BuildHasher, v: Vec<u32>) -> Vec<u32> {
19+
20+
21+
let _ = b.hash_one(&v);
22+
v
23+
}
24+
25+
fn reused_hasher(b: impl BuildHasher) {
26+
let mut hasher = b.build_hasher();
27+
true.hash(&mut hasher);
28+
let _ = hasher.finish();
29+
let _ = hasher.finish();
30+
}
31+
32+
fn reused_hasher_in_return(b: impl BuildHasher) -> u64 {
33+
let mut hasher = b.build_hasher();
34+
true.hash(&mut hasher);
35+
let _ = hasher.finish();
36+
hasher.finish()
37+
}
38+
39+
fn no_hash(b: impl BuildHasher) {
40+
let mut hasher = b.build_hasher();
41+
let _ = hasher.finish();
42+
}
43+
44+
fn hash_twice(b: impl BuildHasher) {
45+
let mut hasher = b.build_hasher();
46+
true.hash(&mut hasher);
47+
true.hash(&mut hasher);
48+
let _ = hasher.finish();
49+
}
50+
51+
fn other_hasher(b: impl BuildHasher) {
52+
let mut other_hasher = b.build_hasher();
53+
54+
let mut hasher = b.build_hasher();
55+
true.hash(&mut other_hasher);
56+
let _ = hasher.finish();
57+
}
58+
59+
fn finish_then_hash(b: impl BuildHasher) {
60+
let mut hasher = b.build_hasher();
61+
let _ = hasher.finish();
62+
true.hash(&mut hasher);
63+
}
64+
65+
fn in_macro(b: impl BuildHasher) {
66+
macro_rules! m {
67+
($b:expr) => {{
68+
let mut hasher = $b.build_hasher();
69+
true.hash(&mut hasher);
70+
let _ = hasher.finish();
71+
}};
72+
}
73+
74+
m!(b);
75+
}
76+
77+
#[clippy::msrv = "1.70"]
78+
fn msrv_1_70(b: impl BuildHasher, v: impl Hash) {
79+
let mut hasher = b.build_hasher();
80+
v.hash(&mut hasher);
81+
let _ = hasher.finish();
82+
}
83+
84+
#[clippy::msrv = "1.71"]
85+
fn msrv_1_71(b: impl BuildHasher, v: impl Hash) {
86+
87+
88+
let _ = b.hash_one(&v);
89+
}

0 commit comments

Comments
 (0)