std: Base Hash for Path on its iterator

Almost all operations on Path are based on the components iterator in one form
or another to handle equivalent paths. The `Hash` implementations, however,
mistakenly just went straight to the underlying `OsStr`, causing these
equivalent paths to not get merged together.

This commit updates the `Hash` implementation to also be based on the iterator
which should ensure that if two paths are equal they hash to the same thing.

cc #29008, but doesn't close it
This commit is contained in:
Alex Crichton 2015-10-29 10:56:54 -07:00
parent 01fd4d6227
commit d2dd700891

View File

@ -103,6 +103,7 @@ use borrow::{Borrow, IntoCow, ToOwned, Cow};
use cmp; use cmp;
use fmt; use fmt;
use fs; use fs;
use hash::{Hash, Hasher};
use io; use io;
use iter; use iter;
use mem; use mem;
@ -446,7 +447,7 @@ enum State {
/// ///
/// Does not occur on Unix. /// Does not occur on Unix.
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
#[derive(Copy, Clone, Eq, Hash, Debug)] #[derive(Copy, Clone, Eq, Debug)]
pub struct PrefixComponent<'a> { pub struct PrefixComponent<'a> {
/// The prefix as an unparsed `OsStr` slice. /// The prefix as an unparsed `OsStr` slice.
raw: &'a OsStr, raw: &'a OsStr,
@ -490,6 +491,13 @@ impl<'a> cmp::Ord for PrefixComponent<'a> {
} }
} }
#[stable(feature = "rust1", since = "1.0.0")]
impl<'a> Hash for PrefixComponent<'a> {
fn hash<H: Hasher>(&self, h: &mut H) {
self.parsed.hash(h);
}
}
/// A single component of a path. /// A single component of a path.
/// ///
/// See the module documentation for an in-depth explanation of components and /// See the module documentation for an in-depth explanation of components and
@ -932,7 +940,7 @@ impl<'a> cmp::Ord for Components<'a> {
/// path.push("system32"); /// path.push("system32");
/// path.set_extension("dll"); /// path.set_extension("dll");
/// ``` /// ```
#[derive(Clone, Hash)] #[derive(Clone)]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub struct PathBuf { pub struct PathBuf {
inner: OsString inner: OsString
@ -1171,6 +1179,13 @@ impl cmp::PartialEq for PathBuf {
} }
} }
#[stable(feature = "rust1", since = "1.0.0")]
impl Hash for PathBuf {
fn hash<H: Hasher>(&self, h: &mut H) {
self.as_path().hash(h)
}
}
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
impl cmp::Eq for PathBuf {} impl cmp::Eq for PathBuf {}
@ -1224,7 +1239,6 @@ impl Into<OsString> for PathBuf {
/// let parent_dir = path.parent(); /// let parent_dir = path.parent();
/// ``` /// ```
/// ///
#[derive(Hash)]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub struct Path { pub struct Path {
inner: OsStr inner: OsStr
@ -1809,6 +1823,15 @@ impl cmp::PartialEq for Path {
} }
} }
#[stable(feature = "rust1", since = "1.0.0")]
impl Hash for Path {
fn hash<H: Hasher>(&self, h: &mut H) {
for component in self.components() {
component.hash(h);
}
}
}
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
impl cmp::Eq for Path {} impl cmp::Eq for Path {}
@ -3035,6 +3058,14 @@ mod tests {
#[test] #[test]
pub fn test_compare() { pub fn test_compare() {
use hash::{Hash, Hasher, SipHasher};
fn hash<T: Hash>(t: T) -> u64 {
let mut s = SipHasher::new_with_keys(0, 0);
t.hash(&mut s);
s.finish()
}
macro_rules! tc( macro_rules! tc(
($path1:expr, $path2:expr, eq: $eq:expr, ($path1:expr, $path2:expr, eq: $eq:expr,
starts_with: $starts_with:expr, ends_with: $ends_with:expr, starts_with: $starts_with:expr, ends_with: $ends_with:expr,
@ -3045,6 +3076,9 @@ mod tests {
let eq = path1 == path2; let eq = path1 == path2;
assert!(eq == $eq, "{:?} == {:?}, expected {:?}, got {:?}", assert!(eq == $eq, "{:?} == {:?}, expected {:?}, got {:?}",
$path1, $path2, $eq, eq); $path1, $path2, $eq, eq);
assert!($eq == (hash(path1) == hash(path2)),
"{:?} == {:?}, expected {:?}, got {} and {}",
$path1, $path2, $eq, hash(path1), hash(path2));
let starts_with = path1.starts_with(path2); let starts_with = path1.starts_with(path2);
assert!(starts_with == $starts_with, assert!(starts_with == $starts_with,