Skip to content

Commit b2e3862

Browse files
committed
Take into account trailing /. as referring to directory
1 parent d628f76 commit b2e3862

File tree

2 files changed

+37
-7
lines changed

2 files changed

+37
-7
lines changed

library/std/src/path.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,11 +2719,30 @@ impl Path {
27192719
PathBuf { inner: OsString::from(inner) }
27202720
}
27212721

2722-
fn ends_with_separator(&self) -> bool {
2722+
/// Returns true if the path's syntax indicates it necessarily can only
2723+
/// refer to a directory.
2724+
///
2725+
/// This is relevant to the PartialEq and PartialOrd impls of Path. For
2726+
/// example, compared as paths "r/s/" and "r/s/." and "r/s/./" and "r/s/./."
2727+
/// are all in the same equivalence class, but a different class from "r/s".
2728+
/// That's because if "r/s" is a file then "r/s" exists while most
2729+
/// filesystems would consider that "r/s/" does not exist, and thus these
2730+
/// must not be equal paths.
2731+
fn syntactically_dir(&self) -> bool {
27232732
let components = self.components();
2724-
match components.path.last() {
2725-
None => false,
2726-
Some(byte) => components.is_sep_byte(*byte),
2733+
let bytes_after_prefix =
2734+
&components.path[components.prefix.as_ref().map_or(0, Prefix::len)..];
2735+
let mut bytes_iter = bytes_after_prefix.iter();
2736+
let last_byte = bytes_iter.next_back();
2737+
let second_last_byte = bytes_iter.next_back();
2738+
match (second_last_byte, last_byte) {
2739+
// Path ends in separator, like "path/to/"
2740+
(_, Some(&sep)) if components.is_sep_byte(sep) => true,
2741+
// Path ends in separator followed by CurDir, like "path/to/."
2742+
(Some(&sep), Some(b'.')) if components.is_sep_byte(sep) => true,
2743+
// Path is ".", because "." and "./." need to be considered equal
2744+
(None, Some(b'.')) => true,
2745+
_ => false,
27272746
}
27282747
}
27292748
}
@@ -2787,7 +2806,7 @@ impl cmp::PartialEq for Path {
27872806
#[inline]
27882807
fn eq(&self, other: &Path) -> bool {
27892808
self.components() == other.components()
2790-
&& self.ends_with_separator() == other.ends_with_separator()
2809+
&& self.syntactically_dir() == other.syntactically_dir()
27912810
}
27922811
}
27932812

@@ -2816,7 +2835,7 @@ impl cmp::Ord for Path {
28162835
#[inline]
28172836
fn cmp(&self, other: &Path) -> cmp::Ordering {
28182837
compare_components(self.components(), other.components())
2819-
.then_with(|| self.ends_with_separator().cmp(&other.ends_with_separator()))
2838+
.then_with(|| self.syntactically_dir().cmp(&other.syntactically_dir()))
28202839
}
28212840
}
28222841

library/std/src/path/tests.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,11 @@ fn test_trailing_separator() {
15511551
assert_eq!(dir, Path::new(r"tmp\f\\"));
15521552
}
15531553

1554+
let dir_dot = Path::new("tmp/f/.");
1555+
assert_ne!(file, dir_dot);
1556+
assert!(file < dir_dot);
1557+
assert_eq!(dir, dir_dot);
1558+
15541559
let file = file.to_owned();
15551560
let dir = dir.to_owned();
15561561
assert_ne!(file, dir);
@@ -1658,9 +1663,15 @@ fn test_ord() {
16581663
ord!(Less, "foo/./bar", "foo/bar/");
16591664
ord!(Equal, "foo/./bar/", "foo/bar/");
16601665
ord!(Less, "foo/bar", "foo/bar/");
1661-
ord!(Equal, "foo/bar", "foo/bar/.");
1666+
ord!(Less, "foo/bar", "foo/bar/.");
16621667
ord!(Less, "foo/bar", "foo/bar//");
16631668
ord!(Equal, "foo/bar/", "foo/bar//");
1669+
ord!(Equal, "foo/bar/", "foo/bar/.");
1670+
ord!(Equal, "foo/bar/", "foo/bar/./");
1671+
ord!(Equal, "foo/bar/", "foo/bar/./.");
1672+
ord!(Less, "/", "./");
1673+
ord!(Equal, ".", "./");
1674+
ord!(Equal, ".", "./.");
16641675
}
16651676

16661677
#[bench]

0 commit comments

Comments
 (0)