> FromIterator for PathBuf {
+ fn from_iter>(iter: I) -> PathBuf {
+ let mut buf = PathBuf::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl> Extend for PathBuf {
+ fn extend>(&mut self, iter: I) {
+ iter.into_iter().for_each(move |p| self.push(p.as_ref()));
+ }
+
+ #[inline]
+ fn extend_one(&mut self, p: P) {
+ self.push(p.as_ref());
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl fmt::Debug for PathBuf {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(&**self, formatter)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl ops::Deref for PathBuf {
+ type Target = Path;
+ #[inline]
+ fn deref(&self) -> &Path {
+ Path::new(&self.inner)
+ }
+}
+
+#[stable(feature = "path_buf_deref_mut", since = "1.68.0")]
+impl ops::DerefMut for PathBuf {
+ #[inline]
+ fn deref_mut(&mut self) -> &mut Path {
+ Path::from_inner_mut(&mut self.inner)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Borrow for PathBuf {
+ #[inline]
+ fn borrow(&self) -> &Path {
+ self.deref()
+ }
+}
+
+#[stable(feature = "default_for_pathbuf", since = "1.17.0")]
+impl Default for PathBuf {
+ #[inline]
+ fn default() -> Self {
+ PathBuf::new()
+ }
+}
+
+#[stable(feature = "cow_from_path", since = "1.6.0")]
+impl<'a> From<&'a Path> for Cow<'a, Path> {
+ /// Creates a clone-on-write pointer from a reference to
+ /// [`Path`].
+ ///
+ /// This conversion does not clone or allocate.
+ #[inline]
+ fn from(s: &'a Path) -> Cow<'a, Path> {
+ Cow::Borrowed(s)
+ }
+}
+
+#[stable(feature = "cow_from_path", since = "1.6.0")]
+impl<'a> From for Cow<'a, Path> {
+ /// Creates a clone-on-write pointer from an owned
+ /// instance of [`PathBuf`].
+ ///
+ /// This conversion does not clone or allocate.
+ #[inline]
+ fn from(s: PathBuf) -> Cow<'a, Path> {
+ Cow::Owned(s)
+ }
+}
+
+#[stable(feature = "cow_from_pathbuf_ref", since = "1.28.0")]
+impl<'a> From<&'a PathBuf> for Cow<'a, Path> {
+ /// Creates a clone-on-write pointer from a reference to
+ /// [`PathBuf`].
+ ///
+ /// This conversion does not clone or allocate.
+ #[inline]
+ fn from(p: &'a PathBuf) -> Cow<'a, Path> {
+ Cow::Borrowed(p.as_path())
+ }
+}
+
+#[stable(feature = "pathbuf_from_cow_path", since = "1.28.0")]
+impl<'a> From> for PathBuf {
+ /// Converts a clone-on-write pointer to an owned path.
+ ///
+ /// Converting from a `Cow::Owned` does not clone or allocate.
+ #[inline]
+ fn from(p: Cow<'a, Path>) -> Self {
+ p.into_owned()
+ }
+}
+
+#[stable(feature = "shared_from_slice2", since = "1.24.0")]
+impl From for Arc {
+ /// Converts a [`PathBuf`] into an [Arc]<[Path]>
by moving the [`PathBuf`] data
+ /// into a new [`Arc`] buffer.
+ #[inline]
+ fn from(s: PathBuf) -> Arc {
+ let arc: Arc = Arc::from(s.into_os_string());
+ unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Path) }
+ }
+}
+
+#[stable(feature = "shared_from_slice2", since = "1.24.0")]
+impl From<&Path> for Arc {
+ /// Converts a [`Path`] into an [`Arc`] by copying the [`Path`] data into a new [`Arc`] buffer.
+ #[inline]
+ fn from(s: &Path) -> Arc {
+ let arc: Arc = Arc::from(s.as_os_str());
+ unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Path) }
+ }
+}
+
+#[stable(feature = "shared_from_slice2", since = "1.24.0")]
+impl From for Rc {
+ /// Converts a [`PathBuf`] into an [Rc]<[Path]>
by moving the [`PathBuf`] data into
+ /// a new [`Rc`] buffer.
+ #[inline]
+ fn from(s: PathBuf) -> Rc {
+ let rc: Rc = Rc::from(s.into_os_string());
+ unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Path) }
+ }
+}
+
+#[stable(feature = "shared_from_slice2", since = "1.24.0")]
+impl From<&Path> for Rc {
+ /// Converts a [`Path`] into an [`Rc`] by copying the [`Path`] data into a new [`Rc`] buffer.
+ #[inline]
+ fn from(s: &Path) -> Rc {
+ let rc: Rc = Rc::from(s.as_os_str());
+ unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Path) }
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl ToOwned for Path {
+ type Owned = PathBuf;
+ #[inline]
+ fn to_owned(&self) -> PathBuf {
+ self.to_path_buf()
+ }
+ #[inline]
+ fn clone_into(&self, target: &mut PathBuf) {
+ self.as_os_str().clone_into(&mut target.inner);
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl PartialEq for PathBuf {
+ #[inline]
+ fn eq(&self, other: &PathBuf) -> bool {
+ self.components() == other.components()
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Hash for PathBuf {
+ fn hash(&self, h: &mut H) {
+ self.as_path().hash(h)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Eq for PathBuf {}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl PartialOrd for PathBuf {
+ #[inline]
+ fn partial_cmp(&self, other: &PathBuf) -> Option {
+ Some(compare_components(self.components(), other.components()))
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Ord for PathBuf {
+ #[inline]
+ fn cmp(&self, other: &PathBuf) -> cmp::Ordering {
+ compare_components(self.components(), other.components())
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl AsRef for PathBuf {
+ #[inline]
+ fn as_ref(&self) -> &OsStr {
+ &self.inner[..]
+ }
+}
+
+impl Path {
+ /// Converts a `Path` to a [`Cow`].
+ ///
+ /// Any non-Unicode sequences are replaced with
+ /// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
+ ///
+ /// [U+FFFD]: super::char::REPLACEMENT_CHARACTER
+ ///
+ /// # Examples
+ ///
+ /// Calling `to_string_lossy` on a `Path` with valid unicode:
+ ///
+ /// ```
+ /// use std::path::Path;
+ ///
+ /// let path = Path::new("foo.txt");
+ /// assert_eq!(path.to_string_lossy(), "foo.txt");
+ /// ```
+ ///
+ /// Had `path` contained invalid unicode, the `to_string_lossy` call might
+ /// have returned `"fo�.txt"`.
+ #[rustc_allow_incoherent_impl]
+ #[stable(feature = "rust1", since = "1.0.0")]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[inline]
+ pub fn to_string_lossy(&self) -> Cow<'_, str> {
+ self.as_os_str().to_string_lossy()
+ }
+
+ /// Converts a `Path` to an owned [`PathBuf`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::path::{Path, PathBuf};
+ ///
+ /// let path_buf = Path::new("foo.txt").to_path_buf();
+ /// assert_eq!(path_buf, PathBuf::from("foo.txt"));
+ /// ```
+ #[rustc_allow_incoherent_impl]
+ #[rustc_conversion_suggestion]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[stable(feature = "rust1", since = "1.0.0")]
+ pub fn to_path_buf(&self) -> PathBuf {
+ PathBuf::from(self.as_os_str().to_os_string())
+ }
+
+ /// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
+ ///
+ /// If `path` is absolute, it replaces the current path.
+ ///
+ /// See [`PathBuf::push`] for more details on what it means to adjoin a path.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::path::{Path, PathBuf};
+ ///
+ /// assert_eq!(Path::new("/etc").join("passwd"), PathBuf::from("/etc/passwd"));
+ /// assert_eq!(Path::new("/etc").join("/bin/sh"), PathBuf::from("/bin/sh"));
+ /// ```
+ #[rustc_allow_incoherent_impl]
+ #[stable(feature = "rust1", since = "1.0.0")]
+ #[must_use]
+ pub fn join>(&self, path: P) -> PathBuf {
+ self._join(path.as_ref())
+ }
+
+ #[rustc_allow_incoherent_impl]
+ fn _join(&self, path: &Path) -> PathBuf {
+ let mut buf = self.to_path_buf();
+ buf.push(path);
+ buf
+ }
+
+ /// Creates an owned [`PathBuf`] like `self` but with the given file name.
+ ///
+ /// See [`PathBuf::set_file_name`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::path::{Path, PathBuf};
+ ///
+ /// let path = Path::new("/tmp/foo.png");
+ /// assert_eq!(path.with_file_name("bar"), PathBuf::from("/tmp/bar"));
+ /// assert_eq!(path.with_file_name("bar.txt"), PathBuf::from("/tmp/bar.txt"));
+ ///
+ /// let path = Path::new("/tmp");
+ /// assert_eq!(path.with_file_name("var"), PathBuf::from("/var"));
+ /// ```
+ #[rustc_allow_incoherent_impl]
+ #[stable(feature = "rust1", since = "1.0.0")]
+ #[must_use]
+ pub fn with_file_name>(&self, file_name: S) -> PathBuf {
+ self._with_file_name(file_name.as_ref())
+ }
+
+ #[rustc_allow_incoherent_impl]
+ fn _with_file_name(&self, file_name: &OsStr) -> PathBuf {
+ let mut buf = self.to_path_buf();
+ buf.set_file_name(file_name);
+ buf
+ }
+
+ /// Creates an owned [`PathBuf`] like `self` but with the given extension.
+ ///
+ /// See [`PathBuf::set_extension`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::path::{Path, PathBuf};
+ ///
+ /// let path = Path::new("foo.rs");
+ /// assert_eq!(path.with_extension("txt"), PathBuf::from("foo.txt"));
+ ///
+ /// let path = Path::new("foo.tar.gz");
+ /// assert_eq!(path.with_extension(""), PathBuf::from("foo.tar"));
+ /// assert_eq!(path.with_extension("xz"), PathBuf::from("foo.tar.xz"));
+ /// assert_eq!(path.with_extension("").with_extension("txt"), PathBuf::from("foo.txt"));
+ /// ```
+ #[rustc_allow_incoherent_impl]
+ #[stable(feature = "rust1", since = "1.0.0")]
+ pub fn with_extension>(&self, extension: S) -> PathBuf {
+ self._with_extension(extension.as_ref())
+ }
+
+ #[rustc_allow_incoherent_impl]
+ fn _with_extension(&self, extension: &OsStr) -> PathBuf {
+ let self_len = self.as_os_str().len();
+ let self_bytes = self.as_os_str().as_encoded_bytes();
+
+ let (new_capacity, slice_to_copy) = match self.extension() {
+ None => {
+ // Enough capacity for the extension and the dot
+ let capacity = self_len + extension.len() + 1;
+ let whole_path = self_bytes;
+ (capacity, whole_path)
+ }
+ Some(previous_extension) => {
+ let capacity = self_len + extension.len() - previous_extension.len();
+ let path_till_dot = &self_bytes[..self_len - previous_extension.len()];
+ (capacity, path_till_dot)
+ }
+ };
+
+ let mut new_path = PathBuf::with_capacity(new_capacity);
+ new_path.inner.extend_from_slice(slice_to_copy);
+ new_path.set_extension(extension);
+ new_path
+ }
+
+ /// Creates an owned [`PathBuf`] like `self` but with the extension added.
+ ///
+ /// See [`PathBuf::add_extension`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(path_add_extension)]
+ ///
+ /// use std::path::{Path, PathBuf};
+ ///
+ /// let path = Path::new("foo.rs");
+ /// assert_eq!(path.with_added_extension("txt"), PathBuf::from("foo.rs.txt"));
+ ///
+ /// let path = Path::new("foo.tar.gz");
+ /// assert_eq!(path.with_added_extension(""), PathBuf::from("foo.tar.gz"));
+ /// assert_eq!(path.with_added_extension("xz"), PathBuf::from("foo.tar.gz.xz"));
+ /// assert_eq!(path.with_added_extension("").with_added_extension("txt"), PathBuf::from("foo.tar.gz.txt"));
+ /// ```
+ #[rustc_allow_incoherent_impl]
+ #[unstable(feature = "path_add_extension", issue = "127292")]
+ pub fn with_added_extension>(&self, extension: S) -> PathBuf {
+ let mut new_path = self.to_path_buf();
+ new_path.add_extension(extension);
+ new_path
+ }
+
+ /// Converts a [`Box`](Box) into a [`PathBuf`] without copying or
+ /// allocating.
+ #[rustc_allow_incoherent_impl]
+ #[stable(feature = "into_boxed_path", since = "1.20.0")]
+ #[must_use = "`self` will be dropped if the result is not used"]
+ pub fn into_path_buf(self: Box) -> PathBuf {
+ let rw = Box::into_raw(self) as *mut OsStr;
+ let inner = unsafe { Box::from_raw(rw) };
+ PathBuf { inner: OsString::from(inner) }
+ }
+}
+
+#[stable(feature = "cow_os_str_as_ref_path", since = "1.8.0")]
+impl AsRef for Cow<'_, OsStr> {
+ #[inline]
+ fn as_ref(&self) -> &Path {
+ Path::new(self)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl AsRef for OsString {
+ #[inline]
+ fn as_ref(&self) -> &Path {
+ Path::new(self)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl AsRef for String {
+ #[inline]
+ fn as_ref(&self) -> &Path {
+ Path::new(self)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl AsRef for PathBuf {
+ #[inline]
+ fn as_ref(&self) -> &Path {
+ self
+ }
+}
+
+#[stable(feature = "path_into_iter", since = "1.6.0")]
+impl<'a> IntoIterator for &'a PathBuf {
+ type Item = &'a OsStr;
+ type IntoIter = Iter<'a>;
+ #[inline]
+ fn into_iter(self) -> Iter<'a> {
+ self.iter()
+ }
+}
+
+macro_rules! impl_cmp {
+ (<$($life:lifetime),*> $lhs:ty, $rhs: ty) => {
+ #[stable(feature = "partialeq_path", since = "1.6.0")]
+ impl<$($life),*> PartialEq<$rhs> for $lhs {
+ #[inline]
+ fn eq(&self, other: &$rhs) -> bool {
+ ::eq(self, other)
+ }
+ }
+
+ #[stable(feature = "partialeq_path", since = "1.6.0")]
+ impl<$($life),*> PartialEq<$lhs> for $rhs {
+ #[inline]
+ fn eq(&self, other: &$lhs) -> bool {
+ ::eq(self, other)
+ }
+ }
+
+ #[stable(feature = "cmp_path", since = "1.8.0")]
+ impl<$($life),*> PartialOrd<$rhs> for $lhs {
+ #[inline]
+ fn partial_cmp(&self, other: &$rhs) -> Option {
+ ::partial_cmp(self, other)
+ }
+ }
+
+ #[stable(feature = "cmp_path", since = "1.8.0")]
+ impl<$($life),*> PartialOrd<$lhs> for $rhs {
+ #[inline]
+ fn partial_cmp(&self, other: &$lhs) -> Option {
+ ::partial_cmp(self, other)
+ }
+ }
+ };
+}
+
+impl_cmp!(<> PathBuf, Path);
+impl_cmp!(<'a> PathBuf, &'a Path);
+impl_cmp!(<'a> Cow<'a, Path>, Path);
+impl_cmp!(<'a, 'b> Cow<'a, Path>, &'b Path);
+impl_cmp!(<'a> Cow<'a, Path>, PathBuf);
+
+macro_rules! impl_cmp_os_str {
+ (<$($life:lifetime),*> $lhs:ty, $rhs: ty) => {
+ #[stable(feature = "cmp_path", since = "1.8.0")]
+ impl<$($life),*> PartialEq<$rhs> for $lhs {
+ #[inline]
+ fn eq(&self, other: &$rhs) -> bool {
+ ::eq(self, other.as_ref())
+ }
+ }
+
+ #[stable(feature = "cmp_path", since = "1.8.0")]
+ impl<$($life),*> PartialEq<$lhs> for $rhs {
+ #[inline]
+ fn eq(&self, other: &$lhs) -> bool {
+ ::eq(self.as_ref(), other)
+ }
+ }
+
+ #[stable(feature = "cmp_path", since = "1.8.0")]
+ impl<$($life),*> PartialOrd<$rhs> for $lhs {
+ #[inline]
+ fn partial_cmp(&self, other: &$rhs) -> Option {
+ ::partial_cmp(self, other.as_ref())
+ }
+ }
+
+ #[stable(feature = "cmp_path", since = "1.8.0")]
+ impl<$($life),*> PartialOrd<$lhs> for $rhs {
+ #[inline]
+ fn partial_cmp(&self, other: &$lhs) -> Option {
+ ::partial_cmp(self.as_ref(), other)
+ }
+ }
+ };
+}
+
+impl_cmp_os_str!(<> PathBuf, OsStr);
+impl_cmp_os_str!(<'a> PathBuf, &'a OsStr);
+impl_cmp_os_str!(<'a> PathBuf, Cow<'a, OsStr>);
+impl_cmp_os_str!(<> PathBuf, OsString);
+impl_cmp_os_str!(<'a> Path, Cow<'a, OsStr>);
+impl_cmp_os_str!(<> Path, OsString);
+impl_cmp_os_str!(<'a, 'b> &'a Path, Cow<'b, OsStr>);
+impl_cmp_os_str!(<'a> &'a Path, OsString);
+impl_cmp_os_str!(<'a> Cow<'a, Path>, OsStr);
+impl_cmp_os_str!(<'a, 'b> Cow<'a, Path>, &'b OsStr);
+impl_cmp_os_str!(<'a> Cow<'a, Path>, OsString);
diff --git a/library/alloc/src/path/tests.rs b/library/alloc/src/path/tests.rs
new file mode 100644
index 0000000000000..f69fd040c6b42
--- /dev/null
+++ b/library/alloc/src/path/tests.rs
@@ -0,0 +1,1689 @@
+use core::hint::black_box;
+use std::collections::{BTreeSet, HashSet};
+
+use super::*;
+
+#[allow(unknown_lints, unused_macro_rules)]
+macro_rules! t (
+ ($path:expr, iter: $iter:expr) => (
+ {
+ let path = Path::new($path);
+
+ // Forward iteration
+ let comps = path.iter()
+ .map(|p| p.to_string_lossy().into_owned())
+ .collect::>();
+ let exp: &[&str] = &$iter;
+ let exps = exp.iter().map(|s| s.to_string()).collect::>();
+ assert!(comps == exps, "iter: Expected {:?}, found {:?}",
+ exps, comps);
+
+ // Reverse iteration
+ let comps = Path::new($path).iter().rev()
+ .map(|p| p.to_string_lossy().into_owned())
+ .collect::>();
+ let exps = exps.into_iter().rev().collect::>();
+ assert!(comps == exps, "iter().rev(): Expected {:?}, found {:?}",
+ exps, comps);
+ }
+ );
+
+ ($path:expr, has_root: $has_root:expr, is_absolute: $is_absolute:expr) => (
+ {
+ let path = Path::new($path);
+
+ let act_root = path.has_root();
+ assert!(act_root == $has_root, "has_root: Expected {:?}, found {:?}",
+ $has_root, act_root);
+
+ let act_abs = path.is_absolute();
+ assert!(act_abs == $is_absolute, "is_absolute: Expected {:?}, found {:?}",
+ $is_absolute, act_abs);
+ }
+ );
+
+ ($path:expr, parent: $parent:expr, file_name: $file:expr) => (
+ {
+ let path = Path::new($path);
+
+ let parent = path.parent().map(|p| p.to_str().unwrap());
+ let exp_parent: Option<&str> = $parent;
+ assert!(parent == exp_parent, "parent: Expected {:?}, found {:?}",
+ exp_parent, parent);
+
+ let file = path.file_name().map(|p| p.to_str().unwrap());
+ let exp_file: Option<&str> = $file;
+ assert!(file == exp_file, "file_name: Expected {:?}, found {:?}",
+ exp_file, file);
+ }
+ );
+
+ ($path:expr, file_stem: $file_stem:expr, extension: $extension:expr) => (
+ {
+ let path = Path::new($path);
+
+ let stem = path.file_stem().map(|p| p.to_str().unwrap());
+ let exp_stem: Option<&str> = $file_stem;
+ assert!(stem == exp_stem, "file_stem: Expected {:?}, found {:?}",
+ exp_stem, stem);
+
+ let ext = path.extension().map(|p| p.to_str().unwrap());
+ let exp_ext: Option<&str> = $extension;
+ assert!(ext == exp_ext, "extension: Expected {:?}, found {:?}",
+ exp_ext, ext);
+ }
+ );
+
+ ($path:expr, file_prefix: $file_prefix:expr, extension: $extension:expr) => (
+ {
+ let path = Path::new($path);
+
+ let prefix = path.file_prefix().map(|p| p.to_str().unwrap());
+ let exp_prefix: Option<&str> = $file_prefix;
+ assert!(prefix == exp_prefix, "file_prefix: Expected {:?}, found {:?}",
+ exp_prefix, prefix);
+
+ let ext = path.extension().map(|p| p.to_str().unwrap());
+ let exp_ext: Option<&str> = $extension;
+ assert!(ext == exp_ext, "extension: Expected {:?}, found {:?}",
+ exp_ext, ext);
+ }
+ );
+
+ ($path:expr, iter: $iter:expr,
+ has_root: $has_root:expr, is_absolute: $is_absolute:expr,
+ parent: $parent:expr, file_name: $file:expr,
+ file_stem: $file_stem:expr, extension: $extension:expr,
+ file_prefix: $file_prefix:expr) => (
+ {
+ t!($path, iter: $iter);
+ t!($path, has_root: $has_root, is_absolute: $is_absolute);
+ t!($path, parent: $parent, file_name: $file);
+ t!($path, file_stem: $file_stem, extension: $extension);
+ t!($path, file_prefix: $file_prefix, extension: $extension);
+ }
+ );
+);
+
+#[test]
+fn into() {
+ use crate::borrow::Cow;
+
+ let static_path = Path::new("/home/foo");
+ let static_cow_path: Cow<'static, Path> = static_path.into();
+ let pathbuf = PathBuf::from("/home/foo");
+
+ {
+ let path: &Path = &pathbuf;
+ let borrowed_cow_path: Cow<'_, Path> = path.into();
+
+ assert_eq!(static_cow_path, borrowed_cow_path);
+ }
+
+ let owned_cow_path: Cow<'static, Path> = pathbuf.into();
+
+ assert_eq!(static_cow_path, owned_cow_path);
+}
+
+#[test]
+fn test_pathbuf_leak() {
+ let string = "/have/a/cake".to_owned();
+ let (len, cap) = (string.len(), string.capacity());
+ let buf = PathBuf::from(string);
+ let leaked = buf.leak();
+ assert_eq!(leaked.as_os_str().as_encoded_bytes(), b"/have/a/cake");
+ unsafe { drop(String::from_raw_parts(leaked.as_mut_os_str() as *mut OsStr as _, len, cap)) }
+}
+
+#[test]
+#[cfg(unix)]
+pub fn test_decompositions_unix() {
+ t!("",
+ iter: [],
+ has_root: false,
+ is_absolute: false,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("foo",
+ iter: ["foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("/",
+ iter: ["/"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("/foo",
+ iter: ["/", "foo"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("/"),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("foo/",
+ iter: ["foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("/foo/",
+ iter: ["/", "foo"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("/"),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("foo/bar",
+ iter: ["foo", "bar"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("/foo/bar",
+ iter: ["/", "foo", "bar"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("/foo"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("///foo///",
+ iter: ["/", "foo"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("/"),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("///foo///bar",
+ iter: ["/", "foo", "bar"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("///foo"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("./.",
+ iter: ["."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("/..",
+ iter: ["/", ".."],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("/"),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("../",
+ iter: [".."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("foo/.",
+ iter: ["foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("foo/..",
+ iter: ["foo", ".."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo"),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("foo/./",
+ iter: ["foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("foo/./bar",
+ iter: ["foo", "bar"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("foo/../",
+ iter: ["foo", ".."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo"),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("foo/../bar",
+ iter: ["foo", "..", "bar"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo/.."),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("./a",
+ iter: [".", "a"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("."),
+ file_name: Some("a"),
+ file_stem: Some("a"),
+ extension: None,
+ file_prefix: Some("a")
+ );
+
+ t!(".",
+ iter: ["."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("./",
+ iter: ["."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("a/b",
+ iter: ["a", "b"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some("b"),
+ file_stem: Some("b"),
+ extension: None,
+ file_prefix: Some("b")
+ );
+
+ t!("a//b",
+ iter: ["a", "b"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some("b"),
+ file_stem: Some("b"),
+ extension: None,
+ file_prefix: Some("b")
+ );
+
+ t!("a/./b",
+ iter: ["a", "b"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some("b"),
+ file_stem: Some("b"),
+ extension: None,
+ file_prefix: Some("b")
+ );
+
+ t!("a/b/c",
+ iter: ["a", "b", "c"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a/b"),
+ file_name: Some("c"),
+ file_stem: Some("c"),
+ extension: None,
+ file_prefix: Some("c")
+ );
+
+ t!(".foo",
+ iter: [".foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some(".foo"),
+ file_stem: Some(".foo"),
+ extension: None,
+ file_prefix: Some(".foo")
+ );
+
+ t!("a/.foo",
+ iter: ["a", ".foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some(".foo"),
+ file_stem: Some(".foo"),
+ extension: None,
+ file_prefix: Some(".foo")
+ );
+
+ t!("a/.rustfmt.toml",
+ iter: ["a", ".rustfmt.toml"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some(".rustfmt.toml"),
+ file_stem: Some(".rustfmt"),
+ extension: Some("toml"),
+ file_prefix: Some(".rustfmt")
+ );
+
+ t!("a/.x.y.z",
+ iter: ["a", ".x.y.z"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some(".x.y.z"),
+ file_stem: Some(".x.y"),
+ extension: Some("z"),
+ file_prefix: Some(".x")
+ );
+}
+
+#[test]
+#[cfg(windows)]
+pub fn test_decompositions_windows() {
+ t!("",
+ iter: [],
+ has_root: false,
+ is_absolute: false,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("foo",
+ iter: ["foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("/",
+ iter: ["\\"],
+ has_root: true,
+ is_absolute: false,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\",
+ iter: ["\\"],
+ has_root: true,
+ is_absolute: false,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("c:",
+ iter: ["c:"],
+ has_root: false,
+ is_absolute: false,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("c:\\",
+ iter: ["c:", "\\"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("c:/",
+ iter: ["c:", "\\"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("/foo",
+ iter: ["\\", "foo"],
+ has_root: true,
+ is_absolute: false,
+ parent: Some("/"),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("foo/",
+ iter: ["foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("/foo/",
+ iter: ["\\", "foo"],
+ has_root: true,
+ is_absolute: false,
+ parent: Some("/"),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("foo/bar",
+ iter: ["foo", "bar"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("/foo/bar",
+ iter: ["\\", "foo", "bar"],
+ has_root: true,
+ is_absolute: false,
+ parent: Some("/foo"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("///foo///",
+ iter: ["\\", "foo"],
+ has_root: true,
+ is_absolute: false,
+ parent: Some("/"),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("///foo///bar",
+ iter: ["\\", "foo", "bar"],
+ has_root: true,
+ is_absolute: false,
+ parent: Some("///foo"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("./.",
+ iter: ["."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("/..",
+ iter: ["\\", ".."],
+ has_root: true,
+ is_absolute: false,
+ parent: Some("/"),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("../",
+ iter: [".."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("foo/.",
+ iter: ["foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("foo/..",
+ iter: ["foo", ".."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo"),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("foo/./",
+ iter: ["foo"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: Some("foo"),
+ file_stem: Some("foo"),
+ extension: None,
+ file_prefix: Some("foo")
+ );
+
+ t!("foo/./bar",
+ iter: ["foo", "bar"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("foo/../",
+ iter: ["foo", ".."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo"),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("foo/../bar",
+ iter: ["foo", "..", "bar"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("foo/.."),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("./a",
+ iter: [".", "a"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("."),
+ file_name: Some("a"),
+ file_stem: Some("a"),
+ extension: None,
+ file_prefix: Some("a")
+ );
+
+ t!(".",
+ iter: ["."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("./",
+ iter: ["."],
+ has_root: false,
+ is_absolute: false,
+ parent: Some(""),
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("a/b",
+ iter: ["a", "b"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some("b"),
+ file_stem: Some("b"),
+ extension: None,
+ file_prefix: Some("b")
+ );
+
+ t!("a//b",
+ iter: ["a", "b"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some("b"),
+ file_stem: Some("b"),
+ extension: None,
+ file_prefix: Some("b")
+ );
+
+ t!("a/./b",
+ iter: ["a", "b"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some("b"),
+ file_stem: Some("b"),
+ extension: None,
+ file_prefix: Some("b")
+ );
+
+ t!("a/b/c",
+ iter: ["a", "b", "c"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a/b"),
+ file_name: Some("c"),
+ file_stem: Some("c"),
+ extension: None,
+ file_prefix: Some("c")
+ );
+
+ t!("a\\b\\c",
+ iter: ["a", "b", "c"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a\\b"),
+ file_name: Some("c"),
+ file_stem: Some("c"),
+ extension: None,
+ file_prefix: Some("c")
+ );
+
+ t!("\\a",
+ iter: ["\\", "a"],
+ has_root: true,
+ is_absolute: false,
+ parent: Some("\\"),
+ file_name: Some("a"),
+ file_stem: Some("a"),
+ extension: None,
+ file_prefix: Some("a")
+ );
+
+ t!("c:\\foo.txt",
+ iter: ["c:", "\\", "foo.txt"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("c:\\"),
+ file_name: Some("foo.txt"),
+ file_stem: Some("foo"),
+ extension: Some("txt"),
+ file_prefix: Some("foo")
+ );
+
+ t!("\\\\server\\share\\foo.txt",
+ iter: ["\\\\server\\share", "\\", "foo.txt"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\server\\share\\"),
+ file_name: Some("foo.txt"),
+ file_stem: Some("foo"),
+ extension: Some("txt"),
+ file_prefix: Some("foo")
+ );
+
+ t!("\\\\server\\share",
+ iter: ["\\\\server\\share", "\\"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\server",
+ iter: ["\\", "server"],
+ has_root: true,
+ is_absolute: false,
+ parent: Some("\\"),
+ file_name: Some("server"),
+ file_stem: Some("server"),
+ extension: None,
+ file_prefix: Some("server")
+ );
+
+ t!("\\\\?\\bar\\foo.txt",
+ iter: ["\\\\?\\bar", "\\", "foo.txt"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\?\\bar\\"),
+ file_name: Some("foo.txt"),
+ file_stem: Some("foo"),
+ extension: Some("txt"),
+ file_prefix: Some("foo")
+ );
+
+ t!("\\\\?\\bar",
+ iter: ["\\\\?\\bar"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\?\\",
+ iter: ["\\\\?\\"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\?\\UNC\\server\\share\\foo.txt",
+ iter: ["\\\\?\\UNC\\server\\share", "\\", "foo.txt"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\?\\UNC\\server\\share\\"),
+ file_name: Some("foo.txt"),
+ file_stem: Some("foo"),
+ extension: Some("txt"),
+ file_prefix: Some("foo")
+ );
+
+ t!("\\\\?\\UNC\\server",
+ iter: ["\\\\?\\UNC\\server"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\?\\UNC\\",
+ iter: ["\\\\?\\UNC\\"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\?\\C:\\foo.txt",
+ iter: ["\\\\?\\C:", "\\", "foo.txt"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\?\\C:\\"),
+ file_name: Some("foo.txt"),
+ file_stem: Some("foo"),
+ extension: Some("txt"),
+ file_prefix: Some("foo")
+ );
+
+ t!("\\\\?\\C:\\",
+ iter: ["\\\\?\\C:", "\\"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\?\\C:",
+ iter: ["\\\\?\\C:"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\?\\foo/bar",
+ iter: ["\\\\?\\foo/bar"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\?\\C:/foo/bar",
+ iter: ["\\\\?\\C:", "\\", "foo/bar"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\?\\C:/"),
+ file_name: Some("foo/bar"),
+ file_stem: Some("foo/bar"),
+ extension: None,
+ file_prefix: Some("foo/bar")
+ );
+
+ t!("\\\\.\\foo\\bar",
+ iter: ["\\\\.\\foo", "\\", "bar"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\.\\foo\\"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("\\\\.\\foo",
+ iter: ["\\\\.\\foo", "\\"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\.\\foo/bar",
+ iter: ["\\\\.\\foo", "\\", "bar"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\.\\foo/"),
+ file_name: Some("bar"),
+ file_stem: Some("bar"),
+ extension: None,
+ file_prefix: Some("bar")
+ );
+
+ t!("\\\\.\\foo\\bar/baz",
+ iter: ["\\\\.\\foo", "\\", "bar", "baz"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\.\\foo\\bar"),
+ file_name: Some("baz"),
+ file_stem: Some("baz"),
+ extension: None,
+ file_prefix: Some("baz")
+ );
+
+ t!("\\\\.\\",
+ iter: ["\\\\.\\", "\\"],
+ has_root: true,
+ is_absolute: true,
+ parent: None,
+ file_name: None,
+ file_stem: None,
+ extension: None,
+ file_prefix: None
+ );
+
+ t!("\\\\?\\a\\b\\",
+ iter: ["\\\\?\\a", "\\", "b"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\?\\a\\"),
+ file_name: Some("b"),
+ file_stem: Some("b"),
+ extension: None,
+ file_prefix: Some("b")
+ );
+
+ t!("\\\\?\\C:\\foo.txt.zip",
+ iter: ["\\\\?\\C:", "\\", "foo.txt.zip"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\?\\C:\\"),
+ file_name: Some("foo.txt.zip"),
+ file_stem: Some("foo.txt"),
+ extension: Some("zip"),
+ file_prefix: Some("foo")
+ );
+
+ t!("\\\\?\\C:\\.foo.txt.zip",
+ iter: ["\\\\?\\C:", "\\", ".foo.txt.zip"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\?\\C:\\"),
+ file_name: Some(".foo.txt.zip"),
+ file_stem: Some(".foo.txt"),
+ extension: Some("zip"),
+ file_prefix: Some(".foo")
+ );
+
+ t!("\\\\?\\C:\\.foo",
+ iter: ["\\\\?\\C:", "\\", ".foo"],
+ has_root: true,
+ is_absolute: true,
+ parent: Some("\\\\?\\C:\\"),
+ file_name: Some(".foo"),
+ file_stem: Some(".foo"),
+ extension: None,
+ file_prefix: Some(".foo")
+ );
+
+ t!("a/.x.y.z",
+ iter: ["a", ".x.y.z"],
+ has_root: false,
+ is_absolute: false,
+ parent: Some("a"),
+ file_name: Some(".x.y.z"),
+ file_stem: Some(".x.y"),
+ extension: Some("z"),
+ file_prefix: Some(".x")
+ );
+}
+
+#[test]
+pub fn test_stem_ext() {
+ t!("foo",
+ file_stem: Some("foo"),
+ extension: None
+ );
+
+ t!("foo.",
+ file_stem: Some("foo"),
+ extension: Some("")
+ );
+
+ t!(".foo",
+ file_stem: Some(".foo"),
+ extension: None
+ );
+
+ t!("foo.txt",
+ file_stem: Some("foo"),
+ extension: Some("txt")
+ );
+
+ t!("foo.bar.txt",
+ file_stem: Some("foo.bar"),
+ extension: Some("txt")
+ );
+
+ t!("foo.bar.",
+ file_stem: Some("foo.bar"),
+ extension: Some("")
+ );
+
+ t!(".", file_stem: None, extension: None);
+
+ t!("..", file_stem: None, extension: None);
+
+ t!(".x.y.z", file_stem: Some(".x.y"), extension: Some("z"));
+
+ t!("..x.y.z", file_stem: Some("..x.y"), extension: Some("z"));
+
+ t!("", file_stem: None, extension: None);
+}
+
+#[test]
+pub fn test_prefix_ext() {
+ t!("foo",
+ file_prefix: Some("foo"),
+ extension: None
+ );
+
+ t!("foo.",
+ file_prefix: Some("foo"),
+ extension: Some("")
+ );
+
+ t!(".foo",
+ file_prefix: Some(".foo"),
+ extension: None
+ );
+
+ t!("foo.txt",
+ file_prefix: Some("foo"),
+ extension: Some("txt")
+ );
+
+ t!("foo.bar.txt",
+ file_prefix: Some("foo"),
+ extension: Some("txt")
+ );
+
+ t!("foo.bar.",
+ file_prefix: Some("foo"),
+ extension: Some("")
+ );
+
+ t!(".", file_prefix: None, extension: None);
+
+ t!("..", file_prefix: None, extension: None);
+
+ t!(".x.y.z", file_prefix: Some(".x"), extension: Some("z"));
+
+ t!("..x.y.z", file_prefix: Some("."), extension: Some("z"));
+
+ t!("", file_prefix: None, extension: None);
+}
+
+#[test]
+pub fn test_push() {
+ macro_rules! tp (
+ ($path:expr, $push:expr, $expected:expr) => ({
+ let mut actual = PathBuf::from($path);
+ actual.push($push);
+ assert!(actual.to_str() == Some($expected),
+ "pushing {:?} onto {:?}: Expected {:?}, got {:?}",
+ $push, $path, $expected, actual.to_str().unwrap());
+ });
+ );
+
+ if cfg!(unix) || cfg!(all(target_env = "sgx", target_vendor = "fortanix")) {
+ tp!("", "foo", "foo");
+ tp!("foo", "bar", "foo/bar");
+ tp!("foo/", "bar", "foo/bar");
+ tp!("foo//", "bar", "foo//bar");
+ tp!("foo/.", "bar", "foo/./bar");
+ tp!("foo./.", "bar", "foo././bar");
+ tp!("foo", "", "foo/");
+ tp!("foo", ".", "foo/.");
+ tp!("foo", "..", "foo/..");
+ tp!("foo", "/", "/");
+ tp!("/foo/bar", "/", "/");
+ tp!("/foo/bar", "/baz", "/baz");
+ tp!("/foo/bar", "./baz", "/foo/bar/./baz");
+ } else {
+ tp!("", "foo", "foo");
+ tp!("foo", "bar", r"foo\bar");
+ tp!("foo/", "bar", r"foo/bar");
+ tp!(r"foo\", "bar", r"foo\bar");
+ tp!("foo//", "bar", r"foo//bar");
+ tp!(r"foo\\", "bar", r"foo\\bar");
+ tp!("foo/.", "bar", r"foo/.\bar");
+ tp!("foo./.", "bar", r"foo./.\bar");
+ tp!(r"foo\.", "bar", r"foo\.\bar");
+ tp!(r"foo.\.", "bar", r"foo.\.\bar");
+ tp!("foo", "", "foo\\");
+ tp!("foo", ".", r"foo\.");
+ tp!("foo", "..", r"foo\..");
+ tp!("foo", "/", "/");
+ tp!("foo", r"\", r"\");
+ tp!("/foo/bar", "/", "/");
+ tp!(r"\foo\bar", r"\", r"\");
+ tp!("/foo/bar", "/baz", "/baz");
+ tp!("/foo/bar", r"\baz", r"\baz");
+ tp!("/foo/bar", "./baz", r"/foo/bar\./baz");
+ tp!("/foo/bar", r".\baz", r"/foo/bar\.\baz");
+
+ tp!("c:\\", "windows", "c:\\windows");
+ tp!("c:", "windows", "c:windows");
+
+ tp!("a\\b\\c", "d", "a\\b\\c\\d");
+ tp!("\\a\\b\\c", "d", "\\a\\b\\c\\d");
+ tp!("a\\b", "c\\d", "a\\b\\c\\d");
+ tp!("a\\b", "\\c\\d", "\\c\\d");
+ tp!("a\\b", ".", "a\\b\\.");
+ tp!("a\\b", "..\\c", "a\\b\\..\\c");
+ tp!("a\\b", "C:a.txt", "C:a.txt");
+ tp!("a\\b", "C:\\a.txt", "C:\\a.txt");
+ tp!("C:\\a", "C:\\b.txt", "C:\\b.txt");
+ tp!("C:\\a\\b\\c", "C:d", "C:d");
+ tp!("C:a\\b\\c", "C:d", "C:d");
+ tp!("C:", r"a\b\c", r"C:a\b\c");
+ tp!("C:", r"..\a", r"C:..\a");
+ tp!("\\\\server\\share\\foo", "bar", "\\\\server\\share\\foo\\bar");
+ tp!("\\\\server\\share\\foo", "C:baz", "C:baz");
+ tp!("\\\\?\\C:\\a\\b", "C:c\\d", "C:c\\d");
+ tp!("\\\\?\\C:a\\b", "C:c\\d", "C:c\\d");
+ tp!("\\\\?\\C:\\a\\b", "C:\\c\\d", "C:\\c\\d");
+ tp!("\\\\?\\foo\\bar", "baz", "\\\\?\\foo\\bar\\baz");
+ tp!("\\\\?\\UNC\\server\\share\\foo", "bar", "\\\\?\\UNC\\server\\share\\foo\\bar");
+ tp!("\\\\?\\UNC\\server\\share", "C:\\a", "C:\\a");
+ tp!("\\\\?\\UNC\\server\\share", "C:a", "C:a");
+
+ // Note: modified from old path API
+ tp!("\\\\?\\UNC\\server", "foo", "\\\\?\\UNC\\server\\foo");
+
+ tp!("C:\\a", "\\\\?\\UNC\\server\\share", "\\\\?\\UNC\\server\\share");
+ tp!("\\\\.\\foo\\bar", "baz", "\\\\.\\foo\\bar\\baz");
+ tp!("\\\\.\\foo\\bar", "C:a", "C:a");
+ // again, not sure about the following, but I'm assuming \\.\ should be verbatim
+ tp!("\\\\.\\foo", "..\\bar", "\\\\.\\foo\\..\\bar");
+
+ tp!("\\\\?\\C:", "foo", "\\\\?\\C:\\foo"); // this is a weird one
+
+ tp!(r"\\?\C:\bar", "../foo", r"\\?\C:\foo");
+ tp!(r"\\?\C:\bar", "../../foo", r"\\?\C:\foo");
+ tp!(r"\\?\C:\", "../foo", r"\\?\C:\foo");
+ tp!(r"\\?\C:", r"D:\foo/./", r"D:\foo/./");
+ tp!(r"\\?\C:", r"\\?\D:\foo\.\", r"\\?\D:\foo\.\");
+ tp!(r"\\?\A:\x\y", "/foo", r"\\?\A:\foo");
+ tp!(r"\\?\A:", r"..\foo\.", r"\\?\A:\foo");
+ tp!(r"\\?\A:\x\y", r".\foo\.", r"\\?\A:\x\y\foo");
+ tp!(r"\\?\A:\x\y", r"", r"\\?\A:\x\y\");
+ }
+}
+
+#[test]
+pub fn test_pop() {
+ macro_rules! tp (
+ ($path:expr, $expected:expr, $output:expr) => ({
+ let mut actual = PathBuf::from($path);
+ let output = actual.pop();
+ assert!(actual.to_str() == Some($expected) && output == $output,
+ "popping from {:?}: Expected {:?}/{:?}, got {:?}/{:?}",
+ $path, $expected, $output,
+ actual.to_str().unwrap(), output);
+ });
+ );
+
+ tp!("", "", false);
+ tp!("/", "/", false);
+ tp!("foo", "", true);
+ tp!(".", "", true);
+ tp!("/foo", "/", true);
+ tp!("/foo/bar", "/foo", true);
+ tp!("foo/bar", "foo", true);
+ tp!("foo/.", "", true);
+ tp!("foo//bar", "foo", true);
+
+ if cfg!(windows) {
+ tp!("a\\b\\c", "a\\b", true);
+ tp!("\\a", "\\", true);
+ tp!("\\", "\\", false);
+
+ tp!("C:\\a\\b", "C:\\a", true);
+ tp!("C:\\a", "C:\\", true);
+ tp!("C:\\", "C:\\", false);
+ tp!("C:a\\b", "C:a", true);
+ tp!("C:a", "C:", true);
+ tp!("C:", "C:", false);
+ tp!("\\\\server\\share\\a\\b", "\\\\server\\share\\a", true);
+ tp!("\\\\server\\share\\a", "\\\\server\\share\\", true);
+ tp!("\\\\server\\share", "\\\\server\\share", false);
+ tp!("\\\\?\\a\\b\\c", "\\\\?\\a\\b", true);
+ tp!("\\\\?\\a\\b", "\\\\?\\a\\", true);
+ tp!("\\\\?\\a", "\\\\?\\a", false);
+ tp!("\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", true);
+ tp!("\\\\?\\C:\\a", "\\\\?\\C:\\", true);
+ tp!("\\\\?\\C:\\", "\\\\?\\C:\\", false);
+ tp!("\\\\?\\UNC\\server\\share\\a\\b", "\\\\?\\UNC\\server\\share\\a", true);
+ tp!("\\\\?\\UNC\\server\\share\\a", "\\\\?\\UNC\\server\\share\\", true);
+ tp!("\\\\?\\UNC\\server\\share", "\\\\?\\UNC\\server\\share", false);
+ tp!("\\\\.\\a\\b\\c", "\\\\.\\a\\b", true);
+ tp!("\\\\.\\a\\b", "\\\\.\\a\\", true);
+ tp!("\\\\.\\a", "\\\\.\\a", false);
+
+ tp!("\\\\?\\a\\b\\", "\\\\?\\a\\", true);
+ }
+}
+
+#[test]
+pub fn test_set_file_name() {
+ macro_rules! tfn (
+ ($path:expr, $file:expr, $expected:expr) => ({
+ let mut p = PathBuf::from($path);
+ p.set_file_name($file);
+ assert!(p.to_str() == Some($expected),
+ "setting file name of {:?} to {:?}: Expected {:?}, got {:?}",
+ $path, $file, $expected,
+ p.to_str().unwrap());
+ });
+ );
+
+ tfn!("foo", "foo", "foo");
+ tfn!("foo", "bar", "bar");
+ tfn!("foo", "", "");
+ tfn!("", "foo", "foo");
+ if cfg!(unix) || cfg!(all(target_env = "sgx", target_vendor = "fortanix")) {
+ tfn!(".", "foo", "./foo");
+ tfn!("foo/", "bar", "bar");
+ tfn!("foo/.", "bar", "bar");
+ tfn!("..", "foo", "../foo");
+ tfn!("foo/..", "bar", "foo/../bar");
+ tfn!("/", "foo", "/foo");
+ } else {
+ tfn!(".", "foo", r".\foo");
+ tfn!(r"foo\", "bar", r"bar");
+ tfn!(r"foo\.", "bar", r"bar");
+ tfn!("..", "foo", r"..\foo");
+ tfn!(r"foo\..", "bar", r"foo\..\bar");
+ tfn!(r"\", "foo", r"\foo");
+ }
+}
+
+#[test]
+pub fn test_set_extension() {
+ macro_rules! tfe (
+ ($path:expr, $ext:expr, $expected:expr, $output:expr) => ({
+ let mut p = PathBuf::from($path);
+ let output = p.set_extension($ext);
+ assert!(p.to_str() == Some($expected) && output == $output,
+ "setting extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}",
+ $path, $ext, $expected, $output,
+ p.to_str().unwrap(), output);
+ });
+ );
+
+ tfe!("foo", "txt", "foo.txt", true);
+ tfe!("foo.bar", "txt", "foo.txt", true);
+ tfe!("foo.bar.baz", "txt", "foo.bar.txt", true);
+ tfe!(".test", "txt", ".test.txt", true);
+ tfe!("foo.txt", "", "foo", true);
+ tfe!("foo", "", "foo", true);
+ tfe!("", "foo", "", false);
+ tfe!(".", "foo", ".", false);
+ tfe!("foo/", "bar", "foo.bar", true);
+ tfe!("foo/.", "bar", "foo.bar", true);
+ tfe!("..", "foo", "..", false);
+ tfe!("foo/..", "bar", "foo/..", false);
+ tfe!("/", "foo", "/", false);
+}
+
+#[test]
+pub fn test_add_extension() {
+ macro_rules! tfe (
+ ($path:expr, $ext:expr, $expected:expr, $output:expr) => ({
+ let mut p = PathBuf::from($path);
+ let output = p.add_extension($ext);
+ assert!(p.to_str() == Some($expected) && output == $output,
+ "adding extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}",
+ $path, $ext, $expected, $output,
+ p.to_str().unwrap(), output);
+ });
+ );
+
+ tfe!("foo", "txt", "foo.txt", true);
+ tfe!("foo.bar", "txt", "foo.bar.txt", true);
+ tfe!("foo.bar.baz", "txt", "foo.bar.baz.txt", true);
+ tfe!(".test", "txt", ".test.txt", true);
+ tfe!("foo.txt", "", "foo.txt", true);
+ tfe!("foo", "", "foo", true);
+ tfe!("", "foo", "", false);
+ tfe!(".", "foo", ".", false);
+ tfe!("foo/", "bar", "foo.bar", true);
+ tfe!("foo/.", "bar", "foo.bar", true);
+ tfe!("..", "foo", "..", false);
+ tfe!("foo/..", "bar", "foo/..", false);
+ tfe!("/", "foo", "/", false);
+
+ // edge cases
+ tfe!("/foo.ext////", "bar", "/foo.ext.bar", true);
+}
+
+#[test]
+pub fn test_with_extension() {
+ macro_rules! twe (
+ ($input:expr, $extension:expr, $expected:expr) => ({
+ let input = Path::new($input);
+ let output = input.with_extension($extension);
+
+ assert!(
+ output.to_str() == Some($expected),
+ "calling Path::new({:?}).with_extension({:?}): Expected {:?}, got {:?}",
+ $input, $extension, $expected, output,
+ );
+ });
+ );
+
+ twe!("foo", "txt", "foo.txt");
+ twe!("foo.bar", "txt", "foo.txt");
+ twe!("foo.bar.baz", "txt", "foo.bar.txt");
+ twe!(".test", "txt", ".test.txt");
+ twe!("foo.txt", "", "foo");
+ twe!("foo", "", "foo");
+ twe!("", "foo", "");
+ twe!(".", "foo", ".");
+ twe!("foo/", "bar", "foo.bar");
+ twe!("foo/.", "bar", "foo.bar");
+ twe!("..", "foo", "..");
+ twe!("foo/..", "bar", "foo/..");
+ twe!("/", "foo", "/");
+
+ // New extension is smaller than file name
+ twe!("aaa_aaa_aaa", "bbb_bbb", "aaa_aaa_aaa.bbb_bbb");
+ // New extension is greater than file name
+ twe!("bbb_bbb", "aaa_aaa_aaa", "bbb_bbb.aaa_aaa_aaa");
+
+ // New extension is smaller than previous extension
+ twe!("ccc.aaa_aaa_aaa", "bbb_bbb", "ccc.bbb_bbb");
+ // New extension is greater than previous extension
+ twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.aaa_aaa_aaa");
+}
+
+#[test]
+pub fn test_with_added_extension() {
+ macro_rules! twe (
+ ($input:expr, $extension:expr, $expected:expr) => ({
+ let input = Path::new($input);
+ let output = input.with_added_extension($extension);
+
+ assert!(
+ output.to_str() == Some($expected),
+ "calling Path::new({:?}).with_added_extension({:?}): Expected {:?}, got {:?}",
+ $input, $extension, $expected, output,
+ );
+ });
+ );
+
+ twe!("foo", "txt", "foo.txt");
+ twe!("foo.bar", "txt", "foo.bar.txt");
+ twe!("foo.bar.baz", "txt", "foo.bar.baz.txt");
+ twe!(".test", "txt", ".test.txt");
+ twe!("foo.txt", "", "foo.txt");
+ twe!("foo", "", "foo");
+ twe!("", "foo", "");
+ twe!(".", "foo", ".");
+ twe!("foo/", "bar", "foo.bar");
+ twe!("foo/.", "bar", "foo.bar");
+ twe!("..", "foo", "..");
+ twe!("foo/..", "bar", "foo/..");
+ twe!("/", "foo", "/");
+
+ // edge cases
+ twe!("/foo.ext////", "bar", "/foo.ext.bar");
+
+ // New extension is smaller than file name
+ twe!("aaa_aaa_aaa", "bbb_bbb", "aaa_aaa_aaa.bbb_bbb");
+ // New extension is greater than file name
+ twe!("bbb_bbb", "aaa_aaa_aaa", "bbb_bbb.aaa_aaa_aaa");
+
+ // New extension is smaller than previous extension
+ twe!("ccc.aaa_aaa_aaa", "bbb_bbb", "ccc.aaa_aaa_aaa.bbb_bbb");
+ // New extension is greater than previous extension
+ twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.bbb_bbb.aaa_aaa_aaa");
+}
+
+#[test]
+fn test_eq_receivers() {
+ use crate::borrow::Cow;
+
+ let borrowed: &Path = Path::new("foo/bar");
+ let mut owned: PathBuf = PathBuf::new();
+ owned.push("foo");
+ owned.push("bar");
+ let borrowed_cow: Cow<'_, Path> = borrowed.into();
+ let owned_cow: Cow<'_, Path> = owned.clone().into();
+
+ macro_rules! t {
+ ($($current:expr),+) => {
+ $(
+ assert_eq!($current, borrowed);
+ assert_eq!($current, owned);
+ assert_eq!($current, borrowed_cow);
+ assert_eq!($current, owned_cow);
+ )+
+ }
+ }
+
+ t!(borrowed, owned, borrowed_cow, owned_cow);
+}
+
+#[test]
+fn into_boxed() {
+ let orig: &str = "some/sort/of/path";
+ let path = Path::new(orig);
+ let boxed: Box = Box::from(path);
+ let path_buf = path.to_owned().into_boxed_path().into_path_buf();
+ assert_eq!(path, &*boxed);
+ assert_eq!(&*boxed, &*path_buf);
+ assert_eq!(&*path_buf, path);
+}
+
+#[test]
+fn test_clone_into() {
+ let mut path_buf = PathBuf::from("supercalifragilisticexpialidocious");
+ let path = Path::new("short");
+ path.clone_into(&mut path_buf);
+ assert_eq!(path, path_buf);
+ assert!(path_buf.into_os_string().capacity() >= 15);
+}
+
+#[test]
+fn into_rc() {
+ let orig = "hello/world";
+ let path = Path::new(orig);
+ let rc: Rc = Rc::from(path);
+ let arc: Arc = Arc::from(path);
+
+ assert_eq!(&*rc, path);
+ assert_eq!(&*arc, path);
+
+ let rc2: Rc = Rc::from(path.to_owned());
+ let arc2: Arc = Arc::from(path.to_owned());
+
+ assert_eq!(&*rc2, path);
+ assert_eq!(&*arc2, path);
+}
+
+#[test]
+#[should_panic = "path separator"]
+fn test_extension_path_sep() {
+ let mut path = PathBuf::from("path/to/file");
+ path.set_extension("d/../../../../../etc/passwd");
+}
+
+#[test]
+#[should_panic = "path separator"]
+#[cfg(windows)]
+fn test_extension_path_sep_alternate() {
+ let mut path = PathBuf::from("path/to/file");
+ path.set_extension("d\\test");
+}
+
+#[test]
+#[cfg(not(windows))]
+fn test_extension_path_sep_alternate() {
+ let mut path = PathBuf::from("path/to/file");
+ path.set_extension("d\\test");
+ assert_eq!(path, Path::new("path/to/file.d\\test"));
+}
+
+#[bench]
+#[cfg_attr(miri, ignore)] // Miri isn't fast...
+fn bench_path_cmp_fast_path_buf_sort(b: &mut test::Bencher) {
+ let prefix = "my/home";
+ let mut paths: Vec<_> =
+ (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect();
+
+ paths.sort();
+
+ b.iter(|| {
+ black_box(paths.as_mut_slice()).sort_unstable();
+ });
+}
+
+#[bench]
+#[cfg_attr(miri, ignore)] // Miri isn't fast...
+fn bench_path_cmp_fast_path_long(b: &mut test::Bencher) {
+ let prefix = "/my/home/is/my/castle/and/my/castle/has/a/rusty/workbench/";
+ let paths: Vec<_> =
+ (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect();
+
+ let mut set = BTreeSet::new();
+
+ paths.iter().for_each(|p| {
+ set.insert(p.as_path());
+ });
+
+ b.iter(|| {
+ set.remove(paths[500].as_path());
+ set.insert(paths[500].as_path());
+ });
+}
+
+#[bench]
+#[cfg_attr(miri, ignore)] // Miri isn't fast...
+fn bench_path_cmp_fast_path_short(b: &mut test::Bencher) {
+ let prefix = "my/home";
+ let paths: Vec<_> =
+ (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect();
+
+ let mut set = BTreeSet::new();
+
+ paths.iter().for_each(|p| {
+ set.insert(p.as_path());
+ });
+
+ b.iter(|| {
+ set.remove(paths[500].as_path());
+ set.insert(paths[500].as_path());
+ });
+}
+
+#[bench]
+#[cfg_attr(miri, ignore)] // Miri isn't fast...
+fn bench_path_hashset(b: &mut test::Bencher) {
+ let prefix = "/my/home/is/my/castle/and/my/castle/has/a/rusty/workbench/";
+ let paths: Vec<_> =
+ (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect();
+
+ let mut set = HashSet::new();
+
+ paths.iter().for_each(|p| {
+ set.insert(p.as_path());
+ });
+
+ b.iter(|| {
+ set.remove(paths[500].as_path());
+ set.insert(black_box(paths[500].as_path()))
+ });
+}
+
+#[bench]
+#[cfg_attr(miri, ignore)] // Miri isn't fast...
+fn bench_path_hashset_miss(b: &mut test::Bencher) {
+ let prefix = "/my/home/is/my/castle/and/my/castle/has/a/rusty/workbench/";
+ let paths: Vec<_> =
+ (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect();
+
+ let mut set = HashSet::new();
+
+ paths.iter().for_each(|p| {
+ set.insert(p.as_path());
+ });
+
+ let probe = PathBuf::from(prefix).join("other");
+
+ b.iter(|| set.remove(black_box(probe.as_path())));
+}
diff --git a/library/core/src/ffi/mod.rs b/library/core/src/ffi/mod.rs
index ec1f9052a1564..b0d7993530118 100644
--- a/library/core/src/ffi/mod.rs
+++ b/library/core/src/ffi/mod.rs
@@ -23,6 +23,22 @@ use crate::fmt;
#[unstable(feature = "c_str_module", issue = "112134")]
pub mod c_str;
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+#[doc(hidden)]
+pub mod os_str;
+
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+#[doc(hidden)]
+pub mod wtf8;
+
#[unstable(
feature = "c_variadic",
issue = "44930",
diff --git a/library/core/src/ffi/os_str.rs b/library/core/src/ffi/os_str.rs
new file mode 100644
index 0000000000000..f010d7f0e7f0c
--- /dev/null
+++ b/library/core/src/ffi/os_str.rs
@@ -0,0 +1,626 @@
+//! [`OsStr`] abd their related types.
+
+use crate::clone::CloneToUninit;
+use crate::hash::{Hash, Hasher};
+use crate::ops::{self, Range};
+use crate::ptr::addr_of_mut;
+use crate::{cmp, fmt, slice};
+
+mod private {
+ /// This trait being unreachable from outside the crate
+ /// prevents outside implementations of our extension traits.
+ /// This allows adding more trait methods in the future.
+ #[unstable(feature = "sealed", issue = "none")]
+ pub trait Sealed {}
+}
+
+#[cfg(any(target_os = "windows", target_os = "uefi"))]
+mod wtf8;
+
+#[cfg(any(target_os = "windows", target_os = "uefi"))]
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+#[doc(hidden)]
+pub use wtf8::Slice;
+
+#[cfg(not(any(target_os = "windows", target_os = "uefi")))]
+mod bytes;
+
+#[cfg(not(any(target_os = "windows", target_os = "uefi")))]
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+#[doc(hidden)]
+pub use bytes::Slice;
+
+#[cfg(any(target_os = "windows", target_os = "uefi"))]
+#[stable(feature = "rust1", since = "1.0.0")]
+pub mod os_str_ext_windows;
+
+#[cfg(not(any(target_os = "windows", target_os = "uefi")))]
+#[stable(feature = "rust1", since = "1.0.0")]
+pub mod os_str_ext_unix;
+
+/// Borrowed reference to an OS string (see [`OsString`]).
+///
+/// This type represents a borrowed reference to a string in the operating system's preferred
+/// representation.
+///
+/// `&OsStr` is to [`OsString`] as &[str]
is to [`String`]: the
+/// former in each pair are borrowed references; the latter are owned strings.
+///
+/// See the [module's toplevel documentation about conversions][conversions] for a discussion on
+/// the traits which `OsStr` implements for [conversions] from/to native representations.
+///
+/// [conversions]: super#conversions
+#[cfg_attr(not(test), rustc_diagnostic_item = "OsStr")]
+#[stable(feature = "rust1", since = "1.0.0")]
+// `OsStr::from_inner` current implementation relies
+// on `OsStr` being layout-compatible with `Slice`.
+// However, `OsStr` layout is considered an implementation detail and must not be relied upon.
+#[repr(transparent)]
+#[rustc_has_incoherent_inherent_impls]
+pub struct OsStr {
+ inner: Slice,
+}
+
+/// Allows extension traits within `std`.
+#[unstable(feature = "sealed", issue = "none")]
+impl private::Sealed for OsStr {}
+
+impl OsStr {
+ /// Coerces into an `OsStr` slice.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsStr;
+ ///
+ /// let os_str = OsStr::new("foo");
+ /// ```
+ #[inline]
+ #[stable(feature = "rust1", since = "1.0.0")]
+ pub fn new + ?Sized>(s: &S) -> &OsStr {
+ s.as_ref()
+ }
+
+ /// Converts a slice of bytes to an OS string slice without checking that the string contains
+ /// valid `OsStr`-encoded data.
+ ///
+ /// The byte encoding is an unspecified, platform-specific, self-synchronizing superset of UTF-8.
+ /// By being a self-synchronizing superset of UTF-8, this encoding is also a superset of 7-bit
+ /// ASCII.
+ ///
+ /// See the [module's toplevel documentation about conversions][conversions] for safe,
+ /// cross-platform [conversions] from/to native representations.
+ ///
+ /// # Safety
+ ///
+ /// As the encoding is unspecified, callers must pass in bytes that originated as a mixture of
+ /// validated UTF-8 and bytes from [`OsStr::as_encoded_bytes`] from within the same Rust version
+ /// built for the same target platform. For example, reconstructing an `OsStr` from bytes sent
+ /// over the network or stored in a file will likely violate these safety rules.
+ ///
+ /// Due to the encoding being self-synchronizing, the bytes from [`OsStr::as_encoded_bytes`] can be
+ /// split either immediately before or immediately after any valid non-empty UTF-8 substring.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::ffi::OsStr;
+ ///
+ /// let os_str = OsStr::new("Mary had a little lamb");
+ /// let bytes = os_str.as_encoded_bytes();
+ /// let words = bytes.split(|b| *b == b' ');
+ /// let words: Vec<&OsStr> = words.map(|word| {
+ /// // SAFETY:
+ /// // - Each `word` only contains content that originated from `OsStr::as_encoded_bytes`
+ /// // - Only split with ASCII whitespace which is a non-empty UTF-8 substring
+ /// unsafe { OsStr::from_encoded_bytes_unchecked(word) }
+ /// }).collect();
+ /// ```
+ ///
+ /// [conversions]: super#conversions
+ #[inline]
+ #[stable(feature = "os_str_bytes", since = "1.74.0")]
+ pub unsafe fn from_encoded_bytes_unchecked(bytes: &[u8]) -> &Self {
+ // SAFETY: unsafe fn
+ Self::from_inner(unsafe { Slice::from_encoded_bytes_unchecked(bytes) })
+ }
+
+ /// Create immutable [`OsStr`] from [`Slice`]
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ #[doc(hidden)]
+ pub fn from_inner(inner: &Slice) -> &OsStr {
+ // SAFETY: OsStr is just a wrapper of Slice,
+ // therefore converting &Slice to &OsStr is safe.
+ unsafe { &*(inner as *const Slice as *const OsStr) }
+ }
+
+ /// Create mutable [`OsStr`] from [`Slice`]
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ #[doc(hidden)]
+ pub fn from_inner_mut(inner: &mut Slice) -> &mut OsStr {
+ // SAFETY: OsStr is just a wrapper of Slice,
+ // therefore converting &mut Slice to &mut OsStr is safe.
+ // Any method that mutates OsStr must be careful not to
+ // break platform-specific encoding, in particular Wtf8 on Windows.
+ unsafe { &mut *(inner as *mut Slice as *mut OsStr) }
+ }
+
+ /// Yields a &[str]
slice if the `OsStr` is valid Unicode.
+ ///
+ /// This conversion may entail doing a check for UTF-8 validity.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsStr;
+ ///
+ /// let os_str = OsStr::new("foo");
+ /// assert_eq!(os_str.to_str(), Some("foo"));
+ /// ```
+ #[stable(feature = "rust1", since = "1.0.0")]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[inline]
+ pub fn to_str(&self) -> Option<&str> {
+ self.inner.to_str().ok()
+ }
+
+ /// Checks whether the `OsStr` is empty.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsStr;
+ ///
+ /// let os_str = OsStr::new("");
+ /// assert!(os_str.is_empty());
+ ///
+ /// let os_str = OsStr::new("foo");
+ /// assert!(!os_str.is_empty());
+ /// ```
+ #[stable(feature = "osstring_simple_functions", since = "1.9.0")]
+ #[must_use]
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.inner.inner.is_empty()
+ }
+
+ /// Returns the length of this `OsStr`.
+ ///
+ /// Note that this does **not** return the number of bytes in the string in
+ /// OS string form.
+ ///
+ /// The length returned is that of the underlying storage used by `OsStr`.
+ /// As discussed in the [`OsString`] introduction, [`OsString`] and `OsStr`
+ /// store strings in a form best suited for cheap inter-conversion between
+ /// native-platform and Rust string forms, which may differ significantly
+ /// from both of them, including in storage size and encoding.
+ ///
+ /// This number is simply useful for passing to other methods, like
+ /// [`OsString::with_capacity`] to avoid reallocations.
+ ///
+ /// See the main `OsString` documentation information about encoding and capacity units.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsStr;
+ ///
+ /// let os_str = OsStr::new("");
+ /// assert_eq!(os_str.len(), 0);
+ ///
+ /// let os_str = OsStr::new("foo");
+ /// assert_eq!(os_str.len(), 3);
+ /// ```
+ #[stable(feature = "osstring_simple_functions", since = "1.9.0")]
+ #[must_use]
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.inner.inner.len()
+ }
+
+ /// Converts an OS string slice to a byte slice. To convert the byte slice back into an OS
+ /// string slice, use the [`OsStr::from_encoded_bytes_unchecked`] function.
+ ///
+ /// The byte encoding is an unspecified, platform-specific, self-synchronizing superset of UTF-8.
+ /// By being a self-synchronizing superset of UTF-8, this encoding is also a superset of 7-bit
+ /// ASCII.
+ ///
+ /// Note: As the encoding is unspecified, any sub-slice of bytes that is not valid UTF-8 should
+ /// be treated as opaque and only comparable within the same Rust version built for the same
+ /// target platform. For example, sending the slice over the network or storing it in a file
+ /// will likely result in incompatible byte slices. See [`OsString`] for more encoding details
+ /// and [`std::ffi`] for platform-specific, specified conversions.
+ ///
+ /// [`std::ffi`]: crate::ffi
+ #[inline]
+ #[stable(feature = "os_str_bytes", since = "1.74.0")]
+ pub fn as_encoded_bytes(&self) -> &[u8] {
+ self.inner.as_encoded_bytes()
+ }
+
+ /// Takes a substring based on a range that corresponds to the return value of
+ /// [`OsStr::as_encoded_bytes`].
+ ///
+ /// The range's start and end must lie on valid `OsStr` boundaries.
+ /// A valid `OsStr` boundary is one of:
+ /// - The start of the string
+ /// - The end of the string
+ /// - Immediately before a valid non-empty UTF-8 substring
+ /// - Immediately after a valid non-empty UTF-8 substring
+ ///
+ /// # Panics
+ ///
+ /// Panics if `range` does not lie on valid `OsStr` boundaries or if it
+ /// exceeds the end of the string.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// #![feature(os_str_slice)]
+ ///
+ /// use std::ffi::OsStr;
+ ///
+ /// let os_str = OsStr::new("foo=bar");
+ /// let bytes = os_str.as_encoded_bytes();
+ /// if let Some(index) = bytes.iter().position(|b| *b == b'=') {
+ /// let key = os_str.slice_encoded_bytes(..index);
+ /// let value = os_str.slice_encoded_bytes(index + 1..);
+ /// assert_eq!(key, "foo");
+ /// assert_eq!(value, "bar");
+ /// }
+ /// ```
+ #[unstable(feature = "os_str_slice", issue = "118485")]
+ pub fn slice_encoded_bytes>(&self, range: R) -> &Self {
+ let encoded_bytes = self.as_encoded_bytes();
+ let Range { start, end } = slice::range(range, ..encoded_bytes.len());
+
+ // `check_public_boundary` should panic if the index does not lie on an
+ // `OsStr` boundary as described above. It's possible to do this in an
+ // encoding-agnostic way, but details of the internal encoding might
+ // permit a more efficient implementation.
+ self.inner.check_public_boundary(start);
+ self.inner.check_public_boundary(end);
+
+ // SAFETY: `slice::range` ensures that `start` and `end` are valid
+ let slice = unsafe { encoded_bytes.get_unchecked(start..end) };
+
+ // SAFETY: `slice` comes from `self` and we validated the boundaries
+ unsafe { Self::from_encoded_bytes_unchecked(slice) }
+ }
+
+ /// Converts this string to its ASCII lower case equivalent in-place.
+ ///
+ /// ASCII letters 'A' to 'Z' are mapped to 'a' to 'z',
+ /// but non-ASCII letters are unchanged.
+ ///
+ /// To return a new lowercased value without modifying the existing one, use
+ /// [`OsStr::to_ascii_lowercase`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsString;
+ ///
+ /// let mut s = OsString::from("GRÜßE, JÜRGEN ❤");
+ ///
+ /// s.make_ascii_lowercase();
+ ///
+ /// assert_eq!("grÜße, jÜrgen ❤", s);
+ /// ```
+ #[stable(feature = "osstring_ascii", since = "1.53.0")]
+ #[inline]
+ pub fn make_ascii_lowercase(&mut self) {
+ self.inner.make_ascii_lowercase()
+ }
+
+ /// Converts this string to its ASCII upper case equivalent in-place.
+ ///
+ /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z',
+ /// but non-ASCII letters are unchanged.
+ ///
+ /// To return a new uppercased value without modifying the existing one, use
+ /// [`OsStr::to_ascii_uppercase`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsString;
+ ///
+ /// let mut s = OsString::from("Grüße, Jürgen ❤");
+ ///
+ /// s.make_ascii_uppercase();
+ ///
+ /// assert_eq!("GRüßE, JüRGEN ❤", s);
+ /// ```
+ #[stable(feature = "osstring_ascii", since = "1.53.0")]
+ #[inline]
+ pub fn make_ascii_uppercase(&mut self) {
+ self.inner.make_ascii_uppercase()
+ }
+
+ /// Checks if all characters in this string are within the ASCII range.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsString;
+ ///
+ /// let ascii = OsString::from("hello!\n");
+ /// let non_ascii = OsString::from("Grüße, Jürgen ❤");
+ ///
+ /// assert!(ascii.is_ascii());
+ /// assert!(!non_ascii.is_ascii());
+ /// ```
+ #[stable(feature = "osstring_ascii", since = "1.53.0")]
+ #[must_use]
+ #[inline]
+ pub fn is_ascii(&self) -> bool {
+ self.inner.is_ascii()
+ }
+
+ /// Checks that two strings are an ASCII case-insensitive match.
+ ///
+ /// Same as `to_ascii_lowercase(a) == to_ascii_lowercase(b)`,
+ /// but without allocating and copying temporaries.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsString;
+ ///
+ /// assert!(OsString::from("Ferris").eq_ignore_ascii_case("FERRIS"));
+ /// assert!(OsString::from("Ferrös").eq_ignore_ascii_case("FERRöS"));
+ /// assert!(!OsString::from("Ferrös").eq_ignore_ascii_case("FERRÖS"));
+ /// ```
+ #[stable(feature = "osstring_ascii", since = "1.53.0")]
+ pub fn eq_ignore_ascii_case>(&self, other: S) -> bool {
+ self.inner.eq_ignore_ascii_case(&other.as_ref().inner)
+ }
+
+ /// Returns an object that implements [`Display`] for safely printing an
+ /// [`OsStr`] that may contain non-Unicode data. This may perform lossy
+ /// conversion, depending on the platform. If you would like an
+ /// implementation which escapes the [`OsStr`] please use [`Debug`]
+ /// instead.
+ ///
+ /// [`Display`]: fmt::Display
+ /// [`Debug`]: fmt::Debug
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(os_str_display)]
+ /// use std::ffi::OsStr;
+ ///
+ /// let s = OsStr::new("Hello, world!");
+ /// println!("{}", s.display());
+ /// ```
+ #[unstable(feature = "os_str_display", issue = "120048")]
+ #[must_use = "this does not display the `OsStr`; \
+ it returns an object that can be displayed"]
+ #[inline]
+ pub fn display(&self) -> Display<'_> {
+ Display { os_str: self }
+ }
+
+ /// Get inner slice
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ #[doc(hidden)]
+ pub fn as_inner(&self) -> &Slice {
+ &self.inner
+ }
+}
+
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for OsStr {
+ #[inline]
+ #[cfg_attr(debug_assertions, track_caller)]
+ unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+ // SAFETY: we're just a wrapper around a platform-specific Slice
+ unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) }
+ }
+}
+
+#[stable(feature = "str_tryfrom_osstr_impl", since = "1.72.0")]
+impl<'a> TryFrom<&'a OsStr> for &'a str {
+ type Error = crate::str::Utf8Error;
+
+ /// Tries to convert an `&OsStr` to a `&str`.
+ ///
+ /// ```
+ /// use std::ffi::OsStr;
+ ///
+ /// let os_str = OsStr::new("foo");
+ /// let as_str = <&str>::try_from(os_str).unwrap();
+ /// assert_eq!(as_str, "foo");
+ /// ```
+ fn try_from(value: &'a OsStr) -> Result {
+ value.inner.to_str()
+ }
+}
+
+#[stable(feature = "osstring_default", since = "1.9.0")]
+impl Default for &OsStr {
+ /// Creates an empty `OsStr`.
+ #[inline]
+ fn default() -> Self {
+ OsStr::new("")
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl PartialEq for OsStr {
+ #[inline]
+ fn eq(&self, other: &OsStr) -> bool {
+ self.as_encoded_bytes().eq(other.as_encoded_bytes())
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl PartialEq for OsStr {
+ #[inline]
+ fn eq(&self, other: &str) -> bool {
+ *self == *OsStr::new(other)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl PartialEq for str {
+ #[inline]
+ fn eq(&self, other: &OsStr) -> bool {
+ *other == *OsStr::new(self)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Eq for OsStr {}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl PartialOrd for OsStr {
+ #[inline]
+ fn partial_cmp(&self, other: &OsStr) -> Option {
+ self.as_encoded_bytes().partial_cmp(other.as_encoded_bytes())
+ }
+ #[inline]
+ fn lt(&self, other: &OsStr) -> bool {
+ self.as_encoded_bytes().lt(other.as_encoded_bytes())
+ }
+ #[inline]
+ fn le(&self, other: &OsStr) -> bool {
+ self.as_encoded_bytes().le(other.as_encoded_bytes())
+ }
+ #[inline]
+ fn gt(&self, other: &OsStr) -> bool {
+ self.as_encoded_bytes().gt(other.as_encoded_bytes())
+ }
+ #[inline]
+ fn ge(&self, other: &OsStr) -> bool {
+ self.as_encoded_bytes().ge(other.as_encoded_bytes())
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl PartialOrd for OsStr {
+ #[inline]
+ fn partial_cmp(&self, other: &str) -> Option {
+ self.partial_cmp(OsStr::new(other))
+ }
+}
+
+// FIXME (#19470): cannot provide PartialOrd for str until we
+// have more flexible coherence rules.
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Ord for OsStr {
+ #[inline]
+ fn cmp(&self, other: &OsStr) -> cmp::Ordering {
+ self.as_encoded_bytes().cmp(other.as_encoded_bytes())
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Hash for OsStr {
+ #[inline]
+ fn hash(&self, state: &mut H) {
+ self.as_encoded_bytes().hash(state)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl fmt::Debug for OsStr {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(&self.inner, formatter)
+ }
+}
+
+/// Helper struct for safely printing an [`OsStr`] with [`format!`] and `{}`.
+///
+/// An [`OsStr`] might contain non-Unicode data. This `struct` implements the
+/// [`Display`] trait in a way that mitigates that. It is created by the
+/// [`display`](OsStr::display) method on [`OsStr`]. This may perform lossy
+/// conversion, depending on the platform. If you would like an implementation
+/// which escapes the [`OsStr`] please use [`Debug`] instead.
+///
+/// # Examples
+///
+/// ```
+/// #![feature(os_str_display)]
+/// use std::ffi::OsStr;
+///
+/// let s = OsStr::new("Hello, world!");
+/// println!("{}", s.display());
+/// ```
+///
+/// [`Display`]: fmt::Display
+/// [`format!`]: crate::format
+#[unstable(feature = "os_str_display", issue = "120048")]
+pub struct Display<'a> {
+ os_str: &'a OsStr,
+}
+
+#[unstable(feature = "os_str_display", issue = "120048")]
+impl fmt::Debug for Display<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(&self.os_str, f)
+ }
+}
+
+#[unstable(feature = "os_str_display", issue = "120048")]
+impl fmt::Display for Display<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.os_str.inner, f)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl AsRef for OsStr {
+ #[inline]
+ fn as_ref(&self) -> &OsStr {
+ self
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl AsRef for str {
+ #[inline]
+ fn as_ref(&self) -> &OsStr {
+ OsStr::from_inner(Slice::from_str(self))
+ }
+}
+
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+#[doc(hidden)]
+impl AsRef for OsStr {
+ #[inline]
+ fn as_ref(&self) -> &Slice {
+ &self.inner
+ }
+}
diff --git a/library/core/src/ffi/os_str/bytes.rs b/library/core/src/ffi/os_str/bytes.rs
new file mode 100644
index 0000000000000..d51045878cd23
--- /dev/null
+++ b/library/core/src/ffi/os_str/bytes.rs
@@ -0,0 +1,202 @@
+#![allow(missing_docs)]
+#![allow(missing_debug_implementations)]
+
+//! The underlying OsString/OsStr implementation on Unix and many other
+//! systems: just a `Vec`/`[u8]`.
+
+use crate::clone::CloneToUninit;
+use crate::fmt::Write;
+use crate::ptr::addr_of_mut;
+use crate::{fmt, mem, str};
+
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+#[repr(transparent)]
+#[rustc_has_incoherent_inherent_impls]
+pub struct Slice {
+ pub inner: [u8],
+}
+
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+impl fmt::Debug for Slice {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(&self.inner.utf8_chunks().debug(), f)
+ }
+}
+
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+impl fmt::Display for Slice {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // If we're the empty string then our iterator won't actually yield
+ // anything, so perform the formatting manually
+ if self.inner.is_empty() {
+ return "".fmt(f);
+ }
+
+ for chunk in self.inner.utf8_chunks() {
+ let valid = chunk.valid();
+ // If we successfully decoded the whole chunk as a valid string then
+ // we can return a direct formatting of the string which will also
+ // respect various formatting flags if possible.
+ if chunk.invalid().is_empty() {
+ return valid.fmt(f);
+ }
+
+ f.write_str(valid)?;
+ f.write_char(char::REPLACEMENT_CHARACTER)?;
+ }
+ Ok(())
+ }
+}
+
+impl Slice {
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn as_encoded_bytes(&self) -> &[u8] {
+ &self.inner
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub unsafe fn from_encoded_bytes_unchecked(s: &[u8]) -> &Slice {
+ // SAFETY: Slice is just a wrapper of [u8]
+ unsafe { mem::transmute(s) }
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[track_caller]
+ #[inline]
+ pub fn check_public_boundary(&self, index: usize) {
+ if index == 0 || index == self.inner.len() {
+ return;
+ }
+ if index < self.inner.len()
+ && (self.inner[index - 1].is_ascii() || self.inner[index].is_ascii())
+ {
+ return;
+ }
+
+ slow_path(&self.inner, index);
+
+ /// We're betting that typical splits will involve an ASCII character.
+ ///
+ /// Putting the expensive checks in a separate function generates notably
+ /// better assembly.
+ #[track_caller]
+ #[inline(never)]
+ fn slow_path(bytes: &[u8], index: usize) {
+ let (before, after) = bytes.split_at(index);
+
+ // UTF-8 takes at most 4 bytes per codepoint, so we don't
+ // need to check more than that.
+ let after = after.get(..4).unwrap_or(after);
+ match str::from_utf8(after) {
+ Ok(_) => return,
+ Err(err) if err.valid_up_to() != 0 => return,
+ Err(_) => (),
+ }
+
+ for len in 2..=4.min(index) {
+ let before = &before[index - len..];
+ if str::from_utf8(before).is_ok() {
+ return;
+ }
+ }
+
+ panic!("byte index {index} is not an OsStr boundary");
+ }
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn from_str(s: &str) -> &Slice {
+ // SAFETY: Slice is just a wrapper of [u8]
+ unsafe { Slice::from_encoded_bytes_unchecked(s.as_bytes()) }
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ pub fn to_str(&self) -> Result<&str, crate::str::Utf8Error> {
+ str::from_utf8(&self.inner)
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn make_ascii_lowercase(&mut self) {
+ self.inner.make_ascii_lowercase()
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn make_ascii_uppercase(&mut self) {
+ self.inner.make_ascii_uppercase()
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn is_ascii(&self) -> bool {
+ self.inner.is_ascii()
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
+ self.inner.eq_ignore_ascii_case(&other.inner)
+ }
+}
+
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for Slice {
+ #[inline]
+ #[cfg_attr(debug_assertions, track_caller)]
+ unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+ // SAFETY: we're just a wrapper around [u8]
+ unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) }
+ }
+}
diff --git a/library/core/src/ffi/os_str/os_str_ext_unix.rs b/library/core/src/ffi/os_str/os_str_ext_unix.rs
new file mode 100644
index 0000000000000..f2040c62ff77c
--- /dev/null
+++ b/library/core/src/ffi/os_str/os_str_ext_unix.rs
@@ -0,0 +1,36 @@
+//! [`OsStrExt`] for unix.
+
+use super::{private, OsStr};
+use crate::mem;
+
+/// Platform-specific extensions to [`OsStr`].
+///
+/// This trait is sealed: it cannot be implemented outside the standard library.
+/// This is so that future additional methods are not breaking changes.
+#[stable(feature = "rust1", since = "1.0.0")]
+pub trait OsStrExt: private::Sealed {
+ #[stable(feature = "rust1", since = "1.0.0")]
+ /// Creates an [`OsStr`] from a byte slice.
+ ///
+ /// See the module documentation for an example.
+ fn from_bytes(slice: &[u8]) -> &Self;
+
+ /// Gets the underlying byte view of the [`OsStr`] slice.
+ ///
+ /// See the module documentation for an example.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ fn as_bytes(&self) -> &[u8];
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl OsStrExt for OsStr {
+ #[inline]
+ fn from_bytes(slice: &[u8]) -> &OsStr {
+ // SAFETY: OsStr is just a wrapper of [u8]
+ unsafe { mem::transmute(slice) }
+ }
+ #[inline]
+ fn as_bytes(&self) -> &[u8] {
+ &self.inner.inner
+ }
+}
diff --git a/library/core/src/ffi/os_str/os_str_ext_windows.rs b/library/core/src/ffi/os_str/os_str_ext_windows.rs
new file mode 100644
index 0000000000000..6205b74872e55
--- /dev/null
+++ b/library/core/src/ffi/os_str/os_str_ext_windows.rs
@@ -0,0 +1,44 @@
+//! [`OsStrExt`] for windows
+
+use super::{private, OsStr};
+#[stable(feature = "rust1", since = "1.0.0")]
+pub use crate::ffi::wtf8::EncodeWide;
+
+/// Windows-specific extensions to [`OsStr`].
+///
+/// This trait is sealed: it cannot be implemented outside the standard library.
+/// This is so that future additional methods are not breaking changes.
+#[stable(feature = "rust1", since = "1.0.0")]
+pub trait OsStrExt: private::Sealed {
+ /// Re-encodes an `OsStr` as a wide character sequence, i.e., potentially
+ /// ill-formed UTF-16.
+ ///
+ /// This is lossless: calling [`OsStringExt::from_wide`] and then
+ /// `encode_wide` on the result will yield the original code units.
+ /// Note that the encoding does not add a final null terminator.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::ffi::OsString;
+ /// use std::os::windows::prelude::*;
+ ///
+ /// // UTF-16 encoding for "Unicode".
+ /// let source = [0x0055, 0x006E, 0x0069, 0x0063, 0x006F, 0x0064, 0x0065];
+ ///
+ /// let string = OsString::from_wide(&source[..]);
+ ///
+ /// let result: Vec = string.encode_wide().collect();
+ /// assert_eq!(&source[..], &result[..]);
+ /// ```
+ #[stable(feature = "rust1", since = "1.0.0")]
+ fn encode_wide(&self) -> EncodeWide<'_>;
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl OsStrExt for OsStr {
+ #[inline]
+ fn encode_wide(&self) -> EncodeWide<'_> {
+ self.inner.inner.encode_wide()
+ }
+}
diff --git a/library/core/src/ffi/os_str/wtf8.rs b/library/core/src/ffi/os_str/wtf8.rs
new file mode 100644
index 0000000000000..4e3ff8d2400bd
--- /dev/null
+++ b/library/core/src/ffi/os_str/wtf8.rs
@@ -0,0 +1,145 @@
+#![allow(missing_docs)]
+#![allow(missing_debug_implementations)]
+
+//! The underlying OsString/OsStr implementation on Windows is a
+//! wrapper around the "WTF-8" encoding; see the `wtf8` module for more.
+use crate::clone::CloneToUninit;
+use crate::ffi::wtf8::{check_utf8_boundary, Wtf8};
+use crate::ptr::addr_of_mut;
+use crate::{fmt, mem};
+
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+#[repr(transparent)]
+#[rustc_has_incoherent_inherent_impls]
+pub struct Slice {
+ pub inner: Wtf8,
+}
+
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+impl fmt::Debug for Slice {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(&self.inner, formatter)
+ }
+}
+
+#[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+)]
+impl fmt::Display for Slice {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.inner, formatter)
+ }
+}
+
+impl Slice {
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn as_encoded_bytes(&self) -> &[u8] {
+ self.inner.as_bytes()
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub unsafe fn from_encoded_bytes_unchecked(s: &[u8]) -> &Slice {
+ // SAFETY:: Slice is just a wrapper of Wtf8
+ unsafe { mem::transmute(Wtf8::from_bytes_unchecked(s)) }
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[track_caller]
+ pub fn check_public_boundary(&self, index: usize) {
+ check_utf8_boundary(&self.inner, index);
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn from_str(s: &str) -> &Slice {
+ // SAFETY: Slice is just a wrapper of wtf8
+ unsafe { mem::transmute(Wtf8::from_str(s)) }
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ pub fn to_str(&self) -> Result<&str, crate::str::Utf8Error> {
+ self.inner.as_str()
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn make_ascii_lowercase(&mut self) {
+ self.inner.make_ascii_lowercase()
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn make_ascii_uppercase(&mut self) {
+ self.inner.make_ascii_uppercase()
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn is_ascii(&self) -> bool {
+ self.inner.is_ascii()
+ }
+
+ #[unstable(
+ feature = "os_str_internals",
+ reason = "internal details of the implementation of os str",
+ issue = "none"
+ )]
+ #[inline]
+ pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
+ self.inner.eq_ignore_ascii_case(&other.inner)
+ }
+}
+
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for Slice {
+ #[inline]
+ #[cfg_attr(debug_assertions, track_caller)]
+ unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+ // SAFETY: we're just a wrapper around Wtf8
+ unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) }
+ }
+}
diff --git a/library/core/src/ffi/wtf8.rs b/library/core/src/ffi/wtf8.rs
new file mode 100644
index 0000000000000..00b7abfbac1a8
--- /dev/null
+++ b/library/core/src/ffi/wtf8.rs
@@ -0,0 +1,595 @@
+#![allow(missing_docs)]
+#![allow(missing_debug_implementations)]
+
+//! Implementation of [the WTF-8 encoding](https://simonsapin.github.io/wtf-8/).
+//!
+//! This library uses Rust’s type system to maintain
+//! [well-formedness](https://simonsapin.github.io/wtf-8/#well-formed),
+//! like the `String` and `&str` types do for UTF-8.
+//!
+//! Since [WTF-8 must not be used
+//! for interchange](https://simonsapin.github.io/wtf-8/#intended-audience),
+//! this library deliberately does not provide access to the underlying bytes
+//! of WTF-8 strings,
+//! nor can it decode WTF-8 from arbitrary bytes.
+//! WTF-8 strings can be obtained from UTF-8, UTF-16, or code points.
+
+// this module is imported from @SimonSapin's repo and has tons of dead code on
+// unix (it's mostly used on windows), so don't worry about dead code here.
+#![allow(dead_code)]
+
+use crate::char::encode_utf16_raw;
+use crate::clone::CloneToUninit;
+use crate::hash::{Hash, Hasher};
+use crate::iter::FusedIterator;
+use crate::ptr::addr_of_mut;
+use crate::str::next_code_point;
+use crate::{fmt, ops, slice, str};
+
+pub const UTF8_REPLACEMENT_CHARACTER: &str = "\u{FFFD}";
+
+/// A Unicode code point: from U+0000 to U+10FFFF.
+///
+/// Compares with the `char` type,
+/// which represents a Unicode scalar value:
+/// a code point that is not a surrogate (U+D800 to U+DFFF).
+#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
+pub struct CodePoint {
+ value: u32,
+}
+
+/// Format the code point as `U+` followed by four to six hexadecimal digits.
+/// Example: `U+1F4A9`
+impl fmt::Debug for CodePoint {
+ #[inline]
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(formatter, "U+{:04X}", self.value)
+ }
+}
+
+impl CodePoint {
+ /// Unsafely creates a new `CodePoint` without checking the value.
+ ///
+ /// Only use when `value` is known to be less than or equal to 0x10FFFF.
+ #[inline]
+ pub unsafe fn from_u32_unchecked(value: u32) -> CodePoint {
+ CodePoint { value }
+ }
+
+ /// Creates a new `CodePoint` if the value is a valid code point.
+ ///
+ /// Returns `None` if `value` is above 0x10FFFF.
+ #[inline]
+ pub fn from_u32(value: u32) -> Option {
+ match value {
+ 0..=0x10FFFF => Some(CodePoint { value }),
+ _ => None,
+ }
+ }
+
+ /// Creates a new `CodePoint` from a `char`.
+ ///
+ /// Since all Unicode scalar values are code points, this always succeeds.
+ #[inline]
+ pub fn from_char(value: char) -> CodePoint {
+ CodePoint { value: value as u32 }
+ }
+
+ /// Returns the numeric value of the code point.
+ #[inline]
+ pub fn to_u32(&self) -> u32 {
+ self.value
+ }
+
+ /// Returns the numeric value of the code point if it is a leading surrogate.
+ #[inline]
+ pub fn to_lead_surrogate(&self) -> Option {
+ match self.value {
+ lead @ 0xD800..=0xDBFF => Some(lead as u16),
+ _ => None,
+ }
+ }
+
+ /// Returns the numeric value of the code point if it is a trailing surrogate.
+ #[inline]
+ pub fn to_trail_surrogate(&self) -> Option {
+ match self.value {
+ trail @ 0xDC00..=0xDFFF => Some(trail as u16),
+ _ => None,
+ }
+ }
+
+ /// Optionally returns a Unicode scalar value for the code point.
+ ///
+ /// Returns `None` if the code point is a surrogate (from U+D800 to U+DFFF).
+ #[inline]
+ pub fn to_char(&self) -> Option {
+ match self.value {
+ 0xD800..=0xDFFF => None,
+ // SAFETY: self.value is valid char
+ _ => Some(unsafe { char::from_u32_unchecked(self.value) }),
+ }
+ }
+
+ /// Returns a Unicode scalar value for the code point.
+ ///
+ /// Returns `'\u{FFFD}'` (the replacement character “�”)
+ /// if the code point is a surrogate (from U+D800 to U+DFFF).
+ #[inline]
+ pub fn to_char_lossy(&self) -> char {
+ self.to_char().unwrap_or('\u{FFFD}')
+ }
+}
+
+/// A borrowed slice of well-formed WTF-8 data.
+///
+/// Similar to `&str`, but can additionally contain surrogate code points
+/// if they’re not in a surrogate pair.
+#[derive(Eq, Ord, PartialEq, PartialOrd)]
+#[repr(transparent)]
+#[rustc_has_incoherent_inherent_impls]
+pub struct Wtf8 {
+ bytes: [u8],
+}
+
+impl AsRef<[u8]> for Wtf8 {
+ #[inline]
+ fn as_ref(&self) -> &[u8] {
+ &self.bytes
+ }
+}
+
+/// Format the slice with double quotes,
+/// and surrogates as `\u` followed by four hexadecimal digits.
+/// Example: `"a\u{D800}"` for a slice with code points [U+0061, U+D800]
+impl fmt::Debug for Wtf8 {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fn write_str_escaped(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
+ use crate::fmt::Write;
+ for c in s.chars().flat_map(|c| c.escape_debug()) {
+ f.write_char(c)?
+ }
+ Ok(())
+ }
+
+ formatter.write_str("\"")?;
+ let mut pos = 0;
+ while let Some((surrogate_pos, surrogate)) = self.next_surrogate(pos) {
+ // SAFETY: self.bytes[pos..surrogate_pos] is valid utf-8
+ write_str_escaped(formatter, unsafe {
+ str::from_utf8_unchecked(&self.bytes[pos..surrogate_pos])
+ })?;
+ write!(formatter, "\\u{{{:x}}}", surrogate)?;
+ pos = surrogate_pos + 3;
+ }
+ // SAFETY: self.bytes[pos..] is valid utf-8
+ write_str_escaped(formatter, unsafe { str::from_utf8_unchecked(&self.bytes[pos..]) })?;
+ formatter.write_str("\"")
+ }
+}
+
+impl fmt::Display for Wtf8 {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let wtf8_bytes = &self.bytes;
+ let mut pos = 0;
+ loop {
+ match self.next_surrogate(pos) {
+ Some((surrogate_pos, _)) => {
+ // SAFETY: self.bytes[pos..surrogate_pos] is valid utf-8
+ formatter.write_str(unsafe {
+ str::from_utf8_unchecked(&wtf8_bytes[pos..surrogate_pos])
+ })?;
+ formatter.write_str(UTF8_REPLACEMENT_CHARACTER)?;
+ pos = surrogate_pos + 3;
+ }
+ None => {
+ // SAFETY: self.bytes[pos..] is valid utf-8
+ let s = unsafe { str::from_utf8_unchecked(&wtf8_bytes[pos..]) };
+ if pos == 0 {
+ return s.fmt(formatter);
+ } else {
+ return formatter.write_str(s);
+ }
+ }
+ }
+ }
+ }
+}
+
+impl Wtf8 {
+ /// Creates a WTF-8 slice from a UTF-8 `&str` slice.
+ ///
+ /// Since WTF-8 is a superset of UTF-8, this always succeeds.
+ #[inline]
+ pub fn from_str(value: &str) -> &Wtf8 {
+ // SAFETY: value is valid utf-8
+ unsafe { Wtf8::from_bytes_unchecked(value.as_bytes()) }
+ }
+
+ /// Creates a WTF-8 slice from a WTF-8 byte slice.
+ ///
+ /// Since the byte slice is not checked for valid WTF-8, this functions is
+ /// marked unsafe.
+ #[inline]
+ pub unsafe fn from_bytes_unchecked(value: &[u8]) -> &Wtf8 {
+ // SAFETY: start with &[u8], end with fancy &[u8]
+ unsafe { &*(value as *const [u8] as *const Wtf8) }
+ }
+
+ /// Creates a mutable WTF-8 slice from a mutable WTF-8 byte slice.
+ ///
+ /// Since the byte slice is not checked for valid WTF-8, this functions is
+ /// marked unsafe.
+ #[inline]
+ pub unsafe fn from_mut_bytes_unchecked(value: &mut [u8]) -> &mut Wtf8 {
+ // SAFETY: start with &mut [u8], end with fancy &mut [u8]
+ unsafe { &mut *(value as *mut [u8] as *mut Wtf8) }
+ }
+
+ /// Returns the length, in WTF-8 bytes.
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.bytes.len()
+ }
+
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.bytes.is_empty()
+ }
+
+ /// Returns the code point at `position` if it is in the ASCII range,
+ /// or `b'\xFF'` otherwise.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `position` is beyond the end of the string.
+ #[inline]
+ pub fn ascii_byte_at(&self, position: usize) -> u8 {
+ match self.bytes[position] {
+ ascii_byte @ 0x00..=0x7F => ascii_byte,
+ _ => 0xFF,
+ }
+ }
+
+ /// Returns an iterator for the string’s code points.
+ #[inline]
+ pub fn code_points(&self) -> Wtf8CodePoints<'_> {
+ Wtf8CodePoints { bytes: self.bytes.iter() }
+ }
+
+ /// Access raw bytes of WTF-8 data
+ #[inline]
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.bytes
+ }
+
+ /// Tries to convert the string to UTF-8 and return a `&str` slice.
+ ///
+ /// Returns `None` if the string contains surrogates.
+ ///
+ /// This does not copy the data.
+ #[inline]
+ pub fn as_str(&self) -> Result<&str, str::Utf8Error> {
+ str::from_utf8(&self.bytes)
+ }
+
+ /// Converts the WTF-8 string to potentially ill-formed UTF-16
+ /// and return an iterator of 16-bit code units.
+ ///
+ /// This is lossless:
+ /// calling `Wtf8Buf::from_ill_formed_utf16` on the resulting code units
+ /// would always return the original WTF-8 string.
+ #[inline]
+ pub fn encode_wide(&self) -> EncodeWide<'_> {
+ EncodeWide { code_points: self.code_points(), extra: 0 }
+ }
+
+ /// Next suroogate
+ #[inline]
+ pub fn next_surrogate(&self, mut pos: usize) -> Option<(usize, u16)> {
+ let mut iter = self.bytes[pos..].iter();
+ loop {
+ let b = *iter.next()?;
+ if b < 0x80 {
+ pos += 1;
+ } else if b < 0xE0 {
+ iter.next();
+ pos += 2;
+ } else if b == 0xED {
+ match (iter.next(), iter.next()) {
+ (Some(&b2), Some(&b3)) if b2 >= 0xA0 => {
+ return Some((pos, decode_surrogate(b2, b3)));
+ }
+ _ => pos += 3,
+ }
+ } else if b < 0xF0 {
+ iter.next();
+ iter.next();
+ pos += 3;
+ } else {
+ iter.next();
+ iter.next();
+ iter.next();
+ pos += 4;
+ }
+ }
+ }
+
+ /// Get final suroogate
+ #[inline]
+ pub fn final_lead_surrogate(&self) -> Option {
+ match self.bytes {
+ [.., 0xED, b2 @ 0xA0..=0xAF, b3] => Some(decode_surrogate(b2, b3)),
+ _ => None,
+ }
+ }
+
+ /// Get initial trai suroogate
+ #[inline]
+ pub fn initial_trail_surrogate(&self) -> Option {
+ match self.bytes {
+ [0xED, b2 @ 0xB0..=0xBF, b3, ..] => Some(decode_surrogate(b2, b3)),
+ _ => None,
+ }
+ }
+
+ #[inline]
+ pub fn make_ascii_lowercase(&mut self) {
+ self.bytes.make_ascii_lowercase()
+ }
+
+ #[inline]
+ pub fn make_ascii_uppercase(&mut self) {
+ self.bytes.make_ascii_uppercase()
+ }
+
+ #[inline]
+ pub fn is_ascii(&self) -> bool {
+ self.bytes.is_ascii()
+ }
+
+ #[inline]
+ pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
+ self.bytes.eq_ignore_ascii_case(&other.bytes)
+ }
+}
+
+/// Returns a slice of the given string for the byte range \[`begin`..`end`).
+///
+/// # Panics
+///
+/// Panics when `begin` and `end` do not point to code point boundaries,
+/// or point beyond the end of the string.
+impl ops::Index> for Wtf8 {
+ type Output = Wtf8;
+
+ #[inline]
+ fn index(&self, range: ops::Range) -> &Wtf8 {
+ // is_code_point_boundary checks that the index is in [0, .len()]
+ if range.start <= range.end
+ && is_code_point_boundary(self, range.start)
+ && is_code_point_boundary(self, range.end)
+ {
+ // SAFETY: start and end is at boundary
+ unsafe { slice_unchecked(self, range.start, range.end) }
+ } else {
+ slice_error_fail(self, range.start, range.end)
+ }
+ }
+}
+
+/// Returns a slice of the given string from byte `begin` to its end.
+///
+/// # Panics
+///
+/// Panics when `begin` is not at a code point boundary,
+/// or is beyond the end of the string.
+impl ops::Index> for Wtf8 {
+ type Output = Wtf8;
+
+ #[inline]
+ fn index(&self, range: ops::RangeFrom) -> &Wtf8 {
+ // is_code_point_boundary checks that the index is in [0, .len()]
+ if is_code_point_boundary(self, range.start) {
+ // SAFETY: start is at boundary
+ unsafe { slice_unchecked(self, range.start, self.len()) }
+ } else {
+ slice_error_fail(self, range.start, self.len())
+ }
+ }
+}
+
+/// Returns a slice of the given string from its beginning to byte `end`.
+///
+/// # Panics
+///
+/// Panics when `end` is not at a code point boundary,
+/// or is beyond the end of the string.
+impl ops::Index> for Wtf8 {
+ type Output = Wtf8;
+
+ #[inline]
+ fn index(&self, range: ops::RangeTo) -> &Wtf8 {
+ // is_code_point_boundary checks that the index is in [0, .len()]
+ if is_code_point_boundary(self, range.end) {
+ // SAFETY: end is at boundary
+ unsafe { slice_unchecked(self, 0, range.end) }
+ } else {
+ slice_error_fail(self, 0, range.end)
+ }
+ }
+}
+
+impl ops::Index for Wtf8 {
+ type Output = Wtf8;
+
+ #[inline]
+ fn index(&self, _range: ops::RangeFull) -> &Wtf8 {
+ self
+ }
+}
+
+#[inline]
+pub fn decode_surrogate(second_byte: u8, third_byte: u8) -> u16 {
+ // The first byte is assumed to be 0xED
+ 0xD800 | (second_byte as u16 & 0x3F) << 6 | third_byte as u16 & 0x3F
+}
+
+#[inline]
+pub fn decode_surrogate_pair(lead: u16, trail: u16) -> char {
+ let code_point = 0x10000 + ((((lead - 0xD800) as u32) << 10) | (trail - 0xDC00) as u32);
+ // SAFETY: code_point is valid char
+ unsafe { char::from_u32_unchecked(code_point) }
+}
+
+/// Copied from str::is_char_boundary
+#[inline]
+pub fn is_code_point_boundary(slice: &Wtf8, index: usize) -> bool {
+ if index == 0 {
+ return true;
+ }
+ match slice.bytes.get(index) {
+ None => index == slice.len(),
+ Some(&b) => (b as i8) >= -0x40,
+ }
+}
+
+/// Verify that `index` is at the edge of either a valid UTF-8 codepoint
+/// (i.e. a codepoint that's not a surrogate) or of the whole string.
+///
+/// These are the cases currently permitted by `OsStr::slice_encoded_bytes`.
+/// Splitting between surrogates is valid as far as WTF-8 is concerned, but
+/// we do not permit it in the public API because WTF-8 is considered an
+/// implementation detail.
+#[track_caller]
+#[inline]
+pub fn check_utf8_boundary(slice: &Wtf8, index: usize) {
+ if index == 0 {
+ return;
+ }
+ match slice.bytes.get(index) {
+ Some(0xED) => (), // Might be a surrogate
+ Some(&b) if (b as i8) >= -0x40 => return,
+ Some(_) => panic!("byte index {index} is not a codepoint boundary"),
+ None if index == slice.len() => return,
+ None => panic!("byte index {index} is out of bounds"),
+ }
+ if slice.bytes[index + 1] >= 0xA0 {
+ // There's a surrogate after index. Now check before index.
+ if index >= 3 && slice.bytes[index - 3] == 0xED && slice.bytes[index - 2] >= 0xA0 {
+ panic!("byte index {index} lies between surrogate codepoints");
+ }
+ }
+}
+
+/// Copied from core::str::raw::slice_unchecked
+#[inline]
+pub unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 {
+ // SAFETY: memory layout of a &[u8] and &Wtf8 are the same
+ unsafe {
+ let len = end - begin;
+ let start = s.as_bytes().as_ptr().add(begin);
+ Wtf8::from_bytes_unchecked(slice::from_raw_parts(start, len))
+ }
+}
+
+/// Copied from core::str::raw::slice_error_fail
+#[inline(never)]
+pub fn slice_error_fail(s: &Wtf8, begin: usize, end: usize) -> ! {
+ assert!(begin <= end);
+ panic!("index {begin} and/or {end} in `{s:?}` do not lie on character boundary");
+}
+
+/// Iterator for the code points of a WTF-8 string.
+///
+/// Created with the method `.code_points()`.
+#[derive(Clone)]
+pub struct Wtf8CodePoints<'a> {
+ bytes: slice::Iter<'a, u8>,
+}
+
+impl<'a> Iterator for Wtf8CodePoints<'a> {
+ type Item = CodePoint;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ // SAFETY: `self.bytes` has been created from a WTF-8 string
+ unsafe { next_code_point(&mut self.bytes).map(|c| CodePoint { value: c }) }
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option) {
+ let len = self.bytes.len();
+ (len.saturating_add(3) / 4, Some(len))
+ }
+}
+
+/// Generates a wide character sequence for potentially ill-formed UTF-16.
+#[stable(feature = "rust1", since = "1.0.0")]
+#[derive(Clone)]
+pub struct EncodeWide<'a> {
+ code_points: Wtf8CodePoints<'a>,
+ extra: u16,
+}
+
+// Copied from libunicode/u_str.rs
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<'a> Iterator for EncodeWide<'a> {
+ type Item = u16;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ if self.extra != 0 {
+ let tmp = self.extra;
+ self.extra = 0;
+ return Some(tmp);
+ }
+
+ let mut buf = [0; 2];
+ self.code_points.next().map(|code_point| {
+ let n = encode_utf16_raw(code_point.value, &mut buf).len();
+ if n == 2 {
+ self.extra = buf[1];
+ }
+ buf[0]
+ })
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option) {
+ let (low, high) = self.code_points.size_hint();
+ let ext = (self.extra != 0) as usize;
+ // every code point gets either one u16 or two u16,
+ // so this iterator is between 1 or 2 times as
+ // long as the underlying iterator.
+ (low + ext, high.and_then(|n| n.checked_mul(2)).and_then(|n| n.checked_add(ext)))
+ }
+}
+
+#[stable(feature = "encode_wide_fused_iterator", since = "1.62.0")]
+impl FusedIterator for EncodeWide<'_> {}
+
+impl Hash for CodePoint {
+ #[inline]
+ fn hash(&self, state: &mut H) {
+ self.value.hash(state)
+ }
+}
+
+impl Hash for Wtf8 {
+ #[inline]
+ fn hash(&self, state: &mut H) {
+ state.write(&self.bytes);
+ 0xfeu8.hash(state)
+ }
+}
+
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for Wtf8 {
+ #[inline]
+ #[cfg_attr(debug_assertions, track_caller)]
+ unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+ // SAFETY: we're just a wrapper around [u8]
+ unsafe { self.bytes.clone_to_uninit(addr_of_mut!((*dst).bytes)) }
+ }
+}
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index e60bcf3aa5db7..299fda449635a 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -407,6 +407,13 @@ pub mod slice;
pub mod str;
pub mod time;
+#[unstable(
+ feature = "path_internals",
+ reason = "internal details of the implementation of path",
+ issue = "none"
+)]
+#[doc(hidden)]
+pub mod path;
pub mod unicode;
/* Async */
diff --git a/library/core/src/path.rs b/library/core/src/path.rs
new file mode 100644
index 0000000000000..8b3d2a28457cc
--- /dev/null
+++ b/library/core/src/path.rs
@@ -0,0 +1,2033 @@
+#![deny(unsafe_op_in_unsafe_fn)]
+
+use crate::clone::CloneToUninit;
+use crate::error::Error;
+use crate::ffi::os_str::{self, OsStr};
+use crate::hash::{Hash, Hasher};
+use crate::iter::FusedIterator;
+use crate::{cmp, fmt};
+
+#[cfg(target_os = "windows")]
+mod windows;
+#[unstable(
+ feature = "path_internals",
+ reason = "internal details of the implementation of path",
+ issue = "none"
+)]
+#[doc(hidden)]
+#[cfg(target_os = "windows")]
+pub use windows::*;
+
+#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
+mod sgx;
+#[unstable(
+ feature = "path_internals",
+ reason = "internal details of the implementation of path",
+ issue = "none"
+)]
+#[doc(hidden)]
+#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
+pub use sgx::*;
+
+#[cfg(any(target_os = "uefi", target_os = "solid_asp3",))]
+mod unsupported_backslash;
+#[unstable(
+ feature = "path_internals",
+ reason = "internal details of the implementation of path",
+ issue = "none"
+)]
+#[doc(hidden)]
+#[cfg(any(target_os = "uefi", target_os = "solid_asp3",))]
+pub use unsupported_backslash::*;
+
+#[cfg(unix)]
+mod unix;
+#[unstable(
+ feature = "path_internals",
+ reason = "internal details of the implementation of path",
+ issue = "none"
+)]
+#[doc(hidden)]
+#[cfg(unix)]
+pub use unix::*;
+
+////////////////////////////////////////////////////////////////////////////////
+// GENERAL NOTES
+////////////////////////////////////////////////////////////////////////////////
+//
+// Parsing in this module is done by directly transmuting OsStr to [u8] slices,
+// taking advantage of the fact that OsStr always encodes ASCII characters
+// as-is. Eventually, this transmutation should be replaced by direct uses of
+// OsStr APIs for parsing, but it will take a while for those to become
+// available.
+
+////////////////////////////////////////////////////////////////////////////////
+// Windows Prefixes
+////////////////////////////////////////////////////////////////////////////////
+
+/// Windows path prefixes, e.g., `C:` or `\\server\share`.
+///
+/// Windows uses a variety of path prefix styles, including references to drive
+/// volumes (like `C:`), network shared folders (like `\\server\share`), and
+/// others. In addition, some path prefixes are "verbatim" (i.e., prefixed with
+/// `\\?\`), in which case `/` is *not* treated as a separator and essentially
+/// no normalization is performed.
+///
+/// # Examples
+///
+/// ```
+/// use std::path::{Component, Path, Prefix};
+/// use std::path::Prefix::*;
+/// use std::ffi::OsStr;
+///
+/// fn get_path_prefix(s: &str) -> Prefix<'_> {
+/// let path = Path::new(s);
+/// match path.components().next().unwrap() {
+/// Component::Prefix(prefix_component) => prefix_component.kind(),
+/// _ => panic!(),
+/// }
+/// }
+///
+/// # if cfg!(windows) {
+/// assert_eq!(Verbatim(OsStr::new("pictures")),
+/// get_path_prefix(r"\\?\pictures\kittens"));
+/// assert_eq!(VerbatimUNC(OsStr::new("server"), OsStr::new("share")),
+/// get_path_prefix(r"\\?\UNC\server\share"));
+/// assert_eq!(VerbatimDisk(b'C'), get_path_prefix(r"\\?\c:\"));
+/// assert_eq!(DeviceNS(OsStr::new("BrainInterface")),
+/// get_path_prefix(r"\\.\BrainInterface"));
+/// assert_eq!(UNC(OsStr::new("server"), OsStr::new("share")),
+/// get_path_prefix(r"\\server\share"));
+/// assert_eq!(Disk(b'C'), get_path_prefix(r"C:\Users\Rust\Pictures\Ferris"));
+/// # }
+/// ```
+#[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
+#[stable(feature = "rust1", since = "1.0.0")]
+pub enum Prefix<'a> {
+ /// Verbatim prefix, e.g., `\\?\cat_pics`.
+ ///
+ /// Verbatim prefixes consist of `\\?\` immediately followed by the given
+ /// component.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ Verbatim(#[stable(feature = "rust1", since = "1.0.0")] &'a OsStr),
+
+ /// Verbatim prefix using Windows' _**U**niform **N**aming **C**onvention_,
+ /// e.g., `\\?\UNC\server\share`.
+ ///
+ /// Verbatim UNC prefixes consist of `\\?\UNC\` immediately followed by the
+ /// server's hostname and a share name.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ VerbatimUNC(
+ #[stable(feature = "rust1", since = "1.0.0")] &'a OsStr,
+ #[stable(feature = "rust1", since = "1.0.0")] &'a OsStr,
+ ),
+
+ /// Verbatim disk prefix, e.g., `\\?\C:`.
+ ///
+ /// Verbatim disk prefixes consist of `\\?\` immediately followed by the
+ /// drive letter and `:`.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ VerbatimDisk(#[stable(feature = "rust1", since = "1.0.0")] u8),
+
+ /// Device namespace prefix, e.g., `\\.\COM42`.
+ ///
+ /// Device namespace prefixes consist of `\\.\` (possibly using `/`
+ /// instead of `\`), immediately followed by the device name.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ DeviceNS(#[stable(feature = "rust1", since = "1.0.0")] &'a OsStr),
+
+ /// Prefix using Windows' _**U**niform **N**aming **C**onvention_, e.g.
+ /// `\\server\share`.
+ ///
+ /// UNC prefixes consist of the server's hostname and a share name.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ UNC(
+ #[stable(feature = "rust1", since = "1.0.0")] &'a OsStr,
+ #[stable(feature = "rust1", since = "1.0.0")] &'a OsStr,
+ ),
+
+ /// Prefix `C:` for the given disk drive.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ Disk(#[stable(feature = "rust1", since = "1.0.0")] u8),
+}
+
+impl<'a> Prefix<'a> {
+ /// Length
+ #[unstable(
+ feature = "path_internals",
+ reason = "internal details of the implementation of path",
+ issue = "none"
+ )]
+ #[doc(hidden)]
+ #[inline]
+ pub fn len(&self) -> usize {
+ use self::Prefix::*;
+ fn os_str_len(s: &OsStr) -> usize {
+ s.as_encoded_bytes().len()
+ }
+ match *self {
+ Verbatim(x) => 4 + os_str_len(x),
+ VerbatimUNC(x, y) => {
+ 8 + os_str_len(x) + if os_str_len(y) > 0 { 1 + os_str_len(y) } else { 0 }
+ }
+ VerbatimDisk(_) => 6,
+ UNC(x, y) => 2 + os_str_len(x) + if os_str_len(y) > 0 { 1 + os_str_len(y) } else { 0 },
+ DeviceNS(x) => 4 + os_str_len(x),
+ Disk(_) => 2,
+ }
+ }
+
+ /// Determines if the prefix is verbatim, i.e., begins with `\\?\`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::path::Prefix::*;
+ /// use std::ffi::OsStr;
+ ///
+ /// assert!(Verbatim(OsStr::new("pictures")).is_verbatim());
+ /// assert!(VerbatimUNC(OsStr::new("server"), OsStr::new("share")).is_verbatim());
+ /// assert!(VerbatimDisk(b'C').is_verbatim());
+ /// assert!(!DeviceNS(OsStr::new("BrainInterface")).is_verbatim());
+ /// assert!(!UNC(OsStr::new("server"), OsStr::new("share")).is_verbatim());
+ /// assert!(!Disk(b'C').is_verbatim());
+ /// ```
+ #[inline]
+ #[must_use]
+ #[stable(feature = "rust1", since = "1.0.0")]
+ pub fn is_verbatim(&self) -> bool {
+ use self::Prefix::*;
+ matches!(*self, Verbatim(_) | VerbatimDisk(_) | VerbatimUNC(..))
+ }
+
+ /// Is drive
+ #[unstable(
+ feature = "path_internals",
+ reason = "internal details of the implementation of path",
+ issue = "none"
+ )]
+ #[doc(hidden)]
+ #[inline]
+ pub fn is_drive(&self) -> bool {
+ matches!(*self, Prefix::Disk(_))
+ }
+
+ #[inline]
+ fn has_implicit_root(&self) -> bool {
+ !self.is_drive()
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Exposed parsing helpers
+////////////////////////////////////////////////////////////////////////////////
+
+/// Determines whether the character is one of the permitted path
+/// separators for the current platform.
+///
+/// # Examples
+///
+/// ```
+/// use std::path;
+///
+/// assert!(path::is_separator('/')); // '/' works for both Unix and Windows
+/// assert!(!path::is_separator('❤'));
+/// ```
+#[must_use]
+#[stable(feature = "rust1", since = "1.0.0")]
+pub fn is_separator(c: char) -> bool {
+ c.is_ascii() && is_sep_byte(c as u8)
+}
+
+/// The primary separator of path components for the current platform.
+///
+/// For example, `/` on Unix and `\` on Windows.
+#[stable(feature = "rust1", since = "1.0.0")]
+pub const MAIN_SEPARATOR: char = MAIN_SEP;
+
+/// The primary separator of path components for the current platform.
+///
+/// For example, `/` on Unix and `\` on Windows.
+#[stable(feature = "main_separator_str", since = "1.68.0")]
+pub const MAIN_SEPARATOR_STR: &str = MAIN_SEP_STR;
+
+////////////////////////////////////////////////////////////////////////////////
+// Misc helpers
+////////////////////////////////////////////////////////////////////////////////
+
+// Iterate through `iter` while it matches `prefix`; return `None` if `prefix`
+// is not a prefix of `iter`, otherwise return `Some(iter_after_prefix)` giving
+// `iter` after having exhausted `prefix`.
+fn iter_after<'a, 'b, I, J>(mut iter: I, mut prefix: J) -> Option
+where
+ I: Iterator- > + Clone,
+ J: Iterator
- >,
+{
+ loop {
+ let mut iter_next = iter.clone();
+ match (iter_next.next(), prefix.next()) {
+ (Some(ref x), Some(ref y)) if x == y => (),
+ (Some(_), Some(_)) => return None,
+ (Some(_), None) => return Some(iter),
+ (None, None) => return Some(iter),
+ (None, Some(_)) => return None,
+ }
+ iter = iter_next;
+ }
+}
+
+// Detect scheme on Redox
+fn has_redox_scheme(s: &[u8]) -> bool {
+ cfg!(target_os = "redox") && s.contains(&b':')
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Cross-platform, iterator-independent parsing
+////////////////////////////////////////////////////////////////////////////////
+
+/// Says whether the first byte after the prefix is a separator.
+fn has_physical_root(s: &[u8], prefix: Option>) -> bool {
+ let path = if let Some(p) = prefix { &s[p.len()..] } else { s };
+ !path.is_empty() && is_sep_byte(path[0])
+}
+
+// basic workhorse for splitting stem and extension
+fn rsplit_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) {
+ if file.as_encoded_bytes() == b".." {
+ return (Some(file), None);
+ }
+
+ let mut iter = file.as_encoded_bytes().rsplitn(2, |b| *b == b'.');
+ let after = iter.next();
+ let before = iter.next();
+ if before == Some(b"") {
+ (Some(file), None)
+ } else {
+ // SAFETY:
+ // The unsafety here stems from converting between &OsStr and &[u8]
+ // and back. This is safe to do because (1) we only look at ASCII
+ // contents of the encoding and (2) new &OsStr values are produced
+ // only from ASCII-bounded slices of existing &OsStr values.
+ unsafe {
+ (
+ before.map(|s| OsStr::from_encoded_bytes_unchecked(s)),
+ after.map(|s| OsStr::from_encoded_bytes_unchecked(s)),
+ )
+ }
+ }
+}
+
+fn split_file_at_dot(file: &OsStr) -> (&OsStr, Option<&OsStr>) {
+ let slice = file.as_encoded_bytes();
+ if slice == b".." {
+ return (file, None);
+ }
+
+ let i = match slice[1..].iter().position(|b| *b == b'.') {
+ Some(i) => i + 1,
+ None => return (file, None),
+ };
+ let before = &slice[..i];
+ let after = &slice[i + 1..];
+ // SAFETY:
+ // The unsafety here stems from converting between &OsStr and &[u8]
+ // and back. This is safe to do because (1) we only look at ASCII
+ // contents of the encoding and (2) new &OsStr values are produced
+ // only from ASCII-bounded slices of existing &OsStr values.
+ unsafe {
+ (
+ OsStr::from_encoded_bytes_unchecked(before),
+ Some(OsStr::from_encoded_bytes_unchecked(after)),
+ )
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The core iterators
+////////////////////////////////////////////////////////////////////////////////
+
+/// Component parsing works by a double-ended state machine; the cursors at the
+/// front and back of the path each keep track of what parts of the path have
+/// been consumed so far.
+///
+/// Going front to back, a path is made up of a prefix, a starting
+/// directory component, and a body (of normal components)
+#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
+enum State {
+ Prefix = 0, // c:
+ StartDir = 1, // / or . or nothing
+ Body = 2, // foo/bar/baz
+ Done = 3,
+}
+
+/// A structure wrapping a Windows path prefix as well as its unparsed string
+/// representation.
+///
+/// In addition to the parsed [`Prefix`] information returned by [`kind`],
+/// `PrefixComponent` also holds the raw and unparsed [`OsStr`] slice,
+/// returned by [`as_os_str`].
+///
+/// Instances of this `struct` can be obtained by matching against the
+/// [`Prefix` variant] on [`Component`].
+///
+/// Does not occur on Unix.
+///
+/// # Examples
+///
+/// ```
+/// # if cfg!(windows) {
+/// use std::path::{Component, Path, Prefix};
+/// use std::ffi::OsStr;
+///
+/// let path = Path::new(r"c:\you\later\");
+/// match path.components().next().unwrap() {
+/// Component::Prefix(prefix_component) => {
+/// assert_eq!(Prefix::Disk(b'C'), prefix_component.kind());
+/// assert_eq!(OsStr::new("c:"), prefix_component.as_os_str());
+/// }
+/// _ => unreachable!(),
+/// }
+/// # }
+/// ```
+///
+/// [`as_os_str`]: PrefixComponent::as_os_str
+/// [`kind`]: PrefixComponent::kind
+/// [`Prefix` variant]: Component::Prefix
+#[stable(feature = "rust1", since = "1.0.0")]
+#[derive(Copy, Clone, Eq, Debug)]
+pub struct PrefixComponent<'a> {
+ /// The prefix as an unparsed `OsStr` slice.
+ raw: &'a OsStr,
+
+ /// The parsed prefix data.
+ parsed: Prefix<'a>,
+}
+
+impl<'a> PrefixComponent<'a> {
+ /// Returns the parsed prefix data.
+ ///
+ /// See [`Prefix`]'s documentation for more information on the different
+ /// kinds of prefixes.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ #[must_use]
+ #[inline]
+ pub fn kind(&self) -> Prefix<'a> {
+ self.parsed
+ }
+
+ /// Returns the raw [`OsStr`] slice for this prefix.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ #[must_use]
+ #[inline]
+ pub fn as_os_str(&self) -> &'a OsStr {
+ self.raw
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<'a> PartialEq for PrefixComponent<'a> {
+ #[inline]
+ fn eq(&self, other: &PrefixComponent<'a>) -> bool {
+ self.parsed == other.parsed
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<'a> PartialOrd for PrefixComponent<'a> {
+ #[inline]
+ fn partial_cmp(&self, other: &PrefixComponent<'a>) -> Option {
+ PartialOrd::partial_cmp(&self.parsed, &other.parsed)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Ord for PrefixComponent<'_> {
+ #[inline]
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ Ord::cmp(&self.parsed, &other.parsed)
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Hash for PrefixComponent<'_> {
+ fn hash(&self, h: &mut H) {
+ self.parsed.hash(h);
+ }
+}
+
+/// A single component of a path.
+///
+/// A `Component` roughly corresponds to a substring between path separators
+/// (`/` or `\`).
+///
+/// This `enum` is created by iterating over [`Components`], which in turn is
+/// created by the [`components`](Path::components) method on [`Path`].
+///
+/// # Examples
+///
+/// ```rust
+/// use std::path::{Component, Path};
+///
+/// let path = Path::new("/tmp/foo/bar.txt");
+/// let components = path.components().collect::>();
+/// assert_eq!(&components, &[
+/// Component::RootDir,
+/// Component::Normal("tmp".as_ref()),
+/// Component::Normal("foo".as_ref()),
+/// Component::Normal("bar.txt".as_ref()),
+/// ]);
+/// ```
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+#[stable(feature = "rust1", since = "1.0.0")]
+pub enum Component<'a> {
+ /// A Windows path prefix, e.g., `C:` or `\\server\share`.
+ ///
+ /// There is a large variety of prefix types, see [`Prefix`]'s documentation
+ /// for more.
+ ///
+ /// Does not occur on Unix.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ Prefix(#[stable(feature = "rust1", since = "1.0.0")] PrefixComponent<'a>),
+
+ /// The root directory component, appears after any prefix and before anything else.
+ ///
+ /// It represents a separator that designates that a path starts from root.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ RootDir,
+
+ /// A reference to the current directory, i.e., `.`.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ CurDir,
+
+ /// A reference to the parent directory, i.e., `..`.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ ParentDir,
+
+ /// A normal component, e.g., `a` and `b` in `a/b`.
+ ///
+ /// This variant is the most common one, it represents references to files
+ /// or directories.
+ #[stable(feature = "rust1", since = "1.0.0")]
+ Normal(#[stable(feature = "rust1", since = "1.0.0")] &'a OsStr),
+}
+
+impl<'a> Component<'a> {
+ /// Extracts the underlying [`OsStr`] slice.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::path::Path;
+ ///
+ /// let path = Path::new("./tmp/foo/bar.txt");
+ /// let components: Vec<_> = path.components().map(|comp| comp.as_os_str()).collect();
+ /// assert_eq!(&components, &[".", "tmp", "foo", "bar.txt"]);
+ /// ```
+ #[must_use = "`self` will be dropped if the result is not used"]
+ #[stable(feature = "rust1", since = "1.0.0")]
+ pub fn as_os_str(self) -> &'a OsStr {
+ match self {
+ Component::Prefix(p) => p.as_os_str(),
+ Component::RootDir => OsStr::new(MAIN_SEP_STR),
+ Component::CurDir => OsStr::new("."),
+ Component::ParentDir => OsStr::new(".."),
+ Component::Normal(path) => path,
+ }
+ }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl AsRef for Component<'_> {
+ #[inline]
+ fn as_ref(&self) -> &OsStr {
+ self.as_os_str()
+ }
+}
+
+#[stable(feature = "path_component_asref", since = "1.25.0")]
+impl AsRef for Component<'_> {
+ #[inline]
+ fn as_ref(&self) -> &Path {
+ self.as_os_str().as_ref()
+ }
+}
+
+/// An iterator over the [`Component`]s of a [`Path`].
+///
+/// This `struct` is created by the [`components`] method on [`Path`].
+/// See its documentation for more.
+///
+/// # Examples
+///
+/// ```
+/// use std::path::Path;
+///
+/// let path = Path::new("/tmp/foo/bar.txt");
+///
+/// for component in path.components() {
+/// println!("{component:?}");
+/// }
+/// ```
+///
+/// [`components`]: Path::components
+#[derive(Clone)]
+#[must_use = "iterators are lazy and do nothing unless consumed"]
+#[stable(feature = "rust1", since = "1.0.0")]
+pub struct Components<'a> {
+ // The path left to parse components from
+ path: &'a [u8],
+
+ // The prefix as it was originally parsed, if any
+ prefix: Option