Question:
The compilation of crates fails in various ways when a certain combination of rustflags is set. With this configuration I encountered the following issues: During installation of the crate (version 0.7.5), rustc crashes with a . See the detailled error output below. When installing , the resulting binary doesn't work correctly (). Using each rustflag individually does not result in this issue. I could reproduce this both on stable and the recent nightly, and with set to either , or (without trying more). : <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summaryError during cargo install --force sd</summary<p</p</detailsAlso possibly relevant for .
I was able to reproduce the errors by specifying (in addition to ) the s or instead of setting a . Other s (e.g. ) work fine as far as I can tell. Possibly related to ?
Possibly related to ?
This was It may be good to figure out if this happens on lld on Linux: ping cleanup or if it's just Windows thing: ping windows Removing nomination also.
Hey Cleanup Crew ICE-breakers! This bug has been identified as a good "Cleanup ICE-breaking candidate". In case it's useful, here are some [instructions] for tackling these sorts of bugs. Maybe take a look? Thanks! <3 [instructions]: https://rustc-dev- cc
Second didn't work ... ping windows
ping windows
Hey Windows Group! This bug has been identified as a good "Windows candidate". In case it's useful, here are some [instructions] for tackling these sorts of bugs. Maybe take a look? Thanks! <3 [instructions]: https://rustc-dev- cc
I spent some time trying to reproduce this, but wasn't able to. Can you provide more specific repro steps? Alternately, can you run under WinDbg, and show the stack trace and at the faulting address? What model CPU are you running on? Is it possible that the target features you're requesting are not available on that CPU?
I'm using a , so at least should be an exact match. AVX is definitely supported as well. I can still reproduce the with the current nightly during . Here are the exact reproduction steps, with to limit the features used: Clean the registry to rule out interference with pre-existing caches: Install : There's nothing more I did to reproduce it. However, just like with the broken installation, it seems somewhat sporadic - about 1 out of 10 times the compilation succeeds. Meta: : : : I'm also using the latest Visual Studio Build Tools, if that's relevant. I'll investigate with WinDbg later today and report back.
Does not reproduce with on 2700X with: LLVM 10 from MSYS2 modify labels: -O-windows +O-windows-msvc
Also reproduces on a different system with a for me with the same reproduction steps as above. Here is the WinDbg output and the stack trace. I used the rustc command specified in the cargo error log. <details<summaryCommand output</summary</details<details<summaryStack trace</summary</detailsAdditionally the WinDbg output and the stack trace of running a miscompiled executable: <details<summaryCommand output</summary</details<details<summaryStack trace</summary
Looks like it's reading a value from TLS and adding an offset to use as a pointer that is not 32-byte aligned (): This is happening in the call which tries to read/write a thread local: LLVM seems to believe it should be 32 byte aligned, but as far as I can tell when ntdll is allocating the TLS slots they're 16 byte aligned Sounds like a similar issue to When built with lld-link there's no alignment specified for the TLS section in the DLL: But using the default msvc linker instead results in the correct alignment: Seems like an LLD bug?
Ah, that's great that you've found what appears to be the root cause. Are you / have you opened an issue for ? Is there more support needed from Windows devs?
I filed a bug:
Finally got around to digging into LLD. Fix at
I'm not sure, does this address the case of enabled AVX-512, where alignment should be even larger (64 byte instead of 32)?
Yes, it does. When the loader (ntdll) encounters a module (exe/dll) using TLS, it looks up the TLS directory in the PE image and uses the alignment specified there for all TLS data in the module. So my patch just makes LLD save the max alignment of all .tls sections it encounters in the final binary. So that means as long as you're annotating your 's with the right alignment then it will work fine, i.e. using which is . In terms of allowed values, it's powers of 2 up to 8192 bytes with 16 as the default if unspecified.

<del> Subproject commit 77a0125981b293260c9aec6355dff46ec707ab59 </del> <ins> Subproject commit 655a1467c98741e332b87661a9046877077ef4dc </ins>


Question:
Causes the following error: This is somewhat confusing, especially since is not a type. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --This issue has been assigned to via . <!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
This looks similar to what I reported in
Here we don't account for anything other than type mismatches which means that later we use the generic prefix We need to account for in the first snippet and ferry the prefix strings from there to the later (instead of what we do now).
I'd like to work on this claim
do not hesitate to reach out if you need help!
Do we need to get the correct prefix strings from the contents of or are they just ?
I believe they can be just "trait".

} debug!("note_type_err(diag={:?})", diag); <ins> enum Mismatch<'a> { Variable(ty::error::ExpectedFound<Ty<'a>>), Fixed(&'static str), } </ins> let (expected_found, exp_found, is_simple_error) = match values { <del> None => (None, None, false), </del> <ins> None => (None, Mismatch::Fixed("type"), false), </ins> Some(values) => { let (is_simple_error, exp_found) = match values { ValuePairs::Types(exp_found) => {


Question:
Causes the following error: This is somewhat confusing, especially since is not a type. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --This issue has been assigned to via . <!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
This looks similar to what I reported in
Here we don't account for anything other than type mismatches which means that later we use the generic prefix We need to account for in the first snippet and ferry the prefix strings from there to the later (instead of what we do now).
I'd like to work on this claim
do not hesitate to reach out if you need help!
Do we need to get the correct prefix strings from the contents of or are they just ?
I believe they can be just "trait".

) .report(diag); <del> (is_simple_err, Some(exp_found)) </del> <ins> (is_simple_err, Mismatch::Variable(exp_found)) </ins> } <del> _ => (false, None), </del> <ins> ValuePairs::TraitRefs(_) => (false, Mismatch::Fixed("trait")), _ => (false, Mismatch::Fixed("type")), </ins> }; let vals = match self.values_str(&values) { Some((expected, found)) => Some((expected, found)),


Question:
Causes the following error: This is somewhat confusing, especially since is not a type. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --This issue has been assigned to via . <!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
This looks similar to what I reported in
Here we don't account for anything other than type mismatches which means that later we use the generic prefix We need to account for in the first snippet and ferry the prefix strings from there to the later (instead of what we do now).
I'd like to work on this claim
do not hesitate to reach out if you need help!
Do we need to get the correct prefix strings from the contents of or are they just ?
I believe they can be just "trait".

} }; if let Some((expected, found)) = expected_found { <del> let expected_label = exp_found.map_or("type".into(), |ef| ef.expected.prefix_string()); let found_label = exp_found.map_or("type".into(), |ef| ef.found.prefix_string()); </del> <ins> let expected_label = match exp_found { Mismatch::Variable(ef) => ef.expected.prefix_string(), Mismatch::Fixed(s) => s.into(), }; let found_label = match exp_found { Mismatch::Variable(ef) => ef.found.prefix_string(), Mismatch::Fixed(s) => s.into(), }; let exp_found = match exp_found { Mismatch::Variable(exp_found) => Some(exp_found), Mismatch::Fixed(_) => None, }; </ins> match (&terr, expected == found) { (TypeError::Sorts(values), extra) => { let sort_string = |ty: Ty<'tcx>| match (extra, &ty.kind) {


Question:
Causes the following error: This is somewhat confusing, especially since is not a type. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --This issue has been assigned to via . <!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
This looks similar to what I reported in
Here we don't account for anything other than type mismatches which means that later we use the generic prefix We need to account for in the first snippet and ferry the prefix strings from there to the later (instead of what we do now).
I'd like to work on this claim
do not hesitate to reach out if you need help!
Do we need to get the correct prefix strings from the contents of or are they just ?
I believe they can be just "trait".

} } } <ins> let exp_found = match exp_found { Mismatch::Variable(exp_found) => Some(exp_found), Mismatch::Fixed(_) => None, }; </ins> if let Some(exp_found) = exp_found { self.suggest_as_ref_where_appropriate(span, &exp_found, diag); }


Question:
Causes the following error: This is somewhat confusing, especially since is not a type. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --This issue has been assigned to via . <!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
This looks similar to what I reported in
Here we don't account for anything other than type mismatches which means that later we use the generic prefix We need to account for in the first snippet and ferry the prefix strings from there to the later (instead of what we do now).
I'd like to work on this claim
do not hesitate to reach out if you need help!
Do we need to get the correct prefix strings from the contents of or are they just ?
I believe they can be just "trait".

