Auto merge of #11781 - partiallytyped:11710, r=xFrednet

Verify Borrow<T> semantics for types that implement Hash, Borrow<str> and Borrow<[u8]>.

Fixes #11710

The essence of the issue is that types that implement Borrow<T> provide a facet or a representation of the underlying type. Under these semantics `hash(a) == hash(a.borrow())`.

This is a problem when a type implements `Borrow<str>`, `Borrow<[u8]>` and Hash, it is expected that the hash of all three types is identical. The problem is that the hash of [u8] is not the same as that of a String, even when the byte reference ([u8]) is derived from `.as_bytes()`

- [x] Followed [lint naming conventions][lint_naming]
- [x] Added passing UI tests (including committed `.stderr` file)
- [x] `cargo test` passes locally
- [x] Executed `cargo dev update_lints`
- [x] Added lint documentation
- [x] Run `cargo dev fmt`

---

 - [x] Explanation of the issue in the code
 - [x] Tests reproducing the issue
 - [x] Lint rule and emission

---

changelog: New lint: [`impl_hash_borrow_with_str_and_bytes`]
[#11781](https://github.com/rust-lang/rust-clippy/pull/11781)
This commit is contained in:
bors 2023-11-19 10:59:34 +00:00
commit 9c3a365fd2
6 changed files with 287 additions and 0 deletions

View file

@ -0,0 +1,136 @@
#![warn(clippy::impl_hash_borrow_with_str_and_bytes)]
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
struct ExampleType {
data: String,
}
impl Hash for ExampleType {
//~^ ERROR: can't
fn hash<H: Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
impl Borrow<str> for ExampleType {
fn borrow(&self) -> &str {
&self.data
}
}
impl Borrow<[u8]> for ExampleType {
fn borrow(&self) -> &[u8] {
self.data.as_bytes()
}
}
struct ShouldNotRaiseForHash {}
impl Hash for ShouldNotRaiseForHash {
fn hash<H: Hasher>(&self, state: &mut H) {
todo!();
}
}
struct ShouldNotRaiseForBorrow {}
impl Borrow<str> for ShouldNotRaiseForBorrow {
fn borrow(&self) -> &str {
todo!();
}
}
impl Borrow<[u8]> for ShouldNotRaiseForBorrow {
fn borrow(&self) -> &[u8] {
todo!();
}
}
struct ShouldNotRaiseForHashBorrowStr {}
impl Hash for ShouldNotRaiseForHashBorrowStr {
fn hash<H: Hasher>(&self, state: &mut H) {
todo!();
}
}
impl Borrow<str> for ShouldNotRaiseForHashBorrowStr {
fn borrow(&self) -> &str {
todo!();
}
}
struct ShouldNotRaiseForHashBorrowSlice {}
impl Hash for ShouldNotRaiseForHashBorrowSlice {
fn hash<H: Hasher>(&self, state: &mut H) {
todo!();
}
}
impl Borrow<[u8]> for ShouldNotRaiseForHashBorrowSlice {
fn borrow(&self) -> &[u8] {
todo!();
}
}
#[derive(Hash)]
//~^ ERROR: can't
struct Derived {
data: String,
}
impl Borrow<str> for Derived {
fn borrow(&self) -> &str {
self.data.as_str()
}
}
impl Borrow<[u8]> for Derived {
fn borrow(&self) -> &[u8] {
self.data.as_bytes()
}
}
struct GenericExampleType<T> {
data: T,
}
impl<T: Hash> Hash for GenericExampleType<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
impl Borrow<str> for GenericExampleType<String> {
fn borrow(&self) -> &str {
&self.data
}
}
impl Borrow<[u8]> for GenericExampleType<&'static [u8]> {
fn borrow(&self) -> &[u8] {
self.data
}
}
struct GenericExampleType2<T> {
data: T,
}
impl Hash for GenericExampleType2<String> {
//~^ ERROR: can't
// this is correctly throwing an error for generic with concrete impl
// for all 3 types
fn hash<H: Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
impl Borrow<str> for GenericExampleType2<String> {
fn borrow(&self) -> &str {
&self.data
}
}
impl Borrow<[u8]> for GenericExampleType2<String> {
fn borrow(&self) -> &[u8] {
self.data.as_bytes()
}
}

View file

@ -0,0 +1,41 @@
error: the semantics of `Borrow<T>` around `Hash` can't be satisfied when both `Borrow<str>` and `Borrow<[u8]>` are implemented
--> $DIR/impl_hash_with_borrow_str_and_bytes.rs:10:6
|
LL | impl Hash for ExampleType {
| ^^^^
|
= note: the `Borrow` semantics require that `Hash` must behave the same for all implementations of Borrow<T>
= note: however, the hash implementations of a string (`str`) and the bytes of a string `[u8]` do not behave the same ...
= note: ... as (`hash("abc") != hash("abc".as_bytes())`
= help: consider either removing one of the `Borrow` implementations (`Borrow<str>` or `Borrow<[u8]>`) ...
= help: ... or not implementing `Hash` for this type
= note: `-D clippy::impl-hash-borrow-with-str-and-bytes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::impl_hash_borrow_with_str_and_bytes)]`
error: the semantics of `Borrow<T>` around `Hash` can't be satisfied when both `Borrow<str>` and `Borrow<[u8]>` are implemented
--> $DIR/impl_hash_with_borrow_str_and_bytes.rs:73:10
|
LL | #[derive(Hash)]
| ^^^^
|
= note: the `Borrow` semantics require that `Hash` must behave the same for all implementations of Borrow<T>
= note: however, the hash implementations of a string (`str`) and the bytes of a string `[u8]` do not behave the same ...
= note: ... as (`hash("abc") != hash("abc".as_bytes())`
= help: consider either removing one of the `Borrow` implementations (`Borrow<str>` or `Borrow<[u8]>`) ...
= help: ... or not implementing `Hash` for this type
= note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info)
error: the semantics of `Borrow<T>` around `Hash` can't be satisfied when both `Borrow<str>` and `Borrow<[u8]>` are implemented
--> $DIR/impl_hash_with_borrow_str_and_bytes.rs:117:6
|
LL | impl Hash for GenericExampleType2<String> {
| ^^^^
|
= note: the `Borrow` semantics require that `Hash` must behave the same for all implementations of Borrow<T>
= note: however, the hash implementations of a string (`str`) and the bytes of a string `[u8]` do not behave the same ...
= note: ... as (`hash("abc") != hash("abc".as_bytes())`
= help: consider either removing one of the `Borrow` implementations (`Borrow<str>` or `Borrow<[u8]>`) ...
= help: ... or not implementing `Hash` for this type
error: aborting due to 3 previous errors