Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions library/std/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2983,7 +2983,8 @@ impl Path {
#[must_use]
#[inline]
pub fn has_trailing_sep(&self) -> bool {
self.as_os_str().as_encoded_bytes().last().copied().is_some_and(is_sep_byte)
let comps = self.components();
self.as_os_str().as_encoded_bytes().last().copied().is_some_and(|b| comps.is_sep_byte(b))
}

/// Ensures that a path has a trailing [separator](MAIN_SEPARATOR),
Expand Down Expand Up @@ -3034,10 +3035,11 @@ impl Path {
#[must_use]
#[inline]
pub fn trim_trailing_sep(&self) -> &Path {
let comps = self.components();
if self.has_trailing_sep() && (!self.has_root() || self.parent().is_some()) {
let mut bytes = self.inner.as_encoded_bytes();
while let Some((last, init)) = bytes.split_last()
&& is_sep_byte(*last)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename is_sep_byte to something less easy to reach for? It seems like in most (all?) cases code should be using path.components().is_sep_byte(...) instead? Should we document this on std::path::is_separator? I'm not 100% sure if all the existing usages are doing the right thing...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I'd like to rewrite our path handling a fair bit. Trying to be maximally generic over all possible path types on all platforms ends up being overly complicated and easy to mess up because you have to be on guard all the time. I think it'd be better if we, at the very least, had system specific sys::Path types even if all they do is implement a few lowish level methods that the higher level std::path::Path can build on.

&& comps.is_sep_byte(*last)
{
bytes = init;
}
Expand Down
17 changes: 17 additions & 0 deletions library/std/tests/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2597,3 +2597,20 @@ fn test_trim_trailing_sep() {
assert_eq!(Path::new("c:..\\\\").trim_trailing_sep().as_os_str(), OsStr::new("c:.."));
}
}

#[cfg(windows)]
#[test]
fn trailing_sep_verbatim() {
assert_eq!(Path::new(r"\\?\C:\path").has_trailing_sep(), false);
assert_eq!(Path::new(r"\\?\C:\path/").has_trailing_sep(), false);
assert_eq!(Path::new(r"\\?\C:\path\").has_trailing_sep(), true);
assert_eq!(Path::new(r"\\?\C:\").has_trailing_sep(), true);
assert_eq!(Path::new(r"\\?\C:/").has_trailing_sep(), false);

assert_eq!(Path::new(r"\\?\C:\path").trim_trailing_sep(), Path::new(r"\\?\C:\path"));
assert_eq!(Path::new(r"\\?\C:\path/").trim_trailing_sep(), Path::new(r"\\?\C:\path/"));
assert_eq!(Path::new(r"\\?\C:\path\").trim_trailing_sep(), Path::new(r"\\?\C:\path"));
assert_eq!(Path::new(r"\\?\C:\path/\\\").trim_trailing_sep(), Path::new(r"\\?\C:\path/"));
assert_eq!(Path::new(r"\\?\C:\").trim_trailing_sep(), Path::new(r"\\?\C:\"));
assert_eq!(Path::new(r"\\?\C:/").trim_trailing_sep(), Path::new(r"\\?\C:/"));
}
Loading