<ins> trait DynEq {} impl<'a> PartialEq for &'a (dyn DynEq + 'static) { fn eq(&self, _other: &Self) -> bool { true } } impl Eq for &dyn DynEq {} //~ ERROR E0308 fn main() { } </ins>


Question:
Causes the following error: This is somewhat confusing, especially since is not a type. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --This issue has been assigned to via . <!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
This looks similar to what I reported in
Here we don't account for anything other than type mismatches which means that later we use the generic prefix We need to account for in the first snippet and ferry the prefix strings from there to the later (instead of what we do now).
I'd like to work on this claim
do not hesitate to reach out if you need help!
Do we need to get the correct prefix strings from the contents of or are they just ?
I believe they can be just "trait".

<ins> error[E0308]: mismatched types --> $DIR/E0308-2.rs:9:6 | LL | impl Eq for &dyn DynEq {} |      ^^ lifetime mismatch | = note: expected trait `std::cmp::PartialEq` found trait `std::cmp::PartialEq` note: the lifetime `'_` as defined on the impl at 9:13... --> $DIR/E0308-2.rs:9:13 | LL | impl Eq for &dyn DynEq {} |             ^ = note: ...does not necessarily outlive the static lifetime error: aborting due to previous error For more information about this error, try `rustc --explain E0308`. </ins>


Question:
and all interfaces directly with the file-system should probably use rather than trying to coerce to handle these cases. (Similar issue to .)
nominating feature-complete
Accepted for backwards-compatible
My last referenced commit is a month old, but I'm still working on this issue (currently finishing up the support for Windows paths).
The issues here of dealing with filesystems that are not utf8 seem related to , at least tangentially.

<ins> // Copyright 2013 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Cross-platform file path handling (re-write) use container::Container; use c_str::CString; use clone::Clone; use iter::Iterator; use option::{Option, None, Some}; use str; use str::StrSlice; use vec; use vec::{CopyableVector, OwnedCopyableVector, OwnedVector}; use vec::{ImmutableEqVector, ImmutableVector}; pub mod posix; pub mod windows; /// Typedef for POSIX file paths. /// See `posix::Path` for more info. pub type PosixPath = posix::Path; /// Typedef for Windows file paths. /// See `windows::Path` for more info. pub type WindowsPath = windows::Path; /// Typedef for the platform-native path type #[cfg(unix)] pub type Path = PosixPath; /// Typedef for the platform-native path type #[cfg(windows)] pub type Path = WindowsPath; /// Typedef for the POSIX path component iterator. /// See `posix::ComponentIter` for more info. pub type PosixComponentIter<'self> = posix::ComponentIter<'self>; // /// Typedef for the Windows path component iterator. // /// See `windows::ComponentIter` for more info. // pub type WindowsComponentIter<'self> = windows::ComponentIter<'self>; /// Typedef for the platform-native component iterator #[cfg(unix)] pub type ComponentIter<'self> = PosixComponentIter<'self>; // /// Typedef for the platform-native component iterator //#[cfg(windows)] //pub type ComponentIter<'self> = WindowsComponentIter<'self>; // Condition that is raised when a NUL is found in a byte vector given to a Path function condition! { // this should be a &[u8] but there's a lifetime issue null_byte: ~[u8] -> ~[u8]; } /// A trait that represents the generic operations available on paths pub trait GenericPath: Clone + GenericPathUnsafe { /// Creates a new Path from a byte vector. /// The resulting Path will always be normalized. /// /// # Failure /// /// Raises the `null_byte` condition if the path contains a NUL. /// /// See individual Path impls for additional restrictions. #[inline] fn from_vec(path: &[u8]) -> Self { if contains_nul(path) { let path = self::null_byte::cond.raise(path.to_owned()); assert!(!contains_nul(path)); unsafe { GenericPathUnsafe::from_vec_unchecked(path) } } else { unsafe { GenericPathUnsafe::from_vec_unchecked(path) } } } /// Creates a new Path from a string. /// The resulting Path will always be normalized. /// /// # Failure /// /// Raises the `null_byte` condition if the path contains a NUL. #[inline] fn from_str(path: &str) -> Self { let v = path.as_bytes(); if contains_nul(v) { GenericPath::from_vec(path.as_bytes()) // let from_vec handle the condition } else { unsafe { GenericPathUnsafe::from_str_unchecked(path) } } } /// Creates a new Path from a CString. /// The resulting Path will always be normalized. /// /// See individual Path impls for potential restrictions. #[inline] fn from_c_str(path: CString) -> Self { // CStrings can't contain NULs unsafe { GenericPathUnsafe::from_vec_unchecked(path.as_bytes()) } } /// Returns the path as a string, if possible. /// If the path is not representable in utf-8, this returns None. #[inline] fn as_str<'a>(&'a self) -> Option<&'a str> { str::from_utf8_slice_opt(self.as_vec()) } /// Returns the path as a byte vector fn as_vec<'a>(&'a self) -> &'a [u8]; /// Returns the directory component of `self`, as a byte vector (with no trailing separator). /// If `self` has no directory component, returns ['.']. fn dirname<'a>(&'a self) -> &'a [u8]; /// Returns the directory component of `self`, as a string, if possible. /// See `dirname` for details. #[inline] fn dirname_str<'a>(&'a self) -> Option<&'a str> { str::from_utf8_slice_opt(self.dirname()) } /// Returns the file component of `self`, as a byte vector. /// If `self` represents the root of the file hierarchy, returns the empty vector. /// If `self` is ".", returns the empty vector. fn filename<'a>(&'a self) -> &'a [u8]; /// Returns the file component of `self`, as a string, if possible. /// See `filename` for details. #[inline] fn filename_str<'a>(&'a self) -> Option<&'a str> { str::from_utf8_slice_opt(self.filename()) } /// Returns the stem of the filename of `self`, as a byte vector. /// The stem is the portion of the filename just before the last '.'. /// If there is no '.', the entire filename is returned. fn filestem<'a>(&'a self) -> &'a [u8] { let name = self.filename(); let dot = '.' as u8; match name.rposition_elem(&dot) { None | Some(0) => name, Some(1) if name == bytes!("..") => name, Some(pos) => name.slice_to(pos) } } /// Returns the stem of the filename of `self`, as a string, if possible. /// See `filestem` for details. #[inline] fn filestem_str<'a>(&'a self) -> Option<&'a str> { str::from_utf8_slice_opt(self.filestem()) } /// Returns the extension of the filename of `self`, as an optional byte vector. /// The extension is the portion of the filename just after the last '.'. /// If there is no extension, None is returned. /// If the filename ends in '.', the empty vector is returned. fn extension<'a>(&'a self) -> Option<&'a [u8]> { let name = self.filename(); let dot = '.' as u8; match name.rposition_elem(&dot) { None | Some(0) => None, Some(1) if name == bytes!("..") => None, Some(pos) => Some(name.slice_from(pos+1)) } } /// Returns the extension of the filename of `self`, as a string, if possible. /// See `extension` for details. #[inline] fn extension_str<'a>(&'a self) -> Option<&'a str> { self.extension().and_then(|v| str::from_utf8_slice_opt(v)) } /// Replaces the directory portion of the path with the given byte vector. /// If `self` represents the root of the filesystem hierarchy, the last path component /// of the given byte vector becomes the filename. /// /// # Failure /// /// Raises the `null_byte` condition if the dirname contains a NUL. #[inline] fn set_dirname(&mut self, dirname: &[u8]) { if contains_nul(dirname) { let dirname = self::null_byte::cond.raise(dirname.to_owned()); assert!(!contains_nul(dirname)); unsafe { self.set_dirname_unchecked(dirname) } } else { unsafe { self.set_dirname_unchecked(dirname) } } } /// Replaces the directory portion of the path with the given string. /// See `set_dirname` for details. #[inline] fn set_dirname_str(&mut self, dirname: &str) { if contains_nul(dirname.as_bytes()) { self.set_dirname(dirname.as_bytes()) // triggers null_byte condition } else { unsafe { self.set_dirname_str_unchecked(dirname) } } } /// Replaces the filename portion of the path with the given byte vector. /// If the replacement name is [], this is equivalent to popping the path. /// /// # Failure /// /// Raises the `null_byte` condition if the filename contains a NUL. #[inline] fn set_filename(&mut self, filename: &[u8]) { if contains_nul(filename) { let filename = self::null_byte::cond.raise(filename.to_owned()); assert!(!contains_nul(filename)); unsafe { self.set_filename_unchecked(filename) } } else { unsafe { self.set_filename_unchecked(filename) } } } /// Replaces the filename portion of the path with the given string. /// See `set_filename` for details. #[inline] fn set_filename_str(&mut self, filename: &str) { if contains_nul(filename.as_bytes()) { self.set_filename(filename.as_bytes()) // triggers null_byte condition } else { unsafe { self.set_filename_str_unchecked(filename) } } } /// Replaces the filestem with the given byte vector. /// If there is no extension in `self` (or `self` has no filename), this is equivalent /// to `set_filename`. Otherwise, if the given byte vector is [], the extension (including /// the preceding '.') becomes the new filename. /// /// # Failure /// /// Raises the `null_byte` condition if the filestem contains a NUL. fn set_filestem(&mut self, filestem: &[u8]) { // borrowck is being a pain here let val = { let name = self.filename(); if !name.is_empty() { let dot = '.' as u8; match name.rposition_elem(&dot) { None | Some(0) => None, Some(idx) => { let mut v; if contains_nul(filestem) { let filestem = self::null_byte::cond.raise(filestem.to_owned()); assert!(!contains_nul(filestem)); v = vec::with_capacity(filestem.len() + name.len() - idx); v.push_all(filestem); } else { v = vec::with_capacity(filestem.len() + name.len() - idx); v.push_all(filestem); } v.push_all(name.slice_from(idx)); Some(v) } } } else { None } }; match val { None => self.set_filename(filestem), Some(v) => unsafe { self.set_filename_unchecked(v) } } } /// Replaces the filestem with the given string. /// See `set_filestem` for details. #[inline] fn set_filestem_str(&mut self, filestem: &str) { self.set_filestem(filestem.as_bytes()) } /// Replaces the extension with the given byte vector. /// If there is no extension in `self`, this adds one. /// If the given byte vector is [], this removes the extension. /// If `self` has no filename, this is a no-op. /// /// # Failure /// /// Raises the `null_byte` condition if the extension contains a NUL. fn set_extension(&mut self, extension: &[u8]) { // borrowck causes problems here too let val = { let name = self.filename(); if !name.is_empty() { let dot = '.' as u8; match name.rposition_elem(&dot) { None | Some(0) => { if extension.is_empty() { None } else { let mut v; if contains_nul(extension) { let extension = self::null_byte::cond.raise(extension.to_owned()); assert!(!contains_nul(extension)); v = vec::with_capacity(name.len() + extension.len() + 1); v.push_all(name); v.push(dot); v.push_all(extension); } else { v = vec::with_capacity(name.len() + extension.len() + 1); v.push_all(name); v.push(dot); v.push_all(extension); } Some(v) } } Some(idx) => { if extension.is_empty() { Some(name.slice_to(idx).to_owned()) } else { let mut v; if contains_nul(extension) { let extension = self::null_byte::cond.raise(extension.to_owned()); assert!(!contains_nul(extension)); v = vec::with_capacity(idx + extension.len() + 1); v.push_all(name.slice_to(idx+1)); v.push_all(extension); } else { v = vec::with_capacity(idx + extension.len() + 1); v.push_all(name.slice_to(idx+1)); v.push_all(extension); } Some(v) } } } } else { None } }; match val { None => (), Some(v) => unsafe { self.set_filename_unchecked(v) } } } /// Replaces the extension with the given string. /// See `set_extension` for details. #[inline] fn set_extension_str(&mut self, extension: &str) { self.set_extension(extension.as_bytes()) } /// Returns a new Path constructed by replacing the dirname with the given byte vector. /// See `set_dirname` for details. /// /// # Failure /// /// Raises the `null_byte` condition if the dirname contains a NUL. #[inline] fn with_dirname(&self, dirname: &[u8]) -> Self { let mut p = self.clone(); p.set_dirname(dirname); p } /// Returns a new Path constructed by replacing the dirname with the given string. /// See `set_dirname` for details. #[inline] fn with_dirname_str(&self, dirname: &str) -> Self { let mut p = self.clone(); p.set_dirname_str(dirname); p } /// Returns a new Path constructed by replacing the filename with the given byte vector. /// See `set_filename` for details. /// /// # Failure /// /// Raises the `null_byte` condition if the filename contains a NUL. #[inline] fn with_filename(&self, filename: &[u8]) -> Self { let mut p = self.clone(); p.set_filename(filename); p } /// Returns a new Path constructed by replacing the filename with the given string. /// See `set_filename` for details. #[inline] fn with_filename_str(&self, filename: &str) -> Self { let mut p = self.clone(); p.set_filename_str(filename); p } /// Returns a new Path constructed by setting the filestem to the given byte vector. /// See `set_filestem` for details. /// /// # Failure /// /// Raises the `null_byte` condition if the filestem contains a NUL. #[inline] fn with_filestem(&self, filestem: &[u8]) -> Self { let mut p = self.clone(); p.set_filestem(filestem); p } /// Returns a new Path constructed by setting the filestem to the given string. /// See `set_filestem` for details. #[inline] fn with_filestem_str(&self, filestem: &str) -> Self { let mut p = self.clone(); p.set_filestem_str(filestem); p } /// Returns a new Path constructed by setting the extension to the given byte vector. /// See `set_extension` for details. /// /// # Failure /// /// Raises the `null_byte` condition if the extension contains a NUL. #[inline] fn with_extension(&self, extension: &[u8]) -> Self { let mut p = self.clone(); p.set_extension(extension); p } /// Returns a new Path constructed by setting the extension to the given string. /// See `set_extension` for details. #[inline] fn with_extension_str(&self, extension: &str) -> Self { let mut p = self.clone(); p.set_extension_str(extension); p } /// Returns the directory component of `self`, as a Path. /// If `self` represents the root of the filesystem hierarchy, returns `self`. fn dir_path(&self) -> Self { // self.dirname() returns a NUL-free vector unsafe { GenericPathUnsafe::from_vec_unchecked(self.dirname()) } } /// Returns the file component of `self`, as a relative Path. /// If `self` represents the root of the filesystem hierarchy, returns None. fn file_path(&self) -> Option<Self> { // self.filename() returns a NUL-free vector match self.filename() { [] => None, v => Some(unsafe { GenericPathUnsafe::from_vec_unchecked(v) }) } } /// Pushes a path (as a byte vector) onto `self`. /// If the argument represents an absolute path, it replaces `self`. /// /// # Failure /// /// Raises the `null_byte` condition if the path contains a NUL. #[inline] fn push(&mut self, path: &[u8]) { if contains_nul(path) { let path = self::null_byte::cond.raise(path.to_owned()); assert!(!contains_nul(path)); unsafe { self.push_unchecked(path) } } else { unsafe { self.push_unchecked(path) } } } /// Pushes a path (as a string) onto `self. /// See `push` for details. #[inline] fn push_str(&mut self, path: &str) { if contains_nul(path.as_bytes()) { self.push(path.as_bytes()) // triggers null_byte condition } else { unsafe { self.push_str_unchecked(path) } } } /// Pushes a Path onto `self`. /// If the argument represents an absolute path, it replaces `self`. #[inline] fn push_path(&mut self, path: &Self) { self.push(path.as_vec()) } /// Pops the last path component off of `self` and returns it. /// If `self` represents the root of the file hierarchy, None is returned. fn pop_opt(&mut self) -> Option<~[u8]>; /// Pops the last path component off of `self` and returns it as a string, if possible. /// `self` will still be modified even if None is returned. /// See `pop_opt` for details. #[inline] fn pop_opt_str(&mut self) -> Option<~str> { self.pop_opt().and_then(|v| str::from_utf8_owned_opt(v)) } /// Returns a new Path constructed by joining `self` with the given path (as a byte vector). /// If the given path is absolute, the new Path will represent just that. /// /// # Failure /// /// Raises the `null_byte` condition if the path contains a NUL. #[inline] fn join(&self, path: &[u8]) -> Self { let mut p = self.clone(); p.push(path); p } /// Returns a new Path constructed by joining `self` with the given path (as a string). /// See `join` for details. #[inline] fn join_str(&self, path: &str) -> Self { let mut p = self.clone(); p.push_str(path); p } /// Returns a new Path constructed by joining `self` with the given path. /// If the given path is absolute, the new Path will represent just that. #[inline] fn join_path(&self, path: &Self) -> Self { let mut p = self.clone(); p.push_path(path); p } /// Returns whether `self` represents an absolute path. /// An absolute path is defined as one that, when joined to another path, will /// yield back the same absolute path. fn is_absolute(&self) -> bool; /// Returns whether `self` is equal to, or is an ancestor of, the given path. /// If both paths are relative, they are compared as though they are relative /// to the same parent path. fn is_ancestor_of(&self, other: &Self) -> bool; /// Returns the Path that, were it joined to `base`, would yield `self`. /// If no such path exists, None is returned. /// If `self` is absolute and `base` is relative, or on Windows if both /// paths refer to separate drives, an absolute path is returned. fn path_relative_from(&self, base: &Self) -> Option<Self>; } /// A trait that represents the unsafe operations on GenericPaths pub trait GenericPathUnsafe { /// Creates a new Path from a byte vector without checking for null bytes. /// The resulting Path will always be normalized. unsafe fn from_vec_unchecked(path: &[u8]) -> Self; /// Creates a new Path from a str without checking for null bytes. /// The resulting Path will always be normalized. #[inline] unsafe fn from_str_unchecked(path: &str) -> Self { GenericPathUnsafe::from_vec_unchecked(path.as_bytes()) } /// Replaces the directory portion of the path with the given byte vector without /// checking for null bytes. /// See `set_dirname` for details. unsafe fn set_dirname_unchecked(&mut self, dirname: &[u8]); /// Replaces the directory portion of the path with the given str without /// checking for null bytes. /// See `set_dirname_str` for details. #[inline] unsafe fn set_dirname_str_unchecked(&mut self, dirname: &str) { self.set_dirname_unchecked(dirname.as_bytes()) } /// Replaces the filename portion of the path with the given byte vector without /// checking for null bytes. /// See `set_filename` for details. unsafe fn set_filename_unchecked(&mut self, filename: &[u8]); /// Replaces the filename portion of the path with the given str without /// checking for null bytes. /// See `set_filename_str` for details. #[inline] unsafe fn set_filename_str_unchecked(&mut self, filename: &str) { self.set_filename_unchecked(filename.as_bytes()) } /// Pushes a byte vector onto `self` without checking for null bytes. /// See `push` for details. unsafe fn push_unchecked(&mut self, path: &[u8]); /// Pushes a str onto `self` without checking for null bytes. /// See `push_str` for details. #[inline] unsafe fn push_str_unchecked(&mut self, path: &str) { self.push_unchecked(path.as_bytes()) } } #[inline(always)] fn contains_nul(v: &[u8]) -> bool { v.iter().any(|&x| x == 0) } </ins>


Question:
and all interfaces directly with the file-system should probably use rather than trying to coerce to handle these cases. (Similar issue to .)
nominating feature-complete
Accepted for backwards-compatible
My last referenced commit is a month old, but I'm still working on this issue (currently finishing up the support for Windows paths).
The issues here of dealing with filesystems that are not utf8 seem related to , at least tangentially.

<ins> // Copyright 2013 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! POSIX file path handling use container::Container; use c_str::{CString, ToCStr}; use clone::Clone; use cmp::Eq; use from_str::FromStr; use iter::{AdditiveIterator, Extendable, Iterator}; use option::{Option, None, Some}; use str; use str::Str; use util; use vec; use vec::CopyableVector; use vec::{Vector, VectorVector}; use super::{GenericPath, GenericPathUnsafe}; /// Iterator that yields successive components of a Path pub type ComponentIter<'self> = vec::SplitIterator<'self, u8>; /// Represents a POSIX file path #[deriving(Clone, DeepClone)] pub struct Path { priv repr: ~[u8], // assumed to never be empty or contain NULs priv sepidx: Option<uint> // index of the final separator in repr } /// The standard path separator character pub static sep: u8 = '/' as u8; /// Returns whether the given byte is a path separator #[inline] pub fn is_sep(u: &u8) -> bool { *u == sep } impl Eq for Path { #[inline] fn eq(&self, other: &Path) -> bool { self.repr == other.repr } } impl FromStr for Path { fn from_str(s: &str) -> Option<Path> { let v = s.as_bytes(); if contains_nul(v) { None } else { Some(unsafe { GenericPathUnsafe::from_vec_unchecked(v) }) } } } impl ToCStr for Path { #[inline] fn to_c_str(&self) -> CString { // The Path impl guarantees no internal NUL unsafe { self.as_vec().to_c_str_unchecked() } } #[inline] unsafe fn to_c_str_unchecked(&self) -> CString { self.as_vec().to_c_str_unchecked() } } impl GenericPathUnsafe for Path { unsafe fn from_vec_unchecked(path: &[u8]) -> Path { let path = Path::normalize(path); assert!(!path.is_empty()); let idx = path.rposition_elem(&sep); Path{ repr: path, sepidx: idx } } unsafe fn set_dirname_unchecked(&mut self, dirname: &[u8]) { match self.sepidx { None if bytes!(".") == self.repr || bytes!("..") == self.repr => { self.repr = Path::normalize(dirname); } None => { let mut v = vec::with_capacity(dirname.len() + self.repr.len() + 1); v.push_all(dirname); v.push(sep); v.push_all(self.repr); self.repr = Path::normalize(v); } Some(0) if self.repr.len() == 1 && self.repr[0] == sep => { self.repr = Path::normalize(dirname); } Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => { self.repr = Path::normalize(dirname); } Some(idx) if dirname.is_empty() => { let v = Path::normalize(self.repr.slice_from(idx+1)); self.repr = v; } Some(idx) => { let mut v = vec::with_capacity(dirname.len() + self.repr.len() - idx); v.push_all(dirname); v.push_all(self.repr.slice_from(idx)); self.repr = Path::normalize(v); } } self.sepidx = self.repr.rposition_elem(&sep); } unsafe fn set_filename_unchecked(&mut self, filename: &[u8]) { match self.sepidx { None if bytes!("..") == self.repr => { let mut v = vec::with_capacity(3 + filename.len()); v.push_all(dot_dot_static); v.push(sep); v.push_all(filename); self.repr = Path::normalize(v); } None => { self.repr = Path::normalize(filename); } Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => { let mut v = vec::with_capacity(self.repr.len() + 1 + filename.len()); v.push_all(self.repr); v.push(sep); v.push_all(filename); self.repr = Path::normalize(v); } Some(idx) => { let mut v = vec::with_capacity(idx + 1 + filename.len()); v.push_all(self.repr.slice_to(idx+1)); v.push_all(filename); self.repr = Path::normalize(v); } } self.sepidx = self.repr.rposition_elem(&sep); } unsafe fn push_unchecked(&mut self, path: &[u8]) { if !path.is_empty() { if path[0] == sep { self.repr = Path::normalize(path); }  else { let mut v = vec::with_capacity(self.repr.len() + path.len() + 1); v.push_all(self.repr); v.push(sep); v.push_all(path); self.repr = Path::normalize(v); } self.sepidx = self.repr.rposition_elem(&sep); } } } impl GenericPath for Path { #[inline] fn as_vec<'a>(&'a self) -> &'a [u8] { self.repr.as_slice() } fn dirname<'a>(&'a self) -> &'a [u8] { match self.sepidx { None if bytes!("..") == self.repr => self.repr.as_slice(), None => dot_static, Some(0) => self.repr.slice_to(1), Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => self.repr.as_slice(), Some(idx) => self.repr.slice_to(idx) } } fn filename<'a>(&'a self) -> &'a [u8] { match self.sepidx { None if bytes!(".") == self.repr || bytes!("..") == self.repr => &[], None => self.repr.as_slice(), Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => &[], Some(idx) => self.repr.slice_from(idx+1) } } fn pop_opt(&mut self) -> Option<~[u8]> { match self.sepidx { None if bytes!(".") == self.repr => None, None => { let mut v = ~['.' as u8]; util::swap(&mut v, &mut self.repr); self.sepidx = None; Some(v) } Some(0) if bytes!("/") == self.repr => None, Some(idx) => { let v = self.repr.slice_from(idx+1).to_owned(); if idx == 0 { self.repr.truncate(idx+1); } else { self.repr.truncate(idx); } self.sepidx = self.repr.rposition_elem(&sep); Some(v) } } } #[inline] fn is_absolute(&self) -> bool { self.repr[0] == sep } fn is_ancestor_of(&self, other: &Path) -> bool { if self.is_absolute() != other.is_absolute() { false } else { let mut ita = self.component_iter(); let mut itb = other.component_iter(); if bytes!(".") == self.repr { return itb.next() != Some(bytes!("..")); } loop { match (ita.next(), itb.next()) { (None, _) => break, (Some(a), Some(b)) if a == b => { loop }, (Some(a), _) if a == bytes!("..") => { // if ita contains only .. components, it's an ancestor return ita.all(|x| x == bytes!("..")); } _ => return false } } true } } fn path_relative_from(&self, base: &Path) -> Option<Path> { if self.is_absolute() != base.is_absolute() { if self.is_absolute() { Some(self.clone()) } else { None } } else { let mut ita = self.component_iter(); let mut itb = base.component_iter(); let mut comps = ~[]; loop { match (ita.next(), itb.next()) { (None, None) => break, (Some(a), None) => { comps.push(a); comps.extend(&mut ita); break; } (None, _) => comps.push(dot_dot_static), (Some(a), Some(b)) if comps.is_empty() && a == b => (), (Some(a), Some(b)) if b == bytes!(".") => comps.push(a), (Some(_), Some(b)) if b == bytes!("..") => return None, (Some(a), Some(_)) => { comps.push(dot_dot_static); for _ in itb { comps.push(dot_dot_static); } comps.push(a); comps.extend(&mut ita); break; } } } Some(Path::new(comps.connect_vec(&sep))) } } } impl Path { /// Returns a new Path from a byte vector /// /// # Failure /// /// Raises the `null_byte` condition if the vector contains a NUL. #[inline] pub fn new(v: &[u8]) -> Path { GenericPath::from_vec(v) } /// Returns a new Path from a string /// /// # Failure /// /// Raises the `null_byte` condition if the str contains a NUL. #[inline] pub fn from_str(s: &str) -> Path { GenericPath::from_str(s) } /// Converts the Path into an owned byte vector pub fn into_vec(self) -> ~[u8] { self.repr } /// Converts the Path into an owned string, if possible pub fn into_str(self) -> Option<~str> { str::from_utf8_owned_opt(self.repr) } /// Returns a normalized byte vector representation of a path, by removing all empty /// components, and unnecessary . and .. components. pub fn normalize<V: Vector<u8>+CopyableVector<u8>>(v: V) -> ~[u8] { // borrowck is being very picky let val = { let is_abs = !v.as_slice().is_empty() && v.as_slice()[0] == sep; let v_ = if is_abs { v.as_slice().slice_from(1) } else { v.as_slice() }; let comps = normalize_helper(v_, is_abs); match comps { None => None, Some(comps) => { if is_abs && comps.is_empty() { Some(~[sep]) } else { let n = if is_abs { comps.len() } else { comps.len() - 1} + comps.iter().map(|v| v.len()).sum(); let mut v = vec::with_capacity(n); let mut it = comps.move_iter(); if !is_abs { match it.next() { None => (), Some(comp) => v.push_all(comp) } } for comp in it { v.push(sep); v.push_all(comp); } Some(v) } } } }; match val { None => v.into_owned(), Some(val) => val } } /// Returns an iterator that yields each component of the path in turn. /// Does not distinguish between absolute and relative paths, e.g. /// /a/b/c and a/b/c yield the same set of components. /// A path of "/" yields no components. A path of "." yields one component. pub fn component_iter<'a>(&'a self) -> ComponentIter<'a> { let v = if self.repr[0] == sep { self.repr.slice_from(1) } else { self.repr.as_slice() }; let mut ret = v.split_iter(is_sep); if v.is_empty() { // consume the empty "" component ret.next(); } ret } } // None result means the byte vector didn't need normalizing fn normalize_helper<'a>(v: &'a [u8], is_abs: bool) -> Option<~[&'a [u8]]> { if is_abs && v.as_slice().is_empty() { return None; } let mut comps: ~[&'a [u8]] = ~[]; let mut n_up = 0u; let mut changed = false; for comp in v.split_iter(is_sep) { if comp.is_empty() { changed = true } else if comp == bytes!(".") { changed = true } else if comp == bytes!("..") { if is_abs && comps.is_empty() { changed = true } else if comps.len() == n_up { comps.push(dot_dot_static); n_up += 1 } else { comps.pop_opt(); changed = true } } else { comps.push(comp) } } if changed { if comps.is_empty() && !is_abs { if v == bytes!(".") { return None; } comps.push(dot_static); } Some(comps) } else { None } } // FIXME (#8169): Pull this into parent module once visibility works #[inline(always)] fn contains_nul(v: &[u8]) -> bool { v.iter().any(|&x| x == 0) } static dot_static: &'static [u8] = &'static ['.' as u8]; static dot_dot_static: &'static [u8] = &'static ['.' as u8, '.' as u8]; #[cfg(test)] mod tests { use super::*; use option::{Some, None}; use iter::Iterator; use str; use vec::Vector; macro_rules! t( (s: $path:expr, $exp:expr) => ( { let path = $path; assert_eq!(path.as_str(), Some($exp)); } ); (v: $path:expr, $exp:expr) => ( { let path = $path; assert_eq!(path.as_vec(), $exp); } ) ) macro_rules! b( ($($arg:expr),+) => ( bytes!($($arg),+) ) ) #[test] fn test_paths() { t!(v: Path::new([]), b!(".")); t!(v: Path::new(b!("/")), b!("/")); t!(v: Path::new(b!("a/b/c")), b!("a/b/c")); t!(v: Path::new(b!("a/b/c", 0xff)), b!("a/b/c", 0xff)); t!(v: Path::new(b!(0xff, "/../foo", 0x80)), b!("foo", 0x80)); let p = Path::new(b!("a/b/c", 0xff)); assert_eq!(p.as_str(), None); t!(s: Path::from_str(""), "."); t!(s: Path::from_str("/"), "/"); t!(s: Path::from_str("hi"), "hi"); t!(s: Path::from_str("hi/"), "hi"); t!(s: Path::from_str("/lib"), "/lib"); t!(s: Path::from_str("/lib/"), "/lib"); t!(s: Path::from_str("hi/there"), "hi/there"); t!(s: Path::from_str("hi/there.txt"), "hi/there.txt"); t!(s: Path::from_str("hi/there/"), "hi/there"); t!(s: Path::from_str("hi/../there"), "there"); t!(s: Path::from_str("../hi/there"), "../hi/there"); t!(s: Path::from_str("/../hi/there"), "/hi/there"); t!(s: Path::from_str("foo/.."), "."); t!(s: Path::from_str("/foo/.."), "/"); t!(s: Path::from_str("/foo/../.."), "/"); t!(s: Path::from_str("/foo/../../bar"), "/bar"); t!(s: Path::from_str("/./hi/./there/."), "/hi/there"); t!(s: Path::from_str("/./hi/./there/./.."), "/hi"); t!(s: Path::from_str("foo/../.."), ".."); t!(s: Path::from_str("foo/../../.."), "../.."); t!(s: Path::from_str("foo/../../bar"), "../bar"); assert_eq!(Path::new(b!("foo/bar")).into_vec(), b!("foo/bar").to_owned()); assert_eq!(Path::new(b!("/foo/../../bar")).into_vec(), b!("/bar").to_owned()); assert_eq!(Path::from_str("foo/bar").into_str(), Some(~"foo/bar")); assert_eq!(Path::from_str("/foo/../../bar").into_str(), Some(~"/bar")); let p = Path::new(b!("foo/bar", 0x80)); assert_eq!(p.as_str(), None); assert_eq!(Path::new(b!("foo", 0xff, "/bar")).into_str(), None); } #[test] fn test_null_byte() { use path2::null_byte::cond; let mut handled = false; let mut p = do cond.trap(|v| { handled = true; assert_eq!(v.as_slice(), b!("foo/bar", 0)); (b!("/bar").to_owned()) }).inside { Path::new(b!("foo/bar", 0)) }; assert!(handled); assert_eq!(p.as_vec(), b!("/bar")); handled = false; do cond.trap(|v| { handled = true; assert_eq!(v.as_slice(), b!("f", 0, "o")); (b!("foo").to_owned()) }).inside { p.set_filename(b!("f", 0, "o")) }; assert!(handled); assert_eq!(p.as_vec(), b!("/foo")); handled = false; do cond.trap(|v| { handled = true; assert_eq!(v.as_slice(), b!("null/", 0, "/byte")); (b!("null/byte").to_owned()) }).inside { p.set_dirname(b!("null/", 0, "/byte")); }; assert!(handled); assert_eq!(p.as_vec(), b!("null/byte/foo")); handled = false; do cond.trap(|v| { handled = true; assert_eq!(v.as_slice(), b!("f", 0, "o")); (b!("foo").to_owned()) }).inside { p.push(b!("f", 0, "o")); }; assert!(handled); assert_eq!(p.as_vec(), b!("null/byte/foo/foo")); } #[test] fn test_null_byte_fail() { use path2::null_byte::cond; use task; macro_rules! t( ($name:expr => $code:block) => ( { let mut t = task::task(); t.supervised(); t.name($name); let res = do t.try $code; assert!(res.is_err()); } ) ) t!(~"new() w/nul" => { do cond.trap(|_| { (b!("null", 0).to_owned()) }).inside { Path::new(b!("foo/bar", 0)) }; }) t!(~"set_filename w/nul" => { let mut p = Path::new(b!("foo/bar")); do cond.trap(|_| { (b!("null", 0).to_owned()) }).inside { p.set_filename(b!("foo", 0)) }; }) t!(~"set_dirname w/nul" => { let mut p = Path::new(b!("foo/bar")); do cond.trap(|_| { (b!("null", 0).to_owned()) }).inside { p.set_dirname(b!("foo", 0)) }; }) t!(~"push w/nul" => { let mut p = Path::new(b!("foo/bar")); do cond.trap(|_| { (b!("null", 0).to_owned()) }).inside { p.push(b!("foo", 0)) }; }) } #[test] fn test_components() { macro_rules! t( (s: $path:expr, $op:ident, $exp:expr) => ( { let path = Path::from_str($path); assert_eq!(path.$op(), ($exp).as_bytes()); } ); (s: $path:expr, $op:ident, $exp:expr, opt) => ( { let path = Path::from_str($path); let left = path.$op().map(|&x| str::from_utf8_slice(x)); assert_eq!(left, $exp); } ); (v: $path:expr, $op:ident, $exp:expr) => ( { let path = Path::new($path); assert_eq!(path.$op(), $exp); } ) ) t!(v: b!("a/b/c"), filename, b!("c")); t!(v: b!("a/b/c", 0xff), filename, b!("c", 0xff)); t!(v: b!("a/b", 0xff, "/c"), filename, b!("c")); t!(s: "a/b/c", filename, "c"); t!(s: "/a/b/c", filename, "c"); t!(s: "a", filename, "a"); t!(s: "/a", filename, "a"); t!(s: ".", filename, ""); t!(s: "/", filename, ""); t!(s: "..", filename, ""); t!(s: "../..", filename, ""); t!(v: b!("a/b/c"), dirname, b!("a/b")); t!(v: b!("a/b/c", 0xff), dirname, b!("a/b")); t!(v: b!("a/b", 0xff, "/c"), dirname, b!("a/b", 0xff)); t!(s: "a/b/c", dirname, "a/b"); t!(s: "/a/b/c", dirname, "/a/b"); t!(s: "a", dirname, "."); t!(s: "/a", dirname, "/"); t!(s: ".", dirname, "."); t!(s: "/", dirname, "/"); t!(s: "..", dirname, ".."); t!(s: "../..", dirname, "../.."); t!(v: b!("hi/there.txt"), filestem, b!("there")); t!(v: b!("hi/there", 0x80, ".txt"), filestem, b!("there", 0x80)); t!(v: b!("hi/there.t", 0x80, "xt"), filestem, b!("there")); t!(s: "hi/there.txt", filestem, "there"); t!(s: "hi/there", filestem, "there"); t!(s: "there.txt", filestem, "there"); t!(s: "there", filestem, "there"); t!(s: ".", filestem, ""); t!(s: "/", filestem, ""); t!(s: "foo/.bar", filestem, ".bar"); t!(s: ".bar", filestem, ".bar"); t!(s: "..bar", filestem, "."); t!(s: "hi/there..txt", filestem, "there."); t!(s: "..", filestem, ""); t!(s: "../..", filestem, ""); t!(v: b!("hi/there.txt"), extension, Some(b!("txt"))); t!(v: b!("hi/there", 0x80, ".txt"), extension, Some(b!("txt"))); t!(v: b!("hi/there.t", 0x80, "xt"), extension, Some(b!("t", 0x80, "xt"))); t!(v: b!("hi/there"), extension, None); t!(v: b!("hi/there", 0x80), extension, None); t!(s: "hi/there.txt", extension, Some("txt"), opt); t!(s: "hi/there", extension, None, opt); t!(s: "there.txt", extension, Some("txt"), opt); t!(s: "there", extension, None, opt); t!(s: ".", extension, None, opt); t!(s: "/", extension, None, opt); t!(s: "foo/.bar", extension, None, opt); t!(s: ".bar", extension, None, opt); t!(s: "..bar", extension, Some("bar"), opt); t!(s: "hi/there..txt", extension, Some("txt"), opt); t!(s: "..", extension, None, opt); t!(s: "../..", extension, None, opt); } #[test] fn test_push() { macro_rules! t( (s: $path:expr, $join:expr) => ( { let path = ($path); let join = ($join); let mut p1 = Path::from_str(path); let p2 = p1.clone(); p1.push_str(join); assert_eq!(p1, p2.join_str(join)); } ) ) t!(s: "a/b/c", ".."); t!(s: "/a/b/c", "d"); t!(s: "a/b", "c/d"); t!(s: "a/b", "/c/d"); } #[test] fn test_push_path() { macro_rules! t( (s: $path:expr, $push:expr, $exp:expr) => ( { let mut p = Path::from_str($path); let push = Path::from_str($push); p.push_path(&push); assert_eq!(p.as_str(), Some($exp)); } ) ) t!(s: "a/b/c", "d", "a/b/c/d"); t!(s: "/a/b/c", "d", "/a/b/c/d"); t!(s: "a/b", "c/d", "a/b/c/d"); t!(s: "a/b", "/c/d", "/c/d"); t!(s: "a/b", ".", "a/b"); t!(s: "a/b", "../c", "a/c"); } #[test] fn test_pop() { macro_rules! t( (s: $path:expr, $left:expr, $right:expr) => ( { let mut p = Path::from_str($path); let file = p.pop_opt_str(); assert_eq!(p.as_str(), Some($left)); assert_eq!(file.map(|s| s.as_slice()), $right); } ); (v: [$($path:expr),+], [$($left:expr),+], Some($($right:expr),+)) => ( { let mut p = Path::new(b!($($path),+)); let file = p.pop_opt(); assert_eq!(p.as_vec(), b!($($left),+)); assert_eq!(file.map(|v| v.as_slice()), Some(b!($($right),+))); } ); (v: [$($path:expr),+], [$($left:expr),+], None) => ( { let mut p = Path::new(b!($($path),+)); let file = p.pop_opt(); assert_eq!(p.as_vec(), b!($($left),+)); assert_eq!(file, None); } ) ) t!(v: ["a/b/c"], ["a/b"], Some("c")); t!(v: ["a"], ["."], Some("a")); t!(v: ["."], ["."], None); t!(v: ["/a"], ["/"], Some("a")); t!(v: ["/"], ["/"], None); t!(v: ["a/b/c", 0x80], ["a/b"], Some("c", 0x80)); t!(v: ["a/b", 0x80, "/c"], ["a/b", 0x80], Some("c")); t!(v: [0xff], ["."], Some(0xff)); t!(v: ["/", 0xff], ["/"], Some(0xff)); t!(s: "a/b/c", "a/b", Some("c")); t!(s: "a", ".", Some("a")); t!(s: ".", ".", None); t!(s: "/a", "/", Some("a")); t!(s: "/", "/", None); assert_eq!(Path::new(b!("foo/bar", 0x80)).pop_opt_str(), None); assert_eq!(Path::new(b!("foo", 0x80, "/bar")).pop_opt_str(), Some(~"bar")); } #[test] fn test_join() { t!(v: Path::new(b!("a/b/c")).join(b!("..")), b!("a/b")); t!(v: Path::new(b!("/a/b/c")).join(b!("d")), b!("/a/b/c/d")); t!(v: Path::new(b!("a/", 0x80, "/c")).join(b!(0xff)), b!("a/", 0x80, "/c/", 0xff)); t!(s: Path::from_str("a/b/c").join_str(".."), "a/b"); t!(s: Path::from_str("/a/b/c").join_str("d"), "/a/b/c/d"); t!(s: Path::from_str("a/b").join_str("c/d"), "a/b/c/d"); t!(s: Path::from_str("a/b").join_str("/c/d"), "/c/d"); t!(s: Path::from_str(".").join_str("a/b"), "a/b"); t!(s: Path::from_str("/").join_str("a/b"), "/a/b"); } #[test] fn test_join_path() { macro_rules! t( (s: $path:expr, $join:expr, $exp:expr) => ( { let path = Path::from_str($path); let join = Path::from_str($join); let res = path.join_path(&join); assert_eq!(res.as_str(), Some($exp)); } ) ) t!(s: "a/b/c", "..", "a/b"); t!(s: "/a/b/c", "d", "/a/b/c/d"); t!(s: "a/b", "c/d", "a/b/c/d"); t!(s: "a/b", "/c/d", "/c/d"); t!(s: ".", "a/b", "a/b"); t!(s: "/", "a/b", "/a/b"); } #[test] fn test_with_helpers() { t!(v: Path::new(b!("a/b/c")).with_dirname(b!("d")), b!("d/c")); t!(v: Path::new(b!("a/b/c")).with_dirname(b!("d/e")), b!("d/e/c")); t!(v: Path::new(b!("a/", 0x80, "b/c")).with_dirname(b!(0xff)), b!(0xff, "/c")); t!(v: Path::new(b!("a/b/", 0x80)).with_dirname(b!("/", 0xcd)), b!("/", 0xcd, "/", 0x80)); t!(s: Path::from_str("a/b/c").with_dirname_str("d"), "d/c"); t!(s: Path::from_str("a/b/c").with_dirname_str("d/e"), "d/e/c"); t!(s: Path::from_str("a/b/c").with_dirname_str(""), "c"); t!(s: Path::from_str("a/b/c").with_dirname_str("/"), "/c"); t!(s: Path::from_str("a/b/c").with_dirname_str("."), "c"); t!(s: Path::from_str("a/b/c").with_dirname_str(".."), "../c"); t!(s: Path::from_str("/").with_dirname_str("foo"), "foo"); t!(s: Path::from_str("/").with_dirname_str(""), "."); t!(s: Path::from_str("/foo").with_dirname_str("bar"), "bar/foo"); t!(s: Path::from_str("..").with_dirname_str("foo"), "foo"); t!(s: Path::from_str("../..").with_dirname_str("foo"), "foo"); t!(s: Path::from_str("..").with_dirname_str(""), "."); t!(s: Path::from_str("../..").with_dirname_str(""), "."); t!(s: Path::from_str("foo").with_dirname_str(".."), "../foo"); t!(s: Path::from_str("foo").with_dirname_str("../.."), "../../foo"); t!(v: Path::new(b!("a/b/c")).with_filename(b!("d")), b!("a/b/d")); t!(v: Path::new(b!("a/b/c", 0xff)).with_filename(b!(0x80)), b!("a/b/", 0x80)); t!(v: Path::new(b!("/", 0xff, "/foo")).with_filename(b!(0xcd)), b!("/", 0xff, "/", 0xcd)); t!(s: Path::from_str("a/b/c").with_filename_str("d"), "a/b/d"); t!(s: Path::from_str(".").with_filename_str("foo"), "foo"); t!(s: Path::from_str("/a/b/c").with_filename_str("d"), "/a/b/d"); t!(s: Path::from_str("/").with_filename_str("foo"), "/foo"); t!(s: Path::from_str("/a").with_filename_str("foo"), "/foo"); t!(s: Path::from_str("foo").with_filename_str("bar"), "bar"); t!(s: Path::from_str("/").with_filename_str("foo/"), "/foo"); t!(s: Path::from_str("/a").with_filename_str("foo/"), "/foo"); t!(s: Path::from_str("a/b/c").with_filename_str(""), "a/b"); t!(s: Path::from_str("a/b/c").with_filename_str("."), "a/b"); t!(s: Path::from_str("a/b/c").with_filename_str(".."), "a"); t!(s: Path::from_str("/a").with_filename_str(""), "/"); t!(s: Path::from_str("foo").with_filename_str(""), "."); t!(s: Path::from_str("a/b/c").with_filename_str("d/e"), "a/b/d/e"); t!(s: Path::from_str("a/b/c").with_filename_str("/d"), "a/b/d"); t!(s: Path::from_str("..").with_filename_str("foo"), "../foo"); t!(s: Path::from_str("../..").with_filename_str("foo"), "../../foo"); t!(s: Path::from_str("..").with_filename_str(""), ".."); t!(s: Path::from_str("../..").with_filename_str(""), "../.."); t!(v: Path::new(b!("hi/there", 0x80, ".txt")).with_filestem(b!(0xff)), b!("hi/", 0xff, ".txt")); t!(v: Path::new(b!("hi/there.txt", 0x80)).with_filestem(b!(0xff)), b!("hi/", 0xff, ".txt", 0x80)); t!(v: Path::new(b!("hi/there", 0xff)).with_filestem(b!(0x80)), b!("hi/", 0x80)); t!(v: Path::new(b!("hi", 0x80, "/there")).with_filestem([]), b!("hi", 0x80)); t!(s: Path::from_str("hi/there.txt").with_filestem_str("here"), "hi/here.txt"); t!(s: Path::from_str("hi/there.txt").with_filestem_str(""), "hi/.txt"); t!(s: Path::from_str("hi/there.txt").with_filestem_str("."), "hi/..txt"); t!(s: Path::from_str("hi/there.txt").with_filestem_str(".."), "hi/...txt"); t!(s: Path::from_str("hi/there.txt").with_filestem_str("/"), "hi/.txt"); t!(s: Path::from_str("hi/there.txt").with_filestem_str("foo/bar"), "hi/foo/bar.txt"); t!(s: Path::from_str("hi/there.foo.txt").with_filestem_str("here"), "hi/here.txt"); t!(s: Path::from_str("hi/there").with_filestem_str("here"), "hi/here"); t!(s: Path::from_str("hi/there").with_filestem_str(""), "hi"); t!(s: Path::from_str("hi").with_filestem_str(""), "."); t!(s: Path::from_str("/hi").with_filestem_str(""), "/"); t!(s: Path::from_str("hi/there").with_filestem_str(".."), "."); t!(s: Path::from_str("hi/there").with_filestem_str("."), "hi"); t!(s: Path::from_str("hi/there.").with_filestem_str("foo"), "hi/foo."); t!(s: Path::from_str("hi/there.").with_filestem_str(""), "hi"); t!(s: Path::from_str("hi/there.").with_filestem_str("."), "."); t!(s: Path::from_str("hi/there.").with_filestem_str(".."), "hi/..."); t!(s: Path::from_str("/").with_filestem_str("foo"), "/foo"); t!(s: Path::from_str(".").with_filestem_str("foo"), "foo"); t!(s: Path::from_str("hi/there..").with_filestem_str("here"), "hi/here."); t!(s: Path::from_str("hi/there..").with_filestem_str(""), "hi"); t!(v: Path::new(b!("hi/there", 0x80, ".txt")).with_extension(b!("exe")), b!("hi/there", 0x80, ".exe")); t!(v: Path::new(b!("hi/there.txt", 0x80)).with_extension(b!(0xff)), b!("hi/there.", 0xff)); t!(v: Path::new(b!("hi/there", 0x80)).with_extension(b!(0xff)), b!("hi/there", 0x80, ".", 0xff)); t!(v: Path::new(b!("hi/there.", 0xff)).with_extension([]), b!("hi/there")); t!(s: Path::from_str("hi/there.txt").with_extension_str("exe"), "hi/there.exe"); t!(s: Path::from_str("hi/there.txt").with_extension_str(""), "hi/there"); t!(s: Path::from_str("hi/there.txt").with_extension_str("."), "hi/there.."); t!(s: Path::from_str("hi/there.txt").with_extension_str(".."), "hi/there..."); t!(s: Path::from_str("hi/there").with_extension_str("txt"), "hi/there.txt"); t!(s: Path::from_str("hi/there").with_extension_str("."), "hi/there.."); t!(s: Path::from_str("hi/there").with_extension_str(".."), "hi/there..."); t!(s: Path::from_str("hi/there.").with_extension_str("txt"), "hi/there.txt"); t!(s: Path::from_str("hi/.foo").with_extension_str("txt"), "hi/.foo.txt"); t!(s: Path::from_str("hi/there.txt").with_extension_str(".foo"), "hi/there..foo"); t!(s: Path::from_str("/").with_extension_str("txt"), "/"); t!(s: Path::from_str("/").with_extension_str("."), "/"); t!(s: Path::from_str("/").with_extension_str(".."), "/"); t!(s: Path::from_str(".").with_extension_str("txt"), "."); } #[test] fn test_setters() { macro_rules! t( (s: $path:expr, $set:ident, $with:ident, $arg:expr) => ( { let path = $path; let arg = $arg; let mut p1 = Path::from_str(path); p1.$set(arg); let p2 = Path::from_str(path); assert_eq!(p1, p2.$with(arg)); } ); (v: $path:expr, $set:ident, $with:ident, $arg:expr) => ( { let path = $path; let arg = $arg; let mut p1 = Path::new(path); p1.$set(arg); let p2 = Path::new(path); assert_eq!(p1, p2.$with(arg)); } ) ) t!(v: b!("a/b/c"), set_dirname, with_dirname, b!("d")); t!(v: b!("a/b/c"), set_dirname, with_dirname, b!("d/e")); t!(v: b!("a/", 0x80, "/c"), set_dirname, with_dirname, b!(0xff)); t!(s: "a/b/c", set_dirname_str, with_dirname_str, "d"); t!(s: "a/b/c", set_dirname_str, with_dirname_str, "d/e"); t!(s: "/", set_dirname_str, with_dirname_str, "foo"); t!(s: "/foo", set_dirname_str, with_dirname_str, "bar"); t!(s: "a/b/c", set_dirname_str, with_dirname_str, ""); t!(s: "../..", set_dirname_str, with_dirname_str, "x"); t!(s: "foo", set_dirname_str, with_dirname_str, "../.."); t!(v: b!("a/b/c"), set_filename, with_filename, b!("d")); t!(v: b!("/"), set_filename, with_filename, b!("foo")); t!(v: b!(0x80), set_filename, with_filename, b!(0xff)); t!(s: "a/b/c", set_filename_str, with_filename_str, "d"); t!(s: "/", set_filename_str, with_filename_str, "foo"); t!(s: ".", set_filename_str, with_filename_str, "foo"); t!(s: "a/b", set_filename_str, with_filename_str, ""); t!(s: "a", set_filename_str, with_filename_str, ""); t!(v: b!("hi/there.txt"), set_filestem, with_filestem, b!("here")); t!(v: b!("hi/there", 0x80, ".txt"), set_filestem, with_filestem, b!("here", 0xff)); t!(s: "hi/there.txt", set_filestem_str, with_filestem_str, "here"); t!(s: "hi/there.", set_filestem_str, with_filestem_str, "here"); t!(s: "hi/there", set_filestem_str, with_filestem_str, "here"); t!(s: "hi/there.txt", set_filestem_str, with_filestem_str, ""); t!(s: "hi/there", set_filestem_str, with_filestem_str, ""); t!(v: b!("hi/there.txt"), set_extension, with_extension, b!("exe")); t!(v: b!("hi/there.t", 0x80, "xt"), set_extension, with_extension, b!("exe", 0xff)); t!(s: "hi/there.txt", set_extension_str, with_extension_str, "exe"); t!(s: "hi/there.", set_extension_str, with_extension_str, "txt"); t!(s: "hi/there", set_extension_str, with_extension_str, "txt"); t!(s: "hi/there.txt", set_extension_str, with_extension_str, ""); t!(s: "hi/there", set_extension_str, with_extension_str, ""); t!(s: ".", set_extension_str, with_extension_str, "txt"); } #[test] fn test_getters() { macro_rules! t( (s: $path:expr, $filename:expr, $dirname:expr, $filestem:expr, $ext:expr) => ( { let path = $path; assert_eq!(path.filename_str(), $filename); assert_eq!(path.dirname_str(), $dirname); assert_eq!(path.filestem_str(), $filestem); assert_eq!(path.extension_str(), $ext); } ); (v: $path:expr, $filename:expr, $dirname:expr, $filestem:expr, $ext:expr) => ( { let path = $path; assert_eq!(path.filename(), $filename); assert_eq!(path.dirname(), $dirname); assert_eq!(path.filestem(), $filestem); assert_eq!(path.extension(), $ext); } ) ) t!(v: Path::new(b!("a/b/c")), b!("c"), b!("a/b"), b!("c"), None); t!(v: Path::new(b!("a/b/", 0xff)), b!(0xff), b!("a/b"), b!(0xff), None); t!(v: Path::new(b!("hi/there.", 0xff)), b!("there.", 0xff), b!("hi"), b!("there"), Some(b!(0xff))); t!(s: Path::from_str("a/b/c"), Some("c"), Some("a/b"), Some("c"), None); t!(s: Path::from_str("."), Some(""), Some("."), Some(""), None); t!(s: Path::from_str("/"), Some(""), Some("/"), Some(""), None); t!(s: Path::from_str(".."), Some(""), Some(".."), Some(""), None); t!(s: Path::from_str("../.."), Some(""), Some("../.."), Some(""), None); t!(s: Path::from_str("hi/there.txt"), Some("there.txt"), Some("hi"), Some("there"), Some("txt")); t!(s: Path::from_str("hi/there"), Some("there"), Some("hi"), Some("there"), None); t!(s: Path::from_str("hi/there."), Some("there."), Some("hi"), Some("there"), Some("")); t!(s: Path::from_str("hi/.there"), Some(".there"), Some("hi"), Some(".there"), None); t!(s: Path::from_str("hi/..there"), Some("..there"), Some("hi"), Some("."), Some("there")); t!(s: Path::new(b!("a/b/", 0xff)), None, Some("a/b"), None, None); t!(s: Path::new(b!("a/b/", 0xff, ".txt")), None, Some("a/b"), None, Some("txt")); t!(s: Path::new(b!("a/b/c.", 0x80)), None, Some("a/b"), Some("c"), None); t!(s: Path::new(b!(0xff, "/b")), Some("b"), None, Some("b"), None); } #[test] fn test_dir_file_path() { t!(v: Path::new(b!("hi/there", 0x80)).dir_path(), b!("hi")); t!(v: Path::new(b!("hi", 0xff, "/there")).dir_path(), b!("hi", 0xff)); t!(s: Path::from_str("hi/there").dir_path(), "hi"); t!(s: Path::from_str("hi").dir_path(), "."); t!(s: Path::from_str("/hi").dir_path(), "/"); t!(s: Path::from_str("/").dir_path(), "/"); t!(s: Path::from_str("..").dir_path(), ".."); t!(s: Path::from_str("../..").dir_path(), "../.."); macro_rules! t( (s: $path:expr, $exp:expr) => ( { let path = $path; let left = path.and_then_ref(|p| p.as_str()); assert_eq!(left, $exp); } ); (v: $path:expr, $exp:expr) => ( { let path = $path; let left = path.map(|p| p.as_vec()); assert_eq!(left, $exp); } ) ) t!(v: Path::new(b!("hi/there", 0x80)).file_path(), Some(b!("there", 0x80))); t!(v: Path::new(b!("hi", 0xff, "/there")).file_path(), Some(b!("there"))); t!(s: Path::from_str("hi/there").file_path(), Some("there")); t!(s: Path::from_str("hi").file_path(), Some("hi")); t!(s: Path::from_str(".").file_path(), None); t!(s: Path::from_str("/").file_path(), None); t!(s: Path::from_str("..").file_path(), None); t!(s: Path::from_str("../..").file_path(), None); } #[test] fn test_is_absolute() { assert_eq!(Path::from_str("a/b/c").is_absolute(), false); assert_eq!(Path::from_str("/a/b/c").is_absolute(), true); assert_eq!(Path::from_str("a").is_absolute(), false); assert_eq!(Path::from_str("/a").is_absolute(), true); assert_eq!(Path::from_str(".").is_absolute(), false); assert_eq!(Path::from_str("/").is_absolute(), true); assert_eq!(Path::from_str("..").is_absolute(), false); assert_eq!(Path::from_str("../..").is_absolute(), false); } #[test] fn test_is_ancestor_of() { macro_rules! t( (s: $path:expr, $dest:expr, $exp:expr) => ( { let path = Path::from_str($path); let dest = Path::from_str($dest); assert_eq!(path.is_ancestor_of(&dest), $exp); } ) ) t!(s: "a/b/c", "a/b/c/d", true); t!(s: "a/b/c", "a/b/c", true); t!(s: "a/b/c", "a/b", false); t!(s: "/a/b/c", "/a/b/c", true); t!(s: "/a/b", "/a/b/c", true); t!(s: "/a/b/c/d", "/a/b/c", false); t!(s: "/a/b", "a/b/c", false); t!(s: "a/b", "/a/b/c", false); t!(s: "a/b/c", "a/b/d", false); t!(s: "../a/b/c", "a/b/c", false); t!(s: "a/b/c", "../a/b/c", false); t!(s: "a/b/c", "a/b/cd", false); t!(s: "a/b/cd", "a/b/c", false); t!(s: "../a/b", "../a/b/c", true); t!(s: ".", "a/b", true); t!(s: ".", ".", true); t!(s: "/", "/", true); t!(s: "/", "/a/b", true); t!(s: "..", "a/b", true); t!(s: "../..", "a/b", true); } #[test] fn test_path_relative_from() { macro_rules! t( (s: $path:expr, $other:expr, $exp:expr) => ( { let path = Path::from_str($path); let other = Path::from_str($other); let res = path.path_relative_from(&other); assert_eq!(res.and_then_ref(|x| x.as_str()), $exp); } ) ) t!(s: "a/b/c", "a/b", Some("c")); t!(s: "a/b/c", "a/b/d", Some("../c")); t!(s: "a/b/c", "a/b/c/d", Some("..")); t!(s: "a/b/c", "a/b/c", Some(".")); t!(s: "a/b/c", "a/b/c/d/e", Some("../..")); t!(s: "a/b/c", "a/d/e", Some("../../b/c")); t!(s: "a/b/c", "d/e/f", Some("../../../a/b/c")); t!(s: "a/b/c", "/a/b/c", None); t!(s: "/a/b/c", "a/b/c", Some("/a/b/c")); t!(s: "/a/b/c", "/a/b/c/d", Some("..")); t!(s: "/a/b/c", "/a/b", Some("c")); t!(s: "/a/b/c", "/a/b/c/d/e", Some("../..")); t!(s: "/a/b/c", "/a/d/e", Some("../../b/c")); t!(s: "/a/b/c", "/d/e/f", Some("../../../a/b/c")); t!(s: "hi/there.txt", "hi/there", Some("../there.txt")); t!(s: ".", "a", Some("..")); t!(s: ".", "a/b", Some("../..")); t!(s: ".", ".", Some(".")); t!(s: "a", ".", Some("a")); t!(s: "a/b", ".", Some("a/b")); t!(s: "..", ".", Some("..")); t!(s: "a/b/c", "a/b/c", Some(".")); t!(s: "/a/b/c", "/a/b/c", Some(".")); t!(s: "/", "/", Some(".")); t!(s: "/", ".", Some("/")); t!(s: "../../a", "b", Some("../../../a")); t!(s: "a", "../../b", None); t!(s: "../../a", "../../b", Some("../a")); t!(s: "../../a", "../../a/b", Some("..")); t!(s: "../../a/b", "../../a", Some("b")); } #[test] fn test_component_iter() { macro_rules! t( (s: $path:expr, $exp:expr) => ( { let path = Path::from_str($path); let comps = path.component_iter().to_owned_vec(); let exp: &[&str] = $exp; let exps = exp.iter().map(|x| x.as_bytes()).to_owned_vec(); assert_eq!(comps, exps); } ); (v: [$($arg:expr),+], [$([$($exp:expr),*]),*]) => ( { let path = Path::new(b!($($arg),+)); let comps = path.component_iter().to_owned_vec(); let exp: &[&[u8]] = [$(b!($($exp),*)),*]; assert_eq!(comps.as_slice(), exp); } ) ) t!(v: ["a/b/c"], [["a"], ["b"], ["c"]]); t!(v: ["/", 0xff, "/a/", 0x80], [[0xff], ["a"], [0x80]]); t!(v: ["../../foo", 0xcd, "bar"], [[".."], [".."], ["foo", 0xcd, "bar"]]); t!(s: "a/b/c", ["a", "b", "c"]); t!(s: "a/b/d", ["a", "b", "d"]); t!(s: "a/b/cd", ["a", "b", "cd"]); t!(s: "/a/b/c", ["a", "b", "c"]); t!(s: "a", ["a"]); t!(s: "/a", ["a"]); t!(s: "/", []); t!(s: ".", ["."]); t!(s: "..", [".."]); t!(s: "../..", ["..", ".."]); t!(s: "../../foo", ["..", "..", "foo"]); } } </ins